{"id":13595374,"url":"https://github.com/iktakahiro/dddpy","last_synced_at":"2025-05-15T14:09:09.903Z","repository":{"id":37950229,"uuid":"340649318","full_name":"iktakahiro/dddpy","owner":"iktakahiro","description":"Python DDD \u0026 Onion Architecture Example and Techniques","archived":false,"fork":false,"pushed_at":"2025-04-26T01:32:18.000Z","size":582,"stargazers_count":612,"open_issues_count":0,"forks_count":69,"subscribers_count":8,"default_branch":"main","last_synced_at":"2025-04-26T02:52:00.676Z","etag":null,"topics":["ddd-architecture","ddd-example","fastapi","onion-architecture","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iktakahiro.png","metadata":{"files":{"readme":"README.ja_JP.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-02-20T12:28:27.000Z","updated_at":"2025-04-26T01:32:21.000Z","dependencies_parsed_at":"2024-08-01T16:39:40.433Z","dependency_job_id":"307f47ab-a39d-48b8-b8a2-f9f91a7bb7a0","html_url":"https://github.com/iktakahiro/dddpy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iktakahiro%2Fdddpy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iktakahiro%2Fdddpy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iktakahiro%2Fdddpy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iktakahiro%2Fdddpy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iktakahiro","download_url":"https://codeload.github.com/iktakahiro/dddpy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254355332,"owners_count":22057354,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ddd-architecture","ddd-example","fastapi","onion-architecture","python"],"created_at":"2024-08-01T16:01:48.877Z","updated_at":"2025-05-15T14:09:04.891Z","avatar_url":"https://github.com/iktakahiro.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"# Python DDD \u0026 Onion-Architecture Example and Techniques\n\n[![A workflow to run test](https://github.com/iktakahiro/dddpy/actions/workflows/test.yml/badge.svg)](https://github.com/iktakahiro/dddpy/actions/workflows/test.yml)\n\n[English](README.md) | 日本語\n\n**注意**: このリポジトリは「PythonのWebアプリケーションでDDDアーキテクチャを実装する方法」を説明するためのサンプルです。参考として使用する場合は、本番環境にデプロイする前に認証とセキュリティの実装を追加してください。\n\n* ブログ記事: [Python DDD オニオンアーキテクチャ](https://iktakahiro.dev/python-ddd-onion-architecture)\n\n## 技術スタック\n\n* [FastAPI](https://fastapi.tiangolo.com/)\n* [SQLAlchemy](https://www.sqlalchemy.org/)\n  * [SQLite](https://www.sqlite.org/index.html)\n* [uv](https://github.com/astral-sh/uv)\n* [Docker](https://www.docker.com/)\n\n## プロジェクトのセットアップ\n\n1. uvを使用して依存関係をインストールします：\n\n```bash\nmake install\n```\n\n2. Webアプリケーションを実行します：\n\n```bash\nmake dev\n```\n\n## コードアーキテクチャ\n\nディレクトリ構造は[オニオンアーキテクチャ](https://jeffreypalermo.com/2008/07/the-onion-architecture-part-1/)に基づいています：\n\n```tree\n├── main.py\n├── dddpy\n│   ├── domain\n│   │   └── todo\n│   │       ├── entities\n│   │       │   └── todo.py\n│   │       ├── value_objects\n│   │       │   ├── todo_title.py\n│   │       │   ├── todo_description.py\n│   │       │   ├── todo_id.py\n│   │       │   └── todo_status.py\n│   │       ├── repositories\n│   │       │   └── todo_repository.py\n│   │       └── exceptions\n│   ├── infrastructure\n│   │   ├── di\n│   │   │   └── injection.py\n│   │   └── sqlite\n│   │       ├── database.py\n│   │       └── todo\n│   │           ├── todo_repository.py\n│   │           └── todo_dto.py\n│   ├── presentation\n│   │   └── api\n│   │       └── todo\n│   │           ├── handlers\n│   │           │   └── todo_api_route_handler.py\n│   │           ├── schemas\n│   │           │   └── todo_schema.py\n│   │           └── error_messages\n│   │               └── todo_error_message.py\n│   └── usecase\n│       └── todo\n│           ├── create_todo_usecase.py\n│           ├── update_todo_usecase.py\n│           ├── start_todo_usecase.py\n│           ├── find_todos_usecase.py\n│           ├── find_todo_by_id_usecase.py\n│           ├── complete_todo_usecase.py\n│           └── delete_todo_usecase.py\n└── tests\n```\n\n### ドメイン層\n\nドメイン層には、コアとなるビジネスロジックとルールが含まれています。主に以下の要素で構成されています：\n\n1. エンティティ\n2. 値オブジェクト\n3. リポジトリインターフェース\n\nこのプロジェクトでの各コンポーネントの実装は以下の通りです：\n\n#### 1. エンティティ\n\nエンティティは一意の識別子を持つドメインモデルです。このプロジェクトでは、`Todo`クラスがエンティティとして実装されています：\n\n```python\nclass Todo:\n    def __init__(\n        self,\n        id: TodoId,\n        title: TodoTitle,\n        description: Optional[TodoDescription] = None,\n        status: TodoStatus = TodoStatus.NOT_STARTED,\n        created_at: datetime = datetime.now(),\n        updated_at: datetime = datetime.now(),\n        completed_at: Optional[datetime] = None,\n    ):\n        self._id = id\n        self._title = title\n        self._description = description\n        self._status = status\n        self._created_at = created_at\n        self._updated_at = updated_at\n        self._completed_at = completed_at\n```\n\nエンティティの主な特徴：\n\n* 一意の識別子（`id`）を持つ\n* 状態を変更できる（`update_title`、`start`、`complete`などのメソッド）\n* 識別子によって同一性が決定される（`__eq__`メソッドの実装）\n\nこのプロジェクトでの`__eq__`メソッドの実装は、DDDの原則に従っています：\n\n```python\ndef __eq__(self, obj: object) -\u003e bool:\n    if isinstance(obj, Todo):\n        return self.id == obj.id\n    return False\n```\n\nこの実装のポイント：\n\n* 同一性は識別子（`id`）のみによって判断される\n* `isinstance`チェックによる型安全性の確保\n* エンティティの本質的な特徴に焦点を当てたクリーンな実装\n\n#### 2. 値オブジェクト\n\n値オブジェクトは識別子を持たない不変のドメインモデルです。このプロジェクトでは、以下のような値オブジェクトを実装しています：\n\n```python\n@dataclass(frozen=True)\nclass TodoTitle:\n    value: str\n\n    def __post_init__(self):\n        if not self.value:\n            raise ValueError('Title is required')\n        if len(self.value) \u003e 100:\n            raise ValueError('Title must be 100 characters or less')\n```\n\n値オブジェクトの主な特徴：\n\n* `@dataclass(frozen=True)`による不変性の保証\n* 値の検証ロジックを含む（`__post_init__`）\n* 識別子を持たない\n* 値の内容によって同一性が決定される\n\n#### 3. リポジトリインターフェース\n\nリポジトリはエンティティの永続化を担当する抽象化レイヤーです。このプロジェクトでは`TodoRepository`インターフェースを次のように実装しています：\n\n```python\nclass TodoRepository(ABC):\n    @abstractmethod\n    def save(self, todo: Todo) -\u003e None:\n        \"\"\"Save a Todo\"\"\"\n\n    @abstractmethod\n    def find_by_id(self, todo_id: TodoId) -\u003e Optional[Todo]:\n        \"\"\"Find a Todo by ID\"\"\"\n\n    @abstractmethod\n    def find_all(self) -\u003e List[Todo]:\n        \"\"\"Get all Todos\"\"\"\n\n    @abstractmethod\n    def delete(self, todo_id: TodoId) -\u003e None:\n        \"\"\"Delete a Todo by ID\"\"\"\n```\n\nリポジトリの主な特徴：\n\n* エンティティの永続化を抽象化する\n* ドメイン層とインフラ層の境界を定義する\n* インフラ層で具体的な実装を提供する\n\n### インフラ層\n\nインフラ層はドメイン層で定義されたインターフェースを実装します。主に以下の要素で構成されています：\n\n1. データベース設定\n2. リポジトリの実装\n3. 外部サービスとの統合\n4. 依存性注入（DI）の設定\n\n### ユースケース層\n\nユースケース層には、アプリケーション固有のビジネスルールが含まれています。主に以下の要素で構成されています：\n\n1. ユースケースの実装\n2. DTO（データ転送オブジェクト）\n3. サービスインターフェース\n\nこのプロジェクトでは、「1つのユースケースに1つのパブリックメソッド」というルールを採用し、各ユースケースを単一の`execute`メソッドを持つ独立したクラスとして実装しています。実装例は以下の通りです：\n\n#### 1. ユースケースインターフェースと実装\n\n各ユースケースは以下の構造に従います：\n\n```python\nclass CreateTodoUseCase:\n    \"\"\"CreateTodoUseCase defines a use case interface for creating a new Todo.\"\"\"\n\n    @abstractmethod\n    def execute(\n        self, title: TodoTitle, description: Optional[TodoDescription] = None\n    ) -\u003e Todo:\n        \"\"\"execute creates a new Todo.\"\"\"\n\n\nclass CreateTodoUseCaseImpl(CreateTodoUseCase):\n    \"\"\"CreateTodoUseCaseImpl implements the use case for creating a new Todo.\"\"\"\n\n    def __init__(self, todo_repository: TodoRepository):\n        self.todo_repository = todo_repository\n\n    def execute(\n        self, title: TodoTitle, description: Optional[TodoDescription] = None\n    ) -\u003e Todo:\n        \"\"\"execute creates a new Todo.\"\"\"\n        todo = Todo.create(title=title, description=description)\n        self.todo_repository.save(todo)\n        return todo\n```\n\nユースケースの主な特徴：\n\n* ユースケースごとに1つのクラスを用意\n* 単一責任の原則に従う設計\n* 明確なインターフェース定義\n* コンストラクタによる依存性注入\n* インスタンス化のためのファクトリ関数\n\n#### 2. エラーハンドリング\n\nユースケースはドメイン固有のエラーを処理します：\n\n```python\nclass StartTodoUseCaseImpl(StartTodoUseCase):\n    def execute(self, todo_id: TodoId) -\u003e None:\n        todo = self.todo_repository.find_by_id(todo_id)\n\n        if todo is None:\n            raise TodoNotFoundError\n\n        if todo.is_completed:\n            raise TodoAlreadyCompletedError\n\n        if todo.status == TodoStatus.IN_PROGRESS:\n            raise TodoAlreadyStartedError\n\n        todo.start()\n        self.todo_repository.save(todo)\n```\n\nエラーハンドリングの主な特徴：\n\n* ドメイン固有の例外を定義\n* 明確なエラー条件の設定\n* 状態変更前の入念な検証\n* アトミックな操作の保証\n\n### プレゼンテーション層\n\nプレゼンテーション層はHTTPリクエストとレスポンスを処理します。主に以下の要素で構成されています：\n\n1. FastAPIルートハンドラ\n2. リクエスト/レスポンスモデル\n3. 入力検証ロジック\n\nハンドラは`presentation/api`ディレクトリに配置され、アプリケーションのAPI層を形成します。各ドメイン（例：`todo`）は独自のハンドラ、スキーマ、エラーメッセージ定義を持っています。\n\n## 動作方法\n\n1. VSCodeでこのリポジトリをクローンして開きます\n2. リモートコンテナを起動します\n3. Dockerコンテナのターミナルで`make dev`を実行します\n4. APIドキュメントにアクセスします：[http://127.0.0.1:8000/docs](http://127.0.0.1:8000/docs)\n\n![OpenAPI Doc](./screenshots/openapi_doc.png)\n\n### RESTful APIのサンプルリクエスト\n\n* 新しいTodoを作成する：\n\n```bash\ncurl --location --request POST 'localhost:8000/todos' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n    \"title\": \"Implement DDD architecture\",\n    \"description\": \"Create a sample application using DDD principles\"\n}'\n```\n\n* POSTリクエストのレスポンス：\n\n```json\n{\n    \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n    \"title\": \"Implement DDD architecture\",\n    \"description\": \"Create a sample application using DDD principles\",\n    \"status\": \"TODO\",\n    \"created_at\": 1614007224642,\n    \"updated_at\": 1614007224642\n}\n```\n\n* Todoを取得する：\n\n```bash\ncurl --location --request GET 'localhost:8000/todos'\n```\n\n* GETリクエストのレスポンス：\n\n```json\n[\n    {\n        \"id\": \"550e8400-e29b-41d4-a716-446655440000\",\n        \"title\": \"Implement DDD architecture\",\n        \"description\": \"Create a sample application using DDD principles\",\n        \"status\": \"not_started\",\n        \"created_at\": 1614006055213,\n        \"updated_at\": 1614006055213\n    }\n]\n```\n\n## 開発\n\n### テストの実行\n\n```bash\nmake test\n```\n\n### コード品質\n\nこのプロジェクトでは、コード品質を維持するために以下のツールを使用しています：\n\n* [mypy](http://mypy-lang.org/) - 静的型チェック\n* [ruff](https://github.com/astral-sh/ruff) - リンティング\n* [pytest](https://docs.pytest.org/) - テスト\n\n### Docker開発環境\n\nこのプロジェクトには、Dockerベースの開発環境用の`.devcontainer`設定が含まれています。これにより、異なるマシン間で一貫した開発環境を確保できます。\n\n## ライセンス\n\nこのプロジェクトはMITライセンスのもとで公開されています。詳細は[LICENSE](LICENSE)ファイルを参照してください。\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiktakahiro%2Fdddpy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiktakahiro%2Fdddpy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiktakahiro%2Fdddpy/lists"}