Domain Driven Design Repositories in Django

Django’s way of data modelling is quite different than that of using repositories commonly used in Domain Driven Design (DDD)’s. An example implementation can be found on my github here.

Django db.models

The design of data modelling in Django can be interpreted as an application of the ‘Active Record Pattern’. A model in Django is a class with field attributes, model methods, a reference to a manager (see below) and serves partly to interact with the underlying database.

from django.db import models

class MyModel(models.Model):
   # define field attributes
   name = models.CharField(max_length=256)

   # custom manager
   objects = MyManager()

   # model methods
   def hey_a_model_method(self):
      return + "!"

Models can be saved, deleted and updated. Another way to communicate with the underlying database is through a manager. Managers are coordinators for collections of entities, as the Django documentation formulates it, in a table-wide manner.

from django.db import models

class MyManager(models.Manager):
   def get_queryset(self):
      return super().get_queryset().values('name')

Then one can call MyModel.objects.all() to retrieve certain data. (Question: How does the manager know how to look up data? Answer: add_class of ModelBase calls contribute_to_class for all its attributes, including a manager.)

Managers give access to entities of a model and it is sort of the standard way to retrieve entities or a specific entity (unless you create one directly). The underlying data structure of a manager is a QuerySet ( . Also QuerySets can be subclassed and extended with domain specific, the custom QuerySet can be used by overriding the “get_queryset” method (commented out in the above code snippet). A manager possesses all methods of the underlying QuerySet due to its class method “_get_queryset_methods”.

Now the question remains, with Model, Manager and QuerySet at our disposal, how do this data quering integrate with Django and what is then a good strategy to mix in DDD?

Django views

The data querying of the previous section is normally initiated by views. For plain vanilla Django, subclasses of GenericView and the likes can be used that specify a model, whence implicitly define a QuerySet to be used for the view, or alternatively, one can set a QuerySet directly.

from django.views.generic.edit import CreateView

class MyView(CreateView):
   model = MyModel
   # ..

For views derived from the Django Rest Framework (DRF), a similar approach is used.

What is a repository?

Now that we briefly summarised data handling in Django, what does DDD say about a repository? A repository is an object that provides a ‘globally accessible’ interface for adding and removing of objects, for instance, aggregates. They persist objects. How many repositories are there? It depends what you need, you can have one for each aggregate for example. Repositories should contain no business logic.

Redirecting Django to a repository

We saw that in Django, the entry points to storing and retrieving data are supplied by both the model and the manager. Both the model and manager can create objects: the model a single object (as well as the manager which delegates this in the background to the model) and the manager a bulk of objects. These two ways to access data is contrary to the DDD philosophy to have one repository that handles access to a database.

By using mixins, or subclassing plus overriding methods, we can redirect the flow to save data to always use a save method in the manager. A manager is considered to be a ‘repository’ once all creation, updating and deleting takes place through the manager.

An example redirection

This section illustrates how saving objects is redirected from the save method of the model to a save method of the manager or repository. There are multiple possibilities to implement redirection:

  1. Change the model to not save/update/delete
  2. Adapt behaviour of serializers to use the repository
  3. Adapt views using hooks around standard serializer behaviour
  4. Use Django signals with pre/post save hooks etc

Here I opted for the first option. The downside of the second option is that this would only work for objects changed through a serializer. The third option can be done by example by using the perform_* hooks that DRF supplies. The fourth option is not really a redirect by a signal and no full control is obtained for saving an object (signals are also asynchronous).

First change modify the model and manager to inherit from new base classes:

from django.db import models
from ddd import DDDModel, DDDManager

class MyManager(DDDManager):

class MyModel(DDDModel):
   # define field attributes
   name = models.CharField(max_length=256)

   # custom manager
   names = MyManager()
   repository = MyRepository

How the calls of the model and manager are intercepted just by inheritance can be read in more detail from here . Now the repository will look like the following:

from utils.singleton import Singleton
from ddd import repository, DDDRepository

class MyRepository(DDDRepository):
    def __init__(self, model):
        self.model = model

    def bulk_create(self, objs):
        # some bulk creation code

    def create(self, obj):
        # some preflight code here
        # some other code there

    def delete(self, obj):
        # some other code

    def update(self, obj):
        # some code

The upshot is that this class, a singleton, is a central one place where database access can be controlled, and for example additional handlers or ACL logic can be hooked in.