1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

もう迷わない!ドメイン駆動開発をベースにクリーンアーキテクチャを取り入れたPython最強フォルダ構成ガイド

Posted at

アプリケーションを開発していると、いつの間にかコードがごちゃごちゃになっている、なんて経験はありませんか?
ビジネスロジックやデータベース、外部サービスへの接続コードが一箇所に混ざり合い、変更を加えるたびに「あれ、どこを直せばいいんだっけ?」と戸惑う――こんな状況は、多くの開発者が通る道です。

ここで頼りになるのが、ドメイン駆動開発(DDD)とクリーンアーキテクチャの考え方です。どちらも「重要なビジネスロジックを明確にし、外部の技術的要素から切り離す」ことを目指しており、その組み合わせはコードベースの見通しを劇的に改善します。本記事では、これらの考え方をふまえたPythonプロジェクトのフォルダ構成例を紹介します。

「次の機能追加はどうレイヤーに分けよう?」「DBを変えてもビジネスロジックに影響が出ないようにしたい」――そんなときに立ち戻れる、一つの指針として参考にしてください。

なぜDDDとクリーンアーキテクチャを組み合わせるのか?

  • DDD (ドメイン駆動開発):ソフトウェアの「本当に重要な部分」、すなわちビジネスロジック(ドメイン)を中心に据え、チームが理解しやすいモデルとして定義していく手法です。
  • クリーンアーキテクチャ:コアのロジックを外部技術から独立させ、依存関係を内側から外側へ一方向に保ちます。これにより、UI、DB、フレームワークなどを取り替えても、ビジネスロジックが簡単には揺らぎません。

両者を組み合わせることで、ドメインモデルを中心とした明確な構造をつくりやすくなります。ドメインをしっかり定義し、その周囲にアプリケーション層、インフラ層、インターフェース層を配していくことで、どこに何を書くべきかが自然に決まってきます。

Pythonプロジェクトの基本スタイル

最近のPythonプロジェクトでは、src/ 配下にコードをまとめ、pyproject.tomlで依存管理をするパターンがよく使われます。PoetryやPipenvとの相性も良く、CI/CDパイプライン構築もやりやすいのがメリットです。

ここで紹介する分割は大きく4つのレイヤーです。

  • domain:ビジネスロジックの本体(エンティティ、値オブジェクト、ドメインサービス、リポジトリの抽象インターフェース)
  • application:ユースケース(ビジネスロジックを活用して、アプリケーションとしての操作・振る舞いを定義)
  • infrastructure:DBや外部APIアクセスなど、環境依存の実装部分
  • interface:WebフレームワークやCLIなど、ユーザーや外部からの入力・出力部分

フォルダ構成例

以下は一つの参考例です。プロジェクト規模や要件に合わせてアレンジできますが、この構成を押さえておくと迷いにくくなります。

project_name/
├─ pyproject.toml
├─ README.md
├─ docs/
├─ tests/
└─ src/
    ├─ domain/
    │   ├─ entities/
    │   │   ├─ user.py
    │   │   └─ order.py
    │   ├─ value_objects/
    │   │   └─ email_address.py
    │   ├─ services/
    │   │   └─ order_service.py
    │   └─ repositories/
    │       └─ user_repository.py   # 抽象インターフェースのみ定義
    │
    ├─ application/
    │   ├─ use_cases/
    │   │   ├─ create_user.py
    │   │   ├─ get_user.py
    │   │   └─ update_order.py
    │   ├─ dtos/
    │   │   └─ user_dto.py
    │   └─ interfaces/
    │       └─ user_repository_if.py # domainの抽象Repoを参照
    │
    ├─ infrastructure/
    │   ├─ persistence/
    │   │   ├─ orm_user.py     # DB接続によるUserRepo実装
    │   │   └─ db_config.py
    │   ├─ external_services/
    │   │   └─ payment_gateway.py
    │   └─ config/
    │       └─ settings.py
    │
    └─ interface/
        ├─ web/
        │   ├─ fastapi_app.py
        │   ├─ routes.py
        │   └─ controllers/
        │       └─ user_controller.py
        ├─ cli/
        │   └─ cli_commands.py
        └─ presenters/
            └─ user_presenter.py

