In this article we dive a bit into Django’s way of data modelling and that of DDD’s notion of a repository and compare these two.
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 names = MyManager() # model methods def hey_a_model_method(self): return self.name + "!"
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 MyQuerySet(self.model, self._db) return super().get_queryset().values('name')
Then one can call MyModel.names.all() to retrieve names. (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 (https://docs.djangoproject.com/en/3.0/ref/models/querysets/#all) . 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?
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. This two way part is contrary to the DDD philosophy to have one repository that handles access to the 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 from a serializer gets redirected from the save method of the model to a save method of the manager or repository.
- deep level: change the model to not save/update/delete
- low level: adapt standard serializer behaviour to use the manager methods
- high level: adapt views using hooks around standard serializer behaviour
I first tried the lower level way to learn the internal workings of DRF. This basically comes down into patching the DRF framework, which is a bad idea. Serializers, especially model serializers, are inherently coupled with models or QuerySets. Changing the flow comes down to forcing the framework to do something it does not want to do. Usual problems like maintenance issues will start playing a role.
One of the main constructs of creating objects in an API-oriented setting (DRF) is through a serializer which uses the model’s save method. By using the following mixin in a serializer, the
Another approach is to use perform_* hooks that DRF supplies. Here instead we adjusted the serializer on a deeper level.
why in repo acl, because serializers views know about model/serializer and requires less changes than hanging in services for acl. Downside: repo’s might do too much (call other repo’s).