dddpy

Python DDD Example and Techniques

MIT License

Stars
557
Committers
4

Python DDD & Onion-Architecture Example and Techniques

English | 日本語

NOTE: このリポジトリは、「PythonのWebアプリケーションでDDDアーキテクチャを実装する方法」を説明するための例です。参考として使用する場合は、本番環境にデプロイする前に認証とセキュリティの実装を追加してください!

技術スタック

コードアーキテクチャ

ディレクトリ構造はオニオンアーキテクチャに基づいています:

├── main.py
├── dddpy
│   ├── domain
│   │   └── book
│   ├── infrastructure
│   │   └── sqlite
│   │       ├── book
│   │       └── database.py
│   ├── presentation
│   │   └── schema
│   │       └── book
│   └── usecase
│       └── book
└── tests

エンティティ

Pythonでエンティティを表現するには、オブジェクトの同一性を確保するために __eq__() メソッドを使用します。

class Book:
    def __init__(self, id: str, title: str):
        self.id: str = id
        self.title: str = title

    def __eq__(self, o: object) -> bool:
        if isinstance(o, Book):
            return self.id == o.id
        return False

値オブジェクト

値オブジェクトを表現するには、@dataclass デコレーターを eq=True および frozen=True と共に使用します。

以下のコードは、値オブジェクトとしての本のISBNコードを実装しています。

@dataclass(init=False, eq=True, frozen=True)
class Isbn:
    value: str

    def __init__(self, value: str):
        if not validate_isbn(value):
            raise ValueError("Value should be a valid ISBN format.")
        object.__setattr__(self, "value", value)

DTO (データ転送オブジェクト)

DTO (データ転送オブジェクト)は、ドメインオブジェクトをインフラ層から分離するための良い実践です。

最小のMVCアーキテクチャでは、モデルはORM(オブジェクト関係マッピング)によって提供されるベースクラスを継承することがよくあります。しかし、その場合、ドメイン層が外部層に依存することになります。

この問題を解決するために、2つのルールを設定できます:

  1. ドメイン層のクラス(エンティティや値オブジェクトなど)は、SQLAlchemyのBaseクラスを継承しない。
  2. データ転送オブジェクトは、ORMクラスを継承する。

CQRS

CQRS(コマンドとクエリの責任分離)パターンは、読み取り操作と書き込み操作を分離するために有用です。

  1. ユースケース層にリードモデルとライトモデルを定義します:

  2. クエリ:

  3. コマンド:

  4. ユースケース:

    • ユースケースはリポジトリインターフェースまたはクエリサービスインターフェースに依存します:
    • ユースケースは、リードモデルまたはライトモデルのインスタンスをメインルーチンに返します。

UoW (ユニット・オブ・ワーク)

ドメイン層の分離に成功しても、トランザクション管理の方法などの問題が残ります。

UoW(ユニット・オブ・ワーク)パターンが解決策となりえます。

まず、ユースケース層に基づいたUoWパターンのインターフェースを定義します。begin(), commit(), rollback()メソッドはトランザクション管理に関連します。

class BookCommandUseCaseUnitOfWork(ABC):
    book_repository: BookRepository

    @abstractmethod
    def begin(self):
        raise NotImplementedError

    @abstractmethod
    def commit(self):
        raise NotImplementedError

    @abstractmethod
    def rollback(self):
        raise NotImplementedError

次に、上記のインターフェースを使用してインフラ層にクラスを実装します。

class BookCommandUseCaseUnitOfWorkImpl(BookCommandUseCaseUnitOfWork):
    def __init__(
        self,
        session: Session,
        book_repository: BookRepository,
    ):
        self.session: Session = session
        self.book_repository: BookRepository = book_repository

    def begin(self):
        self.session.begin()

    def commit(self):
        self.session.commit()

    def rollback(self):
        self.session.rollback()

sessionプロパティはSQLAlchemyのセッションです。

動作方法

  1. このリポジトリをクローンしてVSCodeで開きます。
  2. リモートコンテナを実行します。
  3. Dockerコンテナのターミナルで make dev を実行します。
  4. APIドキュメントにアクセスします: http://127.0.0.1:8000/docs

RESTful APIのサンプルリクエスト

  • 新しい本を作成する:
curl --location --request POST 'localhost:8000/books' \
--header 'Content-Type: application/json' \
--data-raw '{
    "isbn": "978-0321125217",
    "title": "Domain-Driven Design: Tackling Complexity in the Heart of Software",
    "page": 560
}'
  • POSTリクエストのレスポンス:
{
    "id": "HH9uqNdYbjScdiLgaTApcS",
    "isbn": "978-0321125217",
    "title": "Domain-Driven Design: Tackling Complexity in the Heart of Software",
    "page": 560,
    "read_page": 0,
    "created_at": 1614007224642,
    "updated_at": 1614007224642
}
  • 本を取得する:
curl --location --request GET 'localhost:8000/books'
  • GETリクエストのレスポンス:
[
    {
        "id": "e74R3Prx8SfcY8KJFkGVf3",
        "
Badges
Extracted from project README
A workflow to run test
Related Projects