🌊
django rest frameworkでclean architecture(風)を実装してみた
はじめに
- DRFで0→1をやる機会があったのでclean architecture(風)に実装してみました
- あくまで風なのであしからず。。。
- テストについては未記載です!
-
awesome_application
にtests
フォルダが増えるのかなーと思います
-
- DjangoのModel定義はよしなに配置してください🙏
ディレクトリ構成
.venv
|
+-- src
+-- data-models
+-- models.py
config
|
+-- asgi.py
|
+-- settings.py
|
+-- wsgi.py
application
|
+-- domains
|
+-- awesome_domain
|
+-- application_services.py
|
+-- objects.py
|
+-- repositories.py
|
+-- awesome_application
|
+-- urls.py
|
+-- views.py
|
+-- usecases.py
|
+-- repositories.py
|
+-- types.py
|
+-- serializers.py
|
urls.py
各ファイルについて
awesome_domain
配下
application/domains/awesome_domain/application_services.py
- リポジトリを実行する
from typing import TypeVar, Generic
from .repositories import I〇〇Reader
from .objects import D〇〇
T = TypeVar('T')
class 〇〇Service(Generic[T]):
def __init__(self,
reader: I〇〇Reader,
data: T):
self._reader = reader
self._data = data
def execute(self) -> list[D〇〇]:
domain_list = self._reader.read(data=self._data)
return domain_list
application/domains/awesome_domain/objects.py
- ドメインオブジェクトの定義
import datetime
class D〇〇():
def __init__(
self,
id: int,
created_at: datetime.date,
updated_at: datetime.date,
):
self._id = id
self._created_at = created_at
self._updated_at = updated_at
@property
def id(self):
return self._id
@property
def created_at(self):
return self._created_at
@property
def updated_at(self):
return self._updated_at
application/domains/awesome_domain/repositories.py
- リポジトリのインターフェイス
from abc import abstractmethod
from typing import TypeVar, Generic
from .objects import D〇〇
T = TypeVar('T')
class I〇〇Reader(Generic[T]):
@abstractmethod
def read(self, data: T) -> list[D〇〇]:
raise NotImplementedError()
awesome_application
配下
application/awesome_application/views.py
- RequestとResponseに対して責任をもつ
from rest_framework import status
from rest_framework.response import Response
from rest_framework import views
from .usecases import 〇〇Usecase
class 〇〇View(views.APIView):
def get(self, request, *args, **kwargs):
## 本当はここでRequestのバリデーションも行う
data = 〇〇Usecase(
).feed(
args=request.query_params
)
return Response(data=data, status=status.HTTP_200_OK)
application/awesome_application/usecases.py
- serviceを実行する
- ドメインオブジェクト → レスポンスオブジェクトに変換する
from rest_framework import viewsets
from application.domains.awesome_domain.application_services import 〇〇Service
from .repositories import 〇〇Reader
from .serializers import 〇〇Serializer
from .types import RequestType, ResponseType
class 〇〇Usecase(viewsets.ModelViewSet):
def feed(self, args: RequestType) -> ResponseType:
domain_list = 〇〇Service(
reader=〇〇Reader(),
data=args
).execute()
return list(〇〇Serializer(domain_list, many=True).data)
application/awesome_application/repositories.py
- DBアクセスの実態
- DBデータではなく、ドメインオブジェクトに変換する
from data_models.models import 〇〇Model
from application.domains.awesome_domain.repositories import 〇〇Reader
from application.domains.awesome_domain.objects import D〇〇
from .types import RequestType
class 〇〇Reader(I〇〇Reader):
def read(self, data: RequestType) -> list[D〇〇]:
object_list = 〇〇Model.objects.filter(
id__in=data['id_list']
).all()
result: list[D〇〇] = []
for object in object_list:
result.append(D〇〇(
id=object.pk,
created_at=object.created_at.date(),
updated_at=object.updated_at.date()
))
return result
application/awesome_application/serializers.py
- ドメインオブジェクトをレスポンスオブジェクトに変換する用のシリアライザー
from rest_framework import serializers
class 〇〇ResponseSerializer(serializers.Serializer):
id = serializers.IntegerField()
created_at = serializers.DateField()
updated_at = serializers.DateField()
application/awesome_application/types.py
- dictの型定義
-
dict[str, Any]
ダメゼッタイ
from typing import TypedDict
class RequestType(TypedDict):
id_list: list[int]
class ObjectType(TypedDict):
id: int
created_at: str
updated_at: str
class ResponseType(TypedDict):
list: list[ObjectType]
まとめ
- dictを型付けだけでも安全性高まりますね
- 偉い人が100回言っていますが、DB定義とドメインオブジェクトは必ずしもイコールではないのでそれをどう吸収するかが難しいなーと思いました(小並感)
- 自分も思考錯誤の途中なので意見頂けると嬉しいです!
Discussion