各ディレクトリの役割

  • domain/:ビジネスモデルが純度100%で存在する場所。外部には依存せず、エンティティや値オブジェクトを定義します。
  • application/:ユースケース単位でビジネスロジックを呼び出す層。domainには依存しますが、infrastructureやinterfaceには直接依存しないようにします。
  • infrastructure/:具体的なDBアクセスや外部サービスへの接続処理など、技術的な詳細を実装する層です。
  • interface/:Web APIやCLIなど、外部からアプリケーションを操作する際の入り口。コントローラやルーティング、プレゼンテーション周りの処理をまとめます。

依存関係の流れ

クリーンアーキテクチャでは、ドメインが一番内側に位置します。

  • domainはどこにも依存しない
  • applicationはdomainへ依存するが、infrastructureやinterfaceには依存しない(抽象経由で接続)
  • infrastructureとinterfaceはapplicationやdomainの抽象を実装し、外側から内側を参照しない

こうすることで、たとえばDBを変えたくなったときでもdomainやapplicationには手を入れず、infrastructure側だけで対応できます。テスト時にはMockリポジトリを簡単に差し替えることも容易です。

簡単な例:UserエンティティとUseCase

domain層

# src/domain/entities/user.py
class User:
    def __init__(self, user_id: str, email: "EmailAddress"):
        self.user_id = user_id
        self.email = email
# src/domain/value_objects/email_address.py
class EmailAddress:
    def __init__(self, value: str):
        # メール形式の簡易チェックなど
        self.value = value
# src/domain/repositories/user_repository.py
from abc import ABC, abstractmethod
from domain.entities.user import User

class UserRepository(ABC):
    @abstractmethod
    def find_by_id(self, user_id: str) -> User:
        pass

    @abstractmethod
    def save(self, user: User) -> None:
        pass

application層

# src/application/use_cases/create_user.py
from domain.repositories.user_repository import UserRepository
from domain.value_objects.email_address import EmailAddress
from domain.entities.user import User

class CreateUserUseCase:
    def __init__(self, user_repo: UserRepository):
        self.user_repo = user_repo

    def execute(self, user_id: str, email_str: str):
        email = EmailAddress(email_str)
        user = User(user_id, email)
        self.user_repo.save(user)

infrastructure層(DB実装例)

# src/infrastructure/persistence/orm_user.py
from domain.repositories.user_repository import UserRepository
from domain.entities.user import User

class ORMUserRepository(UserRepository):
    def find_by_id(self, user_id: str) -> User:
        # DB検索処理(ORMによる実装)
        ...

    def save(self, user: User) -> None:
        # DBへINSERT/UPDATE
        ...

interface層(Webエンドポイント例)

# src/interface/web/user_controller.py
from fastapi import APIRouter
from application.use_cases.create_user import CreateUserUseCase
from infrastructure.persistence.orm_user import ORMUserRepository

router = APIRouter()

@router.post("/users")
def create_user(user_id: str, email: str):
    uc = CreateUserUseCase(ORMUserRepository())
    uc.execute(user_id, email)
    return {"status": "ok"}

このようにドメインやアプリケーション層では具体的なORMやWebフレームワークに依存していないため、外側の技術を置き換えるのも比較的容易です。

まとめ

  • DDDでビジネスロジック(ドメイン)をしっかり定義
  • クリーンアーキテクチャで依存関係を整理してテストしやすいコード構成へ
  • Pythonで src/ 以下にレイヤー分割すると、変更容易性・保守性が大幅に向上

この構成を導入すれば、「またDBを変えるの?」「フレームワークをアップデートするの面倒だな」といった局面でも、コアなビジネスロジックは手を加えずに済むはずです。日々の開発で悩むことが少しでも減り、あなたのチームがよりスムーズにプロダクトを成長させていくためのヒントになれば幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?