Giter Site home page Giter Site logo

phalt / django-api-domains Goto Github PK

View Code? Open in Web Editor NEW
688.0 21.0 61.0 616 KB

A pragmatic styleguide for Django API Projects

Home Page: https://phalt.github.io/django-api-domains/

License: MIT License

Python 100.00%
django api styleguide style guide rest graphql django-rest-framework graphene-django domains

django-api-domains's Introduction

⚠️ Currently stable and unlikely to change much, provide feedback here ⚠️

Check out my new project for OpenAPI Clients: Clientele

logo.png

Django API Domains

Style guides for the API age

Version Author(s) Date
1.2.1 Paul Hallett [email protected] 25-09-2019
1.2 Paul Hallett [email protected] 10-06-2019
1.1 Paul Hallett [email protected] 09-04-2019
1.0 Paul Hallett [email protected] 01-02-2019

Introduction

This styleguide combines domain-driven design principles and Django's apps pattern to provide a pragmatic guide for developing scalable API services with the Django web framework.

This styleguide tries to tackle two big problems:

  1. Design philosophies and design patterns work in "ideal" situations, and most real life problems do not represent this ideal world. Therefore we need to develop a flexible pattern that can adjust to support different situations.
  2. The original design and documentation of Django is geared heavily towards server-side-rendered-view applications, yet most modern Django applications are built to serve APIs for a separate frontend application. Therefore, Django's patterns are outdated for today's trends.

In order to overcome these problems, this styleguide tries to achieve the following five goals:

  1. Treat Django's apps more like software domains.
  2. Extend Django's apps implementation to support strong bounded context patterns between domains.
  3. Enable separation of domains to happen when it makes sense for increased development velocity, not just for business value.
  4. Design a styleguide that reduces the effort involved in extracting the code for large domains into separate application servers.
  5. Make sure the styleguide compliments API-based applications.

Read the styleguide

The styleguide is now published as a readable documentation site. You can view it at https://phalt.github.io/django-api-domains/ or view the docs folder directly.

django-api-domains's People

Contributors

espadav8 avatar phalt avatar simkimsia avatar voyc-jean avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

django-api-domains's Issues

[FEEDBACK] Clarify with DRF

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

  • models.py
  • apis.py
  • interfaces.py
  • services.py

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?

[QUESTION] Model relationships between domains.

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!

CHANGELOG

Introduce Change log for versions

[FEEDBACK] What about rich models?

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())

[FEEDBACK] Can cover your thoughts on value object?

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

[QUESTION] How to use serializers from other domains for nested relationships

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']

Relative/absolute imports

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:

  • relative imports within a domain:
# src.this_domain.services.py
from .models import Foo
  • absolute imports for other domains in the same project:
# src.this_domain.services.py
from src.other_domain.services import BarService

Any strong opinions for / against?

[FEEDBACK] Example needs more than one domain

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.

[QUESTION] Models required across multiple domains

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

#12

What's next?

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?

[Question] Thoughts on where/how permissions are enforced?

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.

[QUESTION] What's the best way to handle errors between apps?

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.

[FEEDBACK] Change "All data returned from APIs must be JSON serializable."

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...

[FEEDBACK]Isn't this styleguide encouraging anemic domain models?

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'

Clarification on Model / Service business logic ownership

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

Models should handle the following business logic:

  • Simple computed values from the Model's attributes
  • Common filter methods:
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()
  • Wrapping up common update or save actions on the model:
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

Services should be putting together service-level or presentation logic that might spread across the whole domain or many domains. For example:

  • Combining separate actions into a single action:
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()
  • Translate a Model's model object into serialisable objects:
class BookService:
    @staticmethod
    def get_book(id: uuid.UUID) -> BookTuple:
        book = BookModel.objecets.get(id=id)
        return BookTuple(
            author=book.author
        )
  • Translating input data into data we want to store on a model:
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.

[QUESTION] How do you handle models that are used in more than 1 domain/app?

Sorry I change your type to question.

in boundedcontexts https://martinfowler.com/bliki/BoundedContext.html using the same example as Fowler

diagram

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:

  1. Treat Django's apps like software domains.
  2. 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?

[FEEDBACK] Thoughts on foreign key fetching?

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.

[FEEDBACK] Further clarify the role of interfaces.py

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.

Interfaces and DRY

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?

Interfaces and refactoring

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 DomainBInterfaces 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.

[QUESTION] Can you provide an example using DRF?

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?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.