🌊

django rest frameworkでclean architecture(風)を実装してみた

2023/03/17に公開

はじめに

  • DRFで0→1をやる機会があったのでclean architecture(風)に実装してみました
    • あくまで風なのであしからず。。。
  • テストについては未記載です!
    • awesome_applicationtestsフォルダが増えるのかなーと思います
  • 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