Clean Architecture in Django
I have posted this at 21 Buttons engineering blog, the place where I’ve been working last year with an amazing experience :). I mirror here that post, I hope you will like it! Feedback is more than welcome.
Update: I’m working on a little project using this architecture and I’ve published the code on Github: abidria-api
This post will try to explain our approach to apply Clean Architecture on a Django Restful API. It is useful to be familiarized with Django framework as well as with Uncle Bob’s Clean Architecture before keep reading.
To do that, we’ll use a minimalist example of an app with a unique GET endpoint to retrieve a product. Architecture explanation is structured by the same layers as the diagram above. We are going to start with the innermost one:
Entities Layer (Innermost Domain)
Here we define entities.py
:
This is a simplistic example of an entity. A real entity should be richer. Allocate here business logic and high-level rules that are related to this entity (eg: invariant validations).
Use Cases Layer (Outermost Domain)
We call them interactors.py
and they contain the business logic of each use case.
Place here all the application logic.
We use command pattern for their implementation
because it helps with task enqueueing,
rollback when an error occurs
and also separates dependencies and parameters
(really useful for readability,
testing and dependency injection):
Again, this example is too simple. In a user registration, for example, we should validate user attributes, check if username is available against the repo, create new user entity, store it and call mailer service to ask for a confirmation.
Interface Adapters Layer
Here we have pieces that are decoupled from framework, but are conscious of the environment (API Restful, database storage, caching…).
First of all we have views.py
.
They follow Django’s view structure
but are completely decoupled from it
(we’ll see how on the next section):
And here the serializers.py
(just to show that view returns a python dict as body):
ProductView
just gets an interactor from a factory
(we’ll see later what that Factory
is…),
parses the input params
(could also make some syntactic/format validations)
and formats the output with serializers
(also handling and formatting exceptions).
On the other side of this layer
we have our frontal repositories.py
.
They don’t access to storage directly
(these parts are explained on the next layer)
but are in charge of selecting the source storage,
caching, indexing, etc:
Framework & Drivers Layer
Composed by Django and third party libraries, this layer is also where we place our code related to that parts to abstract their implementations (glue code).
In our example we have two parts of that kind: database and web.
For the first part we have created a repository that it’s completely tied to Django ORM:
As you can see, both object and exception that this class returns are defined by us, thus we hide all the orm details.
And for the second one we have created a view wrapper to hide format details and decouple our views from the framework:
The goals of this wrapper are two: convert all the arguments of the request to pure python objects and format the output response (so the views can also return pure python objects).
Also, self.view_factory.create()
creates the view
with all its dependencies (we explain it in detail below).
In these layer we also have models.py
, admin.py
, urls.py
,
settings.py
, migrations and other Django related code
(we will not detail it here because it has no peculiarities).
These layer is totally coupled to Django (or other libraries). Although, it is really powerful and essential for our app we must try to keep it as lean as we can!
Dependency Injection
But… how do we join all these pieces? Dependency injection to the rescue!
As we have seen before, we create the view with factories.py
,
who are in charge of solving dependencies:
Factories are in charge of creating and solving dependencies recursively, giving the responsability of each element to its own factory resolver.
And finally, the place where all begins: urls.py
This post not aims to destroy Django architecture and components, nor misuse it. It’s just an alternative that can be really useful in some kind of contexts.