DDDを適用したレイヤードアーキテクチャとは?Pythonサンプルでわかりやすく解説!

DDD(ドメイン駆動設計)におけるレイヤードアーキテクチャは、以下の4層に分割することが一般的です。

  • コントローラー層(インターフェース層 / プレゼンテーション層)
  • ユースケース層(アプリケーション層 / アプリケーションサービス層)
  • ドメイン層(リポジトリインターフェース含む)
  • インフラストラクチャ層(リポジトリ実装クラス)

しかし、初学者にとっては、上記の各層の違いが分かりにくく、どの層に何を書けば良いのか混乱しがちです。

この記事では、各層の責務を丁寧に整理し、Pythonで書いたシンプルなサンプルコードを通して、レイヤードアーキテクチャについてわかりやすく解説します。

DDDを適用したレイヤードアーキテクチャの全体像

DDDを適用したレイヤードアーキテクチャの全体像

レイヤードアーキテクチャは以下の4層に分割するのが一般的です。

  • コントローラー層(インターフェース層 / プレゼンテーション層)
    • ユーザーの入力とユースケース層までの橋渡しを行う層。
    • 入出力の変換やバリデーションを行い、結果をユーザーに返す。
  • ユースケース層(アプリケーション層 / アプリケーションサービス層)
    • コントローラー層から呼び出され、ドメイン層のモデル(エンティティ・値オブジェクト)やサービスを利用して処理を組み立て、アプリケーションとしての処理シナリオ(ユースケース)を実現する層。
  • ドメイン層(リポジトリインターフェース含む)
    • ビジネスルールそのものを表現する層。
    • エンティティ、値オブジェクト、ドメインサービス、リポジトリのインターフェースを含み、ユースケース層に対して、アプリで扱うルールや振る舞いを提供する。
    • リポジトリのインターフェースだけを含む(実装はインフラストラクチャ層が行う)。
  • インフラストラクチャ層(リポジトリ実装クラス)
    • DBや外部システムなどの技術的処理を担う層。
    • ドメイン層のリポジトリインターフェースを実装する。

レイヤードアーキテクチャの各層の責務と特徴

コントローラー層(インターフェース層 / プレゼンテーション層)

コントローラー層(インターフェース層 / プレゼンテーション層)は、ユーザーの入力とユースケース層までの橋渡しを行う層です。ユーザーからのリクエストを受け取り、内部(ユースケース層)へ正しい形に整えて渡し、結果をユーザーに分かりやすい形で返します。

コントローラー層の責務特徴を以下に示します。

コントローラー層の責務

  1. リクエストを受け取る
    • ユーザーからの入力(例:HTTPリクエスト)を受け取る。
    • APIのエンドポイントを定義し、外部との窓口を提供する。
      • 例:POST /users で新規ユーザー登録、GET /users/<id>でユーザー情報取得
  2. 入力値をユースケース層に渡せる形にマッピングする
    • 「ユーザーからの入力で定義されている型」と「ユースケース層で使用する型」が異なる場合、そのマッピングを行う。
        • 文字列を数値に変換する
        • JSONをPythonオブジェクトに変換する
        • API仕様の入力を内部ドメインのオブジェクトに変換する
  3. 入力値のバリデーションを行う
    • 必須入力・型チェック・フォーマットチェックなど
  4. ユースケース層を呼び出す
    • 「このリクエストは何をしたいのか?」に応じて正しいユースケース層を呼び出す。
  5. レスポンスを整形して返す
    • 呼び出したユースケース層の結果をユーザーにわかりやすい形式(例:JSONレスポンス)で返す。

コントローラー層の特徴

  • ビジネスロジックは持たない
    • 「入力値が正しいかどうか」や「ユーザーが登録済みかどうか」といった「アプリケーションのルール」は知らない
    • ただの仲介役・翻訳役に徹する
  • 入出力に責任を持つ
    • 入力データの形式チェック(型変換、フォーマット調整など)
    • 出力データのフォーマット化(JSON, HTML, CLI出力など)
  • APIエンドポイントを通じて外部との接点を担う

ユースケース層(アプリケーション層 / アプリケーションサービス層)

ユースケース層(アプリケーション層 / アプリケーションサービス層)は、コントローラー層から呼び出され、ドメイン層のモデル(エンティティ・値オブジェクト)やサービスを利用して処理を組み立て、アプリケーションのシナリオ(ユースケース)を実現する層です。自らビジネスルールを持たず、あくまで「シナリオの調整役」として振る舞います。

