I write open source software. I love Python ๐.
I am obsessed with HTTP, APIs and Hypermedia systems ๐
I live in Norwich, and work in London. ๐ฌ๐ง
๐ท I shoot film photography (currently on a Leica M6).
A pragmatic styleguide for Django API Projects
Home Page: https://phalt.github.io/django-api-domains/
License: MIT License
I write open source software. I love Python ๐.
I am obsessed with HTTP, APIs and Hypermedia systems ๐
I live in Norwich, and work in London. ๐ฌ๐ง
๐ท I shoot film photography (currently on a Leica M6).
When using DRF, apis.py contains DRF view functions or view classes. Then What does the interfaces.py look like? Do I need use requests to handle the transformation of data from other domains?
I am curious to know so wants to clarify
in https://phalt.github.io/django-api-domains/plugins/#django-rest-framework
you wrote
When using DRF, we can organise the logic in a domain this way:
urls.py - Router and URL configuration.
apis.py - DRF view functions or view classes.
serializers.py - Serialization for models.
and
https://phalt.github.io/django-api-domains/files (let's call this Files
section)
you have a
Plus you have this helpful diagram https://phalt.github.io/django-api-domains/styleguide/#visualisation
Can you help me to understand how the files work with DRF situation? Perhaps another diagram?
Because I cannot visualize how to use DRF on top of what you recommend under Files
Another related question is
DO you recommend the use of GenericAPIView or APIView classes in DRF for the apis.py
?
Do you mind giving a visual idea of how two different domains both using DRF would interact with one another similar to https://phalt.github.io/django-api-domains/examples/ ?
What if they are different apps of the same Django project?
Would love to get your thoughts on the best way to handle accessing nested foreign key relationships. Here's a made up example (where the relationship is Book
-> Author
-> PublishingCompany
).
Domain Inventory
:
# models.py
class Book(models.Model):
author = models.ForeignKey(Author)
class Author(models.Model):
publishing_company = models.ForeignKey(PublishingCompany)
class PublishingCompany(models.Model):
...
# apis.py
class BookAPI:
@staticmethod
def get(*, book_id: uuid.UUID) -> Dict:
return BookService.get_book(id=book_id)
Now let's say we have another Domain, Purchasing
that's responsible for placing orders. From this domain, say we want to do something access some info via book.author.publishing_company
. What would be the best way to handle this?
I can see two options, but neither seem great.
Option 1: Inside BookService.get_book()
, we can add Book.objects.get(id=id).select_related('author__publishing_company')
. The downside of this is that we are always fetching extra data, whether the caller uses it or not.
Option 2: Inside the Purchasing
domain, we issue separate calls to BookAPI
, PublishingCompanyAPI
, etc but this is also doing extra work.
Ideally, there would be some way of cleanly passing to BookAPI
which data should be fetched.
Introduce Change log for versions
I would just like some clarity on this. The user guide states that UUID fields should be used for model relationships, and that Services should control the relationship between models.
If a relationship spans multiple tables, say: Book
-> Author
-> Organisation
-> Address
, does this mean we would need to execute 4 SELECT queries (through different APIs) if we want a Book
's Address
, aggregating the results similar to Django's prefetch functionality?
Thanks!
Hi. Thanks for your hard work on this documentation.
Even as a sole-developer wanting to refactor a fairly small Django application (12-15k LOC), I find value at least in the concepts offered by the approach presented here. It has set me on a path to better understand DDD, so I've also watched this talk which advocates services.py
for writing to the database and selectors.py
for reading from the database.
There's one part that I don't quite understand yet: the interface part. A few things are puzzling me about it and I hope to get some clarifications (I'd be happy to contribute to the docs if conclude the answer may be helpful to others). I hope I can articulate clearly.
We are told in the docs that if one domain needs to talk to another, it must do so using an interface that it cultivates. In other words, DomainA
that wants to take some info from DomainB
must do so strictly through DomainBInterface
. But what about DRY?
Let's assume three domains: Artist
, Album
, User
. (just like in the example docs, it could be argued that Artist
and Album
should live inside the same domain)
Assume that both Artist
and User
will want to fetch the same info from our Album
, say albums within the last seven days. Does that mean that Artist
and User
must both write the same Album
interface?
Below is some pseudo-code:
# user/interfaces.py
from album.apis import AlbumAPI
def get_latest_albums():
AlbumAPI.get_latest()
# album/interfaces.py
from album.apis import AlbumAPI
def get_latest_albums():
AlbumAPI.get_latest()
Doesn't this produce non-DRY code?
Tying with what is brought-up above, it's still unclear to me why the fact we have interfaces.py
saves us headache when refactoring.
If a method in domain_b.apis
has to change, then we potential will have to refactor all DomainBInterface
s that were constructed in other domains (domain_a.interfaces.DomainBinterface
, domain_c.interfaces.DomainBinterface
, etc...).
Whereas if we consume on the DomainBAPI
directly, we only need to change the method in one place? I'm sure there's something I'm missing here.
I found dry-python/bookshelf#56 using dry python tools interesting. What do you think combining both/create separate one using best from both?
While trying to implement the structure that you have posed, I have reached a point where I wonder what would be the best course of action:
In my use case, I have two domains, one that includes a User model, and another one that includes an Outfit model. I'm also using django serializers atm as they provide some helpers to ease development.
For my use case, I want to return the User model nested inside the Outfit model, but that would cross models between domains. What would be the best approach here?
from django.contrib.auth.models import User
from .models.look import Outfit
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = ['username', 'email', 'url']
class OutfitSerializer(serializers.HyperlinkedModelSerializer):
user = UserSerializer()
class Meta:
model = Look
fields = ['shortcode', 'user', 'url']
I've seen you based this project on DDD, I'd like to ask you what is the guidance about rich models (entities)
? Shouldn't the services just orchestrate the calls? Like calling many services, external infra actions, etc?
For example:
Given I want to reserve a book
When I reserve one book
And there is at least one available book
Then The available quantity of this book should be decreased by one
And I must receive an email informing I reserved it
#book_service.py
@atomic
def reserve_book(self, id: int, user_id: int) -> None:
user = User.objects.get(id=user_id)
book = Book.objects.get(id=id)
book.reserve(user.email)
book.save()
email_client.send(user.name, user.email, "book reserved")
#models/book.py
def reserve(self, user_email: string):
if(self.available_quantity == 0):
raise Exception("book not available")
self.availabe_quantity -= 1
self.renting_history.append(user_email, datetime.today())
Hey phalt, how are u doing?
So... I'm currently following ur style guide on some projects that I'm currently working on and I've been thinking... Isn't this style guide encouraging anemic domain models?
Because you threaten the apps as our "domains" and don't write any domain classes and don't even write some mapping functions to map from domain to model and vice-versa.
You also say to people keep business logic on the service layer. But let's say that I have a Dog
domain class and I want to implement the bark
method. Following this guide I would implement this on the service layer and, lets say, do something like this:
class DogService:
@staticmethod
def bark(dog):
return 'woof'
Would it be more meaningful to be written down like this?
class Dog:
def bark(self):
return 'woof'
I did find given styleguide useful, but slightly limited in sense, that normal application would probably/might have request/response objects, that also should be mentioned and example should cover, where they could be located.
Model is not necessary one and the same in that sense.
Also since domain is being covered then in example of the shop you might encounter domains such as Product, Order, Price. It would be nice to see how those domain in example would cross paths.
Thank you.
I noticed that within the styleguide and examples, service functions / methods always return a Dict. Is returning more complext data, such as model instances, permissible?
PS, the one exception is this example, but I think this may be a mistake as a dict is ultimately returned.
Hi there, i like what you're trying to do with modernising django to use DDD
There's another DDD related book called cosmic python whose authors recommend this DDD pattern called value object
They use python 3.7 dataclass and froze it.
Was wondering if you will cover on how to use value object in Django context. (At least >= Django 2.2)
Here's the relevant line https://github.com/cosmicpython/book/blob/3008a8a4be52e8dcaf5cb7447dd7f2b26f5d9b7d/chapter_01_domain_model.asciidoc#dataclasses-are-great-for-value-objects
Sometimes in russian Telegram chats I read "Django was sexy in 2008-2012, but it looks legacy and outdated in 2019 now...". Thank you for share your opinion, I completely agree with your suggestions. This is the best way to answer them "No, Django is modern framework for modern web. And there is the Guide how to cook Django. How to make Django Great Again".
Okay...
About APIs. Check this project. https://github.com/gluk-w/django-grpc/tree/master/tests Author tried to implement gRPC server with django project. Also https://github.com/grpc/grpc-web is stable now... So in my opinion line "All data returned from APIs must be JSON serializable." is incorrect. It's depends on API types...
Hi, thanks for the grit style guide.
I would like to invite you to take a look at the dry-python project.
There are two libraries for service layer: stories and returns.
There is a library for repository layer: mappers.
And there is a library for dependency injection: dependencies.
Best regards,
Artem.
On the Domains page under domain rules the numbered list formatting is a little messed up on the generated docs. Both rules are listed as "1.":
Considering the example:
# user/internal.py
from .services import UserService
def get_user():
try:
UserService.get_user()
except SomeIntegrationError:
# Do something
# artists/interfaces.py
from user.apis import UserAPI
def get_user():
return UserAPI.get_user()
What do you guys think it's the best way to handle this situation?
I'm thinking of just raise an exception on internal.py and ignore that we should return a Json or we could return a dict with an error
key and check on interfaces
if that key exists and then handle the error there. But I don't like either option.
From looking at your guide I am struggling to find a good approach on how to use interfaces to use cross domain models.
If we take a Book and Author example, where these are separate domains. If we have an api endpoint that lists all the books in the system and as part of the data we want to return the authors name. How would we do this without having to make a request to the Author interface for each book that exists to get its details. As this could cause alot of requests to the database i.e. 1 query for the list of books + x number of queries for each books author.
This problem would become more prominent if the different domains live on separate hosts.
What is the approach you take? I have seen you have mentioned having a UserSerializer in the domain for its context (which makes sense), but how would you pass your data between the two domains?
Thanks
There is zero mention of this within the domain style guide and I'd like to add a clarification.
In my current project that has adapted the styleguide, we follow this convention:
# src.this_domain.services.py
from .models import Foo
# src.this_domain.services.py
from src.other_domain.services import BarService
Any strong opinions for / against?
IMO ideally, each domain should enforce its own permissions. This would require passing along some sort of context
with every call, and then services
enforce permissions based on the requested data and context
.
Curious to hear what others are doing as well.
I've had people asking for more clarification on what business logic should live where. "Should I put this method in the model or the service layer?"
A common anti-pattern is the anemic data model, where Models are so basic they basically set and get data. I would like to avoid that, and I didn't plan for DADs to promote that. My main goal in the separation was drawing a line between presentation/service-level functionality and data storage functionality
The current DAD (Django Api Domain) version rules that Models
should not have any complex business logic, but they can do simple thing like provide computed values, like so:
class Book(models.Model):
author_name = models.CharField(max_length=256)
book_name = models.CharField(max_length=256)
@property
def book_and_author_name(self):
# A simple property method
return f'{self.book_name} - {self.author_name}'
The Services
should handle more complex business logic across multiple Models, or multiple domains.
I think we need to add some more examples of what type of logic should live where, and I want to allow suggestions from the community.
Here is my current feeling on the matter:
Models should handle the following business logic:
class Book(models.Model):
@classmethod
def get_available(cls) -> List[Book]:
return Book.objects.filter(on_loan=False, for_sale=True)
>>> available_books = Book.get_available()
class Book(models.Model):
def update_days_on_loan(self) -> None:
self.days_on_loan = self._days_on_loan + 1
self.save()
>>> Book.update_days_on_loan()
Services should be putting together service-level or presentation logic that might spread across the whole domain or many domains. For example:
class LoanService:
@staticmethod
def rent_book(*, book: Book, user: User) -> None:
book.set_on_loan(user)
book.reset_days_on_loan_count()
user.update_books_rented()
class BookService:
@staticmethod
def get_book(id: uuid.UUID) -> BookTuple:
book = BookModel.objecets.get(id=id)
return BookTuple(
author=book.author
)
class BookService
@staticmethod
def create_book(
author_first_name: str,
author_surname: str,
name: str
) -> None:
Book.objects.create(
author=f'{author_first_name} {author_surname}',
book_name=name,
)
Is this level of clarification sufficient?
Should the bar be moved more in one direction or the other?
Feedback please.
Hey ๐๐ฝ
Thanks for building this! But now that this project has been marked as unmaintained, what's the next best resource for building Domain Driven API's in Django?
Sorry I change your type to question.
in boundedcontexts https://martinfowler.com/bliki/BoundedContext.html using the same example as Fowler
You can have Customer and Product in more than 1 boundedcontexts. Since in your styleguide, your first two goals at https://phalt.github.io/django-api-domains/ are:
- Treat Django's apps like software domains.
- Extend Django's apps implementation to support strong bounded context patterns between domains.
Using Fowler's example, where would you create the Django model for Customer or Product, if the Support is an Django app and sales is another Django app?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.