Search code examples
djangodjango-rest-frameworkdomain-driven-designclean-architecture

Where should I place the logic for one entity altering the state of another entity within Django REST Framework?


Imagine a simple app where user's can post some requests (for example - they need some help, food) and other users can through chat give some reply to this request. Here are my simplified models:

class UserRequest(models.Model):
    ACTIVE = 'active'
    INACTIVE = 'inactive'
    STATUS_CHOICES = [
        (ACTIVE, 'Active'),
        (INACTIVE, 'Inactive'),
    ]

    user = models.ForeignKey(User, on_delete=models.CASCADE)
    request_text = models.TextField()
    status = models.CharField(max_length=8, choices=STATUS_CHOICES, default=INACTIVE)


class RequestChat(models.Model):
    user_request = models.ForeignKey(UserRequest, on_delete=models.CASCADE)


class ChatMessage(models.Model):
    text = models.TextField()
    chat = models.ForeignKey(RequestChat, on_delete=models.CASCADE)

I need to implement this type of functionality:

When at least one chat message is created (written to chat) - the associated request status should be changed from INACTIVE to ACTIVE.

Should this logic be placed in the save() method of the ChatMessage model or should it be handled using a Django signal? Or perhaps there might be another way of doing this? Additionally, can you recommend any resources such as books, articles, or videos that provide guidance on this topic?


Solution

  • As of everything with software architecture: it depends.

    The topic that you are asking about is Aggregate design. There is a lot of material out there. I think that Implementing Domain Driven Design by Vernon has a good example about Aggregate design. I think i also found this article useful, which is a 3-part series about Aggregate design.

    Now, it shouldn't be in a save method. A save method is a persistence concern and shouldn't know the business rules about when a UserRequest becomes active or not.

    There should be some Domain object that knows when a UserRequest becomes ACTIVE. This domain object could be the UserRequest itself, meaning that UserRequest is the root Aggregate and ChatMessage is an Entity inside that Aggregate. When retrieving a UserRequest all ChatMessages are retrieved. Adding a new chat message is done thru the root aggregate (userRequest.addChatMessage). When the chat message is pushed, you can change the status to active. This has the downside that loading all ChatMessages may not be performant and also it creates a lot of contingency depending on what operations the users are allowed to do (e.g.: since all the chat messages are modified through the request, in order to keep everything consistent only one modification can be done at any time).

    The other alternative is to make them different Aggregates and use eventual consistency. When a ChatMessage is saved, it emits an event that the UserRequest module can listen and change the status to active. There should be a Domain service on the UserRequest module that knows the rules about when the request becomes active.

    Both approaches are fine, it will depend on your system and business characteristics.