ユースケース層の責務特徴を以下に示します。

ユースケース層の責務

  1. アプリケーションのシナリオ(ユースケース)を実現する
    • アプリケーションにおける一連の処理(ユースケース)を表現する。
    • 例:ユーザー登録ユースケース → 「ユーザーが存在するか確認 → 存在しなければ作成 → 保存する」
  2. ドメイン層にアプリで扱うルールを委譲する
    • ビジネスルールの判断やロジックはドメイン層に任せる。
    • ドメイン層のモデルやサービスを利用して処理を組み立てる。
    • 自身は「どのモデルやサービスを使って処理を組み立てるか」に集中する。
  3. 自分自身はビジネスロジックを持たず、調整役に徹する
    • 入力値を受け取り、ドメイン層に渡す。
    • 処理の結果をコントローラー層に返す。

ユースケース層の特徴

  • アプリケーションが「何を達成するか」を定義する層
  • ドメイン層を呼び出して処理を組み立てる「シナリオ担当」
  • 自身はアプリケーションの知識を持たず、処理の流れだけを管理する
  • 複数のドメイン操作やリポジトリ操作を組み合わせ、ユースケースを完結させる

ドメイン層(リポジトリ層インターフェース含む)

ドメイン層は、アプリケーションの中心となる層で、ビジネスルールそのものを表現する層でアプリケーションで扱うルールや知識を表現します例えば、

  • メールアドレスには必ず「@」が含まれていること
  • ユーザーIDは重複してはいけない

といったルールを、技術的な処理と切り離して表現します。こうしたルールは、DB操作やWeb API呼び出しといった技術的な処理に依存しません。その代わりにドメイン層では、リポジトリのインターフェースを定義し、「保存」「検索」といった処理自体はインフラストラクチャ層に任せます。

ドメイン層には、エンティティ(Entity)、値オブジェクト(Value Object)、ドメインサービス(Domain Service)、リポジトリインターフェース(Repository Interface)などが含まれます。

ドメイン層の責務特徴を以下に示します。

