With the recent rise of AI, I think that the choice to build with Python for new system development has increased. When it comes to writing new web services in Python, I think the main options are to provide a framework such as React or Vue.js for the front end and a REST API with Django or Flask for the back end. With a simple configuration, the Django REST Framework makes it easy and simple to create an API server, but when it gets a little complicated, it quickly becomes chaotic if you don't think about the architecture properly. ~~ In the case I entered, this is already chaos and chaos. .. .. ~~ As far as I've looked, there's no article on the combination of Django REST Framework + Clean Architecture, so I'll consider it in this article as well as my own study and summarizing my thoughts. ~~ It's rather painful, so I'd like to introduce it with the contents summarized here. ~~ Please comment if you have a good design.
This time, we are considering excluding Project and Application (described later). Also, routing, ViewSet, ModelManager, etc. are not considered.
Since it's just pictures and sentences, I tried to implement a sample. Sample code can be found here [https://github.com/ryoutoku/django_rest_clean_architecture). Based on the Django tutorial model, I'll just implement Question POST. (However, I couldn't think about the business logic so much, so the implementation is just making POST processing uselessly redundant.)
Django itself is a framework based on the idea that there are multiple Applications in one Project, and MVT (Model
, View
, Template
) in it.
In the Django REST Framework, there is no Template
and more Serializer
.
Below is a diagram of the architecture and roles of the Django REST Framework.
For CRUD processing for one model
(1 table of Database), basically view
-- serializer
-- model
is 1 to 1 to 1, and it can be made simply.
For business-level Web services, when REST API is executed, logic processing is performed according to some business knowledge, rules, and judgments, so it is rare that only 1table CRUD processing is performed. However, if you start implementing it without deep consideration of the architecture, the architecture provided by the Django REST Framework will lead to chaos. .. ..
For me, the following two points are big problems that tend to be chaotic. Isn't it basically a framework that easily breaks the single-responsibility principle? I feel that.
views
tends to be complicated (prone to Fat Contoller)This is a problem called Fat Controller, in which logic is written in views
.
It is a problem that tends to occur when a beginner or someone who is not interested in architecture makes it.
In Django REST Framework, if it is simple, there is almost no implementation of CRUD processing as follows.
models.py
from django.db import models
class Question(models.Model):
#Offer ORM
question_text = models.CharField(max_length=10)
pub_date = models.DateTimeField('date published')
serialzers.py
from .models import Question
from rest_framework import serializers
class QuestionSerializer(serializers.ModelSerializer):
# data <->Provides model conversion
class Meta:
model = Question
fields = '__all__'
# data <->model Define the model and field to be converted
views.py
from rest_framework import viewsets
from .models import Question
from .serializers import QuestionSerializer
class QuestionViewSet(viewsets.ModelViewSet):
#REST API I/Offer O
queryset = Question.objects.all()
serializer_class = QuestionSerializer
#Because the inheritance source ModelViewSet defines a function that is an API for CRUD processing.
#No need to create a method
CRUD processing is provided and hidden on the Django REST Framework side.
Therefore, for example, if you have to incorporate some logic when saving data (POST), you can easily write it in views-> "Add it to views
(Controller) for the time being ". Isn't it often the case that you write various things in .py?
If you don't write a new class instead of writing it solidly, it will cause you to become a Fat Controller.
serializers
and models
Since Serializer provides validation, you have to worry about where to implement validation (not at the level of business knowledge) such as the upper and lower limits of values and the format of strings.
If you put the logic in such a way that it is implemented in serializers
because validation is provided, it will put seliarizers
on multiple responsibilities.
What is the validation of serializers
when implemented in models
? It will become.
Also, if you are developing with a large number of people, the logic will be scattered unless you implement it with a unified idea. .. ..
If you don't think about what to implement and where, it can cause chaos.
I feel that the fact that Serializer provides too many functions (too good) is the reason why it tends to be chaotic. Specifically, I wonder if the cause is as follows.
-serializer.save ()
saves the corresponding model
record (depending on the inheriting Serializer class)
--Because it can be saved using either model
or serializer
--Serializer has validation function and you can customize validation
-> I want to put business knowledge in serializer.validation ()
(because it's called validation)
I googled "django rest framework logic" and it came out first [this page](https://medium.com/@amarbasic4/django-rest-framework-where-to-put-business-logic-82e71c339022 In), it is written that the logic is collected in serializers
.
However, there is a problem with the test.
This person is written as writing in views
, but I feel that business logic is likely to be scattered. .. .. ..
Below is a diagram of the correspondence between my interpretation of Django REST Framework and Clean Architecture. The left is the original composition, the right is the [usual figure] of Clean Architecture (https://blog.cleancoder.com/uncle-bob/images/2012-08-13-the-clean-architecture/CleanArchitecture.jpg) I tried to map it. The left is the reference and the right is the mapping.
Looking at this, there is no corresponding xxx Business Rule. .. ..
Based on the above, we designed it as follows. Or rather, I probably referred to here.
Roughly, I designed it with the following policy.
Obviously, domain logic is not written in views
, serializers
, and models
.
Conversely, if it doesn't include domain logic, it's supposed to be used according to the Django REST Framework.
The behavior when the REST API is hit is described below (some are not implemented).
--When executing Query (GET)
--Use the functionality provided by the Django REST Framework
--If you are pulling data from a resource other than Django, define it on the serializer
side with SerializerMethodField
etc.
--When executing Command: No business logic ver
--In validate ()
of serializer
, validate the value using ʻAggregate and
DomainObject`
--When executing Command (POST / PUT / PATCH / DELETE): With business logic ver
views
passes dict
of data from serializer
to ʻapplication_services` as an argument creates ʻAggregate
based on the argument dict
and processes it.
--Use ʻIXXXReader to generate ʻAggregate
from DB data
--When saving the data, based on the argument dict
, generate ʻAggregate, pass it to ʻIXXXWriter
and save it.
--Returns a response-aware dict
to views
serializer
for response based on dict
obtained from ʻapplication_services`Below is a summary of what I thought while researching and designing.
--I think that the I / F on the Interface Adapters side and the XXX Bussiness Rules side was simple and the logic could be divided.
--But there are still more files and modules
――It may be unpopular with people who care about performance
--The habit of data
and validated_data
(especially DateTimeField
) of serializer
is strong.
--I'm addicted to returning str
for data
and datetime
for validated_data
.
--I want to make it a little easier to implement, such as DomainObject
, or make it simpler.
--It is better to use NamedTuple
or to dict the property to make it easier to make and use.
--Depending on the contents of Query, Command, and Command, there are implementation patterns only for REST Framework and implementation patterns that consider Clean Architecture.
--Patterns tend to be chaotic, so common recognition is required within the project
--In Python, defining Interface is not very good.
If you want to create an API server (or rather design a URI), I thought it would be better to use GraphQL and REST API properly with the following policy.
--Provided by GraphQL for Query --From a UI-based perspective, it is difficult for REST APIs to provide only the necessary data, and if you try to provide REST APIs for each UI, the number of APIs will increase. --Since Query does not require validation, GraphQL, which can be provided flexibly, may be more suitable. --Provided by REST API for Command --Since it seems that business logic is often involved in each command, it is better to explicitly determine the API and its parameters instead of providing flexible I / F.
I'm sure at the current site
"It's simpler and easier to understand if you stuff the logic in views
(1 method 200 lines or more logic implemented) "
"If you make a class in vain, the performance will be ... (It seems that performance is not measured)"
Ah ... I don't understand ...
https://medium.com/@amarbasic4/django-rest-framework-where-to-put-business-logic-82e71c339022 https://isseisuzuki.com/it/django-rest-framework-concept/ https://qiita.com/MinoDriven/items/3c7db287e2c66f36589a https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html https://blog.tai2.net/the_clean_architecture.html
Recommended Posts