English | 日本語
NOTE: このリポジトリは、「PythonのWebアプリケーションでDDDアーキテクチャを実装する方法」を説明するための例です。参考として使用する場合は、本番環境にデプロイする前に認証とセキュリティの実装を追加してください!
- ブログ投稿: Python 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 (データ転送オブジェクト)は、ドメインオブジェクトをインフラ層から分離するための良い実践です。
最小のMVCアーキテクチャでは、モデルはORM(オブジェクト関係マッピング)によって提供されるベースクラスを継承することがよくあります。しかし、その場合、ドメイン層が外部層に依存することになります。
この問題を解決するために、2つのルールを設定できます:
- ドメイン層のクラス(エンティティや値オブジェクトなど)は、SQLAlchemyのBaseクラスを継承しない。
- データ転送オブジェクトは、ORMクラスを継承する。
CQRS(コマンドとクエリの責任分離)パターンは、読み取り操作と書き込み操作を分離するために有用です。
-
ユースケース層にリードモデルとライトモデルを定義します:
-
クエリ:
- ユースケース層にクエリサービスインターフェースを定義します:
- インフラ層にクエリサービスの実装を定義します:
-
コマンド:
- ドメイン層にリポジトリインターフェースを定義します:
- インフラ層にリポジトリの実装を定義します:
-
ユースケース:
- ユースケースはリポジトリインターフェースまたはクエリサービスインターフェースに依存します:
- ユースケースは、リードモデルまたはライトモデルのインスタンスをメインルーチンに返します。
ドメイン層の分離に成功しても、トランザクション管理の方法などの問題が残ります。
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のセッションです。
- このリポジトリをクローンしてVSCodeで開きます。
- リモートコンテナを実行します。
- Dockerコンテナのターミナルで
make dev
を実行します。 - APIドキュメントにアクセスします: http://127.0.0.1:8000/docs
- 新しい本を作成する:
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",
"