ドメイン層の責務

  1. エンティティ(Entity)を定義する
    • 一意な識別子を持ち、ライフサイクルの中で状態が変化するオブジェクト
    • 例:User(id, name, email
  2. 値オブジェクト(Value Object)を定義する
    • 識別子を持たない不変オブジェクト
    • 値が同じであれば同一とみなされる
    • 不変なので外部から変更できないように定義して、コンストラクタで初期化する
    • 例:メールアドレス(Email)、金額(Money
  3. ドメインサービス(Domain Service)を定義する
    • 複数のエンティティや値オブジェクトにまたがるビジネスルールを表現
    • 特定のエンティティに属さない振る舞いを切り出す場所
    • 例:パスワードのハッシュ化、ユーザーのロール確認
  4. リポジトリインターフェース(Repository Interface)を定義する
    • エンティティのライフサイクルを制御する操作を提供(CRUD操作など)
    • 「どのように保存するか」は知らず、あくまで抽象契約のみ定義する
    • 例:UserRepositoryfind_by_id, saveを定義)

ドメイン層の特徴

  • アプリケーションの中核:最も重要な層であり、アプリ全体のルールや知識を保持
  • ビジネスルールを直接表現する:アプリに関わる判断・ロジックは必ずここに集約
  • 外部技術に依存しない:DBやフレームワークが変わってもドメイン層は変更しない
  • 純粋でテストしやすい:外部との依存がないためユニットテストが容易

インフラストラクチャ層(リポジトリ実装クラス)

インフラストラクチャ層は、DB操作やWeb API呼び出しといった技術的な処理を担当する層です。ドメイン層で定義されたリポジトリインターフェースを実際に実装します。

ドメイン層との関係

  • ドメイン層ではリポジトリインターフェースを定義します(「保存」「検索」といった操作の仕様だけ)。
  • インフラストラクチャ層は、そのインターフェースを実際に実装します(例:MySQLを使ってユーザー情報を保存する)。

インフラストラクチャ層の責務特徴を以下に示します。

インフラストラクチャ層の責務

  1. ドメイン層のリポジトリインターフェースを実装する
    • ドメイン層が定義した抽象契約(例:UserRepository)を具体的に実装する。
    • DBや外部APIを利用してCRUD操作を実現する。
  2. 技術的な処理を担当する
    • SQL発行やORM(SQLAlchemy, Django ORMなど)によるDBアクセス
    • 外部APIの呼び出し(REST, gRPCなど)
    • ファイル入出力(CSV, JSON, 画像保存など)

インフラストラクチャ層の特徴

  • 技術的関心事を引き受ける
    • アプリケーションのルールではなく、永続化や通信といった技術的処理を担う。
  • 置き換え可能性が高い
    • DB(MySQL → PostgreSQL)や外部サービスの切り替えも、この層を差し替えるだけで可能になる。

レイヤードアーキテクチャにおける各層の依存関係

レイヤードアーキテクチャは、ソフトウェアを「層(レイヤ)」に分けて設計する方法です。図のように上下にレイヤを並べ、上位のレイヤが下位のレイヤに依存するのが特徴です。

[ コントローラー層(インターフェース層 / プレゼンテーション層) ]
        ↓
[ ユースケース層(アプリケーション層 / アプリケーションサービス層) ]
        ↓
[ ドメイン層(リポジトリインターフェース含む) ]
        ↓
[ インフラストラクチャ層(リポジトリ実装クラス) ]

基本的には「直下のレイヤ」に依存しますが、必ずしもそれだけに限定されません。例えば、ユースケース層が直接インフラストラクチャ層に依存するケースや、コントローラー層がドメイン層を呼び出すケースもあります。

DDD(ドメイン駆動設計)を取り入れた場合

レイヤードアーキテクチャにDDDの考え方を適用すると、DIP(依存関係逆転の原則)を用いて、下のような依存関係になります。

[ コントローラー層(インターフェース層 / プレゼンテーション層) ]
        ↓
[ ユースケース層(アプリケーション層 / アプリケーションサービス層) ]
        ↓
[ ドメイン層(リポジトリインターフェース含む) ]
        ↑
[ インフラストラクチャ層(リポジトリ実装クラス) ]

純粋なレイヤードアーキテクチャとの違いは、ドメイン層とインフラ層の依存関係が逆転していることです。

  • ユースケース層 → ドメイン層のインターフェースに依存
    • 例:UserRepositoryというインターフェースを利用する。
  • インフラストラクチャ層 → ドメイン層のインターフェースを実装
    • 例:UserRepositoryImplとしてDB操作を実装する。

これにより、ユースケース層やドメイン層のコードは具体的なDBや外部サービスに依存しなくなります。もしDBをMySQLからPostgreSQLに切り替える、外部APIを差し替えるといった変更があっても、ユースケース層やドメイン層のコードは修正不要で、インフラストラクチャ層の実装を入れ替えるだけで済みます。

この考え方を「依存性逆転の原則(DIP)」と呼びます。

ここで重要なのは、ドメイン層は上位層を一切知らないという点です。ドメイン層は純粋に「アプリケーションで扱うルール」を表現するだけで、コントローラーやユースケースの存在を意識しません。

また、ユースケース層はドメイン層のインターフェースに依存する形をとります。例えばUserUseCaseUserRepositoryに依存しますが、それがSQLiteなのかPostgreSQLなのかは知りません。「どの実装を使うか」はインフラ層で決まります。

Pythonで書くシンプルDDD

ここでは「ユーザーを新規登録する」というシンプルなユースケースを例に、4層に分けて実装してみます。

フォルダ構成

myapp-ddd/               ← プロジェクトのルート
├── main.py              ← エントリーポイント(Flask起動 & 依存注入)
└── myapp/               ← アプリケーション本体
    ├── controller/       ← コントローラー層(外部I/Oとの接点)
    │   └── user_controller.py
    ├── usecase/          ← ユースケース層(アプリケーションのシナリオ)
    │   └── user_usecase.py
    ├── domain/           ← ドメイン層(ビジネスルール)
    │   ├── user.py
    │   ├── user_repository.py
    │   └── user_service.py
    └── infrastructure/   ← インフラストラクチャ層(技術的な処理)
        └── user_repository_impl.py

コントローラー層(controller/user_controller.py)

from flask import Flask, request, jsonify
from myapp.usecase.user_usecase import UserUseCase

app = Flask(__name__)

# main.py から依存注入される
user_usecase: UserUseCase = None


@app.route("/users", methods=["POST"])
def create_user():
    """
    ユーザー登録API
    - ユーザーからの入力は JSON (文字列形式)
    - ユースケース層では int や ValueObject を使う
    - コントローラーで型変換・マッピングを行う
    """
    data = request.json

    try:
        # 入力マッピング (string → int)
        user_id = int(data["id"])
        email = data["email"]
        name = data["name"]

        # ユースケース呼び出し
        user_usecase.register(user_id, email, name)

        # 結果を JSON 形式に整形して返す
        return jsonify({
            "status": "success",
            "message": f"ユーザー登録成功: id={user_id}, email={email}, name={name}"
        }), 201

    except Exception as e:
        return jsonify({
            "status": "error",
            "message": str(e)
        }), 400


@app.route("/users/<user_id>", methods=["GET"])
def show_user(user_id):
    """
    ユーザー情報取得API
    - URLパラメータは文字列 (str)
    - ユースケース層では int を使うため変換する
    """
    try:
        user = user_usecase.fetch_user(int(user_id))
        if user:
            # ドメイン層のオブジェクト → JSON にマッピング
            return jsonify({
                "id": user.user_id,
                "name": user.name,
                "email": user.email.address
            }), 200
        else:
            return jsonify({"message": "ユーザーが見つかりません"}), 404

    except Exception as e:
        return jsonify({"status": "error", "message": str(e)}), 400
  • 入力マッピング
    • HTTPのJSONはすべて文字列扱いになりがち。
    • 例えばidstrで来るが、ユースケース層はintを期待しているのでint(data["id"])で変換している。
  • 出力マッピング
    • ユースケース層から返ってくるのはUserエンティティ。
    • 直接返すのではなく、JSONに変換してクライアントに返す。

ユースケース層(usecase/user_usecase.py)

from myapp.domain.user_service import UserService
from myapp.domain.user import User, Email
from typing import Optional

class UserUseCase:
    """アプリケーションのユースケースを実現する層"""

    def __init__(self, user_service: UserService):
        self.user_service = user_service

    def register(self, user_id: int, email: str, name: str) -> None:
        """
        ユーザー登録のユースケースを実行する。
        - すでにユーザーが存在するか確認
        - 存在しなければ新規登録
        """
        existing_user = self.user_service.get_user(user_id)
        if existing_user:
            raise ValueError("このユーザーIDはすでに登録されています")

        self.user_service.register_user(user_id, email, name)

    def fetch_user(self, user_id: int) -> Optional[User]:
        """
        ユーザー情報取得のユースケースを実行する。
        """
        return self.user_service.get_user(user_id)
  • ビジネスルール(例:Emailの正しさ判定)は持たない
  • ユースケース層はドメイン層(UserService)を呼び出してシナリオ(存在確認 → 登録)を組み立てる。
  • 「Emailの形式が正しいか?」「Userをどう保存するか?」といったルールはドメイン層が担当する。
  • つまり、処理の流れはユースケース層が決めるが、ビジネスルールの中身はドメイン層に丸投げするのがDDDの基本。

ドメイン層

Entity & Value Object(domain/user.py)

from dataclasses import dataclass

# Value Object: Email
@dataclass(frozen=True)
class Email:
    """メールアドレスを表す値オブジェクト(不変)"""
    address: str

    def __post_init__(self):
        if "@" not in self.address:
            raise ValueError("Invalid email address")


# Entity: User
class User:
    """ユーザーを表すエンティティ(一意なIDを持つ)"""

    def __init__(self, user_id: int, email: Email, name: str):
        self.user_id = user_id  # 識別子
        self.email = email      # 値オブジェクトを属性に持つ
        self.name = name

    def change_email(self, new_email: Email):
        """ユーザーのメールアドレスを変更する"""
        self.email = new_email

    def __repr__(self):
        return f"User(id={self.user_id}, name={self.name}, email={self.email.address})"
  • Value Object(Email)
    • 不変(frozen=Trueにより変更不可)
    • 「@が含まれているか」などのバリデーションを自分で担う
    • つまり「正しい状態しか存在できないオブジェクト」
  • Entity(User)
    • 一意なID(user_id)を持ち、ライフサイクルの中で状態が変わりうる
    • Emailを属性に持ち、ビジネスルールに基づいて操作される
    • メールアドレスの変更メソッドを持っている → 「状態変化が許される」ことがエンティティの特徴

Repository Interface(domain/user_repository.py)

from abc import ABC, abstractmethod
from typing import Optional
from myapp.domain.user import User


class UserRepository(ABC):
    """
    ユーザー情報の永続化操作を定義するリポジトリインターフェース。
    具体的な実装はインフラストラクチャ層に委ねる。
    """

    @abstractmethod
    def find_by_id(self, user_id: int) -> Optional[User]:
        """
        指定のユーザーIDに該当するユーザーを返す。

        Args:
            user_id (int): 検索対象のユーザーID。

        Returns:
            Optional[User]: ユーザーが存在する場合はUserオブジェクト、存在しない場合はNone。
        """
        pass

    @abstractmethod
    def save(self, user: User) -> None:
        """
        ユーザー情報を保存または更新する。

        Args:
            user (User): 保存したいユーザーオブジェクト。

        Returns:
            None
        """
        pass
  • ここではインターフェース(抽象クラス)だけ実装する。
  • インフラストラクチャ層がこれを実装することで、DBやファイルシステムに依存せずにドメイン層を保てる。

Domain Service(domain/user_service.py)

from typing import Optional
from myapp.domain.user_repository import UserRepository
from myapp.domain.user import User, Email

class UserService:
    """
    ドメインサービス。
    - エンティティ単体に置くと不自然または肥大化する「ルール」をまとめる。
    - リポジトリの「抽象」に依存して確認・保存を指示する(実装詳細は知らない)。
    """

    def __init__(self, user_repository: UserRepository):
        self.user_repository = user_repository

    def register_user(self, user_id: int, email_str: str, name: str) -> None:
        """
        ユーザー登録のルールを適用してユーザーを作成・保存する。

        流れ:
          1) メール文字列から Email 値オブジェクトを生成(形式チェックは Email 側)
          2) ID の重複有無をリポジトリ経由で確認
          3) 問題なければ User を生成し、保存を指示(実装はインフラ層)

        Raises:
            ValueError: ID が既に使われている場合
        """
        email = Email(email_str)  # 値オブジェクト側で形式バリデーション
        existing = self.user_repository.find_by_id(user_id)
        if existing is not None:
            raise ValueError("User with this ID already exists")

        user = User(user_id, email, name)
        self.user_repository.save(user)

    def get_user(self, user_id: int) -> Optional[User]:
        """
        ユーザーIDでユーザーを取得する(保存実装は知らない/委譲する)。
        """
        return self.user_repository.find_by_id(user_id)
  • エンティティだけに置くと不自然・肥大化する「ルール」を外出ししてまとめる場所です。
  • 例えば「複数のエンティティをまたぐ判定(例: ユーザー登録時にIDの重複がないか)」や、「そのデータがすでにDBに保存済みかどうかの永続化の確認」といった処理を行います。
  • 技術的処理(SQLやHTTP)は持ちません。技術的な処理はインフラストラクチャ層が担当します。

インフラストラクチャ層(infrastructure/user_repository_impl.py)

import sqlite3
from typing import Optional
from myapp.domain.user_repository import UserRepository
from myapp.domain.user import User, Email

class SQLiteUserRepository(UserRepository):
    """SQLiteを使ったユーザーリポジトリ実装"""

    def __init__(self, db_path: str = "myapp.db"):
        self.db_path = db_path
        self._initialize_db()

    def _initialize_db(self):
        """テーブルがなければ作成"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS users (
                id INTEGER PRIMARY KEY,
                email TEXT NOT NULL,
                name TEXT NOT NULL
            )
        """)
        conn.commit()
        conn.close()

    def find_by_id(self, user_id: int) -> Optional[User]:
        """ユーザーIDで検索"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT id, email, name FROM users WHERE id = ?", (user_id,))
        row = cursor.fetchone()
        conn.close()

        if row:
            return User(user_id=row[0], email=Email(row[1]), name=row[2])
        return None

    def save(self, user: User) -> None:
        """ユーザーを保存(INSERT or REPLACE)"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("""
            INSERT OR REPLACE INTO users (id, email, name)
            VALUES (?, ?, ?)
        """, (user.user_id, user.email.address, user.name))
        conn.commit()
        conn.close()
  • インフラストラクチャ層は「技術的な処理」を担当します。
    • 今回は SQLite を利用して、ユーザー情報をDBに保存・取得できるようにしました。
    • UserRepository(ドメイン層で定義されたインターフェース)を継承し、実際の保存先(SQLite DB)を実装。
    • UserService(ドメインサービス)や UserUseCase(ユースケース層)は 、この実装がSQLiteなのか、PostgreSQLなのか、ファイル保存なのか知りません。
    • DIP(依存性逆転の原則)のおかげで、上位層のコードは変更不要です。
  • これにより、UserService(ドメインサービス)や UserUseCase(ユースケース層)は 「UserRepositoryが使える」 ことだけを知っていて、具体的に「どこに保存されているか」は知らなくて済みます。

今回の例ではSQLiteを使いましたが、もしMySQLやPostgreSQLを使いたい場合でも、UserRepositoryインターフェースを実装した別のクラス(例:MySQLUserRepository)を用意して差し替えるだけで対応できます。これが依存性逆転の原則(DIP)の大きな利点で、ユースケース層やドメイン層のコードを一切変更せずに、保存先や外部サービスを切り替えられるのです。

main.py

from myapp.controller import user_controller
from myapp.usecase.user_usecase import UserUseCase
from myapp.domain.user_service import UserService
from myapp.infrastructure.user_repository_impl import SQLiteUserRepository

# 依存関係を組み立てる
repo = SQLiteUserRepository("myapp.db")
service = UserService(repo)
usecase = UserUseCase(service)

# Flaskコントローラに依存を注入
user_controller.user_usecase = usecase

if __name__ == "__main__":
    user_controller.app.run(debug=True, host="0.0.0.0", port=5000)
  • SQLiteUserRepository(インフラ層の実装)を作成
  • UserService(ドメインサービス)に渡す
  • UserUseCase(ユースケース層)に渡す
  • そのユースケースをコントローラ層に注入
  • 最後に Flask サーバーを起動

【補足】動かす方法(WSL上の場合)

まず、WSLのプロジェクトルートで仮想環境を作り、入ります。

# 仮想環境を作る(初回のみ)
python3 -m venv .venv

# 仮想環境に入る
source .venv/bin/activate

ターミナルの先頭が (.venv) となればOKです。

次に、必要なライブラリをインストールします。

pip install flask

次に、プロジェクトのルート (myapp-ddd/)で以下のコマンドを実行して、サーバーを起動します。

python main.py

すると、以下のように起動ログがでます。

(.venv) user01@Ubuntu:~/work/myapp-ddd$ python main.py
 * Serving Flask app 'myapp.controller.user_controller'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://172.27.236.162:5000
Press CTRL+C to quit
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 294-647-968

ユーザー登録の動作確認は別ターミナルで以下のコマンドを実行します。

curl -X POST -H "Content-Type: application/json" \
  -d '{"id":"1","email":"test@example.com","name":"太郎"}' \
  http://127.0.0.1:5000/users

レスポンス例

{
  "status": "success",
  "message": "ユーザー登録成功: id=1, email=test@example.com, name=太郎"
}

登録したユーザー取得の動作確認は別ターミナルで以下のコマンドを実行します。

curl http://127.0.0.1:5000/users/1

レスポンス例

{
  "id": 1,
  "name": "太郎",
  "email": "test@example.com"
}

本記事のまとめ

この記事では、DDD(ドメイン駆動設計)を適用したレイヤードアーキテクチャについて、4層それぞれの責務と特徴を整理し、Pythonのサンプルコードを通して解説しました。

  • コントローラー層(インターフェース層 / プレゼンテーション層)
    • ユーザーからの入力を受け取り、ユースケース層に渡す「窓口」。
    • 入力値のバリデーションや型変換を行い、出力をユーザーに返す役割。
  • ユースケース層(アプリケーション層 / アプリケーションサービス層)
    • アプリケーションのシナリオ(ユースケース)を表現する層。
    • ビジネスルールは持たず、ドメイン層のモデルやサービスを組み合わせて処理を組み立てる。
  • ドメイン層(リポジトリインターフェース含む)
    • アプリケーションの中心。
    • ビジネスルールを直接表現し、エンティティ、値オブジェクト、ドメインサービス、リポジトリインターフェースを含む。
    • 外部技術に依存せず、純粋な形でルールを保持する。
  • インフラストラクチャ層(リポジトリ実装クラス)
    • 技術的処理を担当する層。
    • ドメイン層で定義されたリポジトリインターフェースを実装し、DBや外部サービスとの接続を担う。

今回のサンプルは「ユーザー登録」というシンプルなユースケースでしたが、同じ考え方を応用すれば、より複雑なアプリケーションにも対応できます。

お読み頂きありがとうございました。

スポンサーリンク