DDD(ドメイン駆動設計)におけるレイヤードアーキテクチャは、以下の4層に分割することが一般的です。
- コントローラー層(インターフェース層 / プレゼンテーション層)
- ユースケース層(アプリケーション層 / アプリケーションサービス層)
- ドメイン層(リポジトリインターフェース含む)
- インフラストラクチャ層(リポジトリ実装クラス)
しかし、初学者にとっては、上記の各層の違いが分かりにくく、どの層に何を書けば良いのか混乱しがちです。
この記事では、各層の責務を丁寧に整理し、Pythonで書いたシンプルなサンプルコードを通して、レイヤードアーキテクチャについてわかりやすく解説します。
DDDを適用したレイヤードアーキテクチャの全体像

レイヤードアーキテクチャは以下の4層に分割するのが一般的です。
- コントローラー層(インターフェース層 / プレゼンテーション層)
- ユーザーの入力とユースケース層までの橋渡しを行う層。
- 入出力の変換やバリデーションを行い、結果をユーザーに返す。
- ユースケース層(アプリケーション層 / アプリケーションサービス層)
- コントローラー層から呼び出され、ドメイン層のモデル(エンティティ・値オブジェクト)やサービスを利用して処理を組み立て、アプリケーションとしての処理シナリオ(ユースケース)を実現する層。
- ドメイン層(リポジトリインターフェース含む)
- ビジネスルールそのものを表現する層。
- エンティティ、値オブジェクト、ドメインサービス、リポジトリのインターフェースを含み、ユースケース層に対して、アプリで扱うルールや振る舞いを提供する。
- リポジトリのインターフェースだけを含む(実装はインフラストラクチャ層が行う)。
- インフラストラクチャ層(リポジトリ実装クラス)
- DBや外部システムなどの技術的処理を担う層。
- ドメイン層のリポジトリインターフェースを実装する。
レイヤードアーキテクチャの各層の責務と特徴
コントローラー層(インターフェース層 / プレゼンテーション層)
コントローラー層(インターフェース層 / プレゼンテーション層)は、ユーザーの入力とユースケース層までの橋渡しを行う層です。ユーザーからのリクエストを受け取り、内部(ユースケース層)へ正しい形に整えて渡し、結果をユーザーに分かりやすい形で返します。
コントローラー層の責務と特徴を以下に示します。
コントローラー層の責務
- リクエストを受け取る
- ユーザーからの入力(例:HTTPリクエスト)を受け取る。
- APIのエンドポイントを定義し、外部との窓口を提供する。
- 例:
POST /users
で新規ユーザー登録、GET /users/<id>
でユーザー情報取得
- 例:
- 入力値をユースケース層に渡せる形にマッピングする
- 「ユーザーからの入力で定義されている型」と「ユースケース層で使用する型」が異なる場合、そのマッピングを行う。
- 例
- 文字列を数値に変換する
- JSONをPythonオブジェクトに変換する
- API仕様の入力を内部ドメインのオブジェクトに変換する
- 例
- 「ユーザーからの入力で定義されている型」と「ユースケース層で使用する型」が異なる場合、そのマッピングを行う。
- 入力値のバリデーションを行う
- 必須入力・型チェック・フォーマットチェックなど
- ユースケース層を呼び出す
- 「このリクエストは何をしたいのか?」に応じて正しいユースケース層を呼び出す。
- レスポンスを整形して返す
- 呼び出したユースケース層の結果をユーザーにわかりやすい形式(例:JSONレスポンス)で返す。
コントローラー層の特徴
- ビジネスロジックは持たない
- 「入力値が正しいかどうか」や「ユーザーが登録済みかどうか」といった「アプリケーションのルール」は知らない
- ただの仲介役・翻訳役に徹する
- 入出力に責任を持つ
- 入力データの形式チェック(型変換、フォーマット調整など)
- 出力データのフォーマット化(JSON, HTML, CLI出力など)
- APIエンドポイントを通じて外部との接点を担う
ユースケース層(アプリケーション層 / アプリケーションサービス層)
ユースケース層(アプリケーション層 / アプリケーションサービス層)は、コントローラー層から呼び出され、ドメイン層のモデル(エンティティ・値オブジェクト)やサービスを利用して処理を組み立て、アプリケーションのシナリオ(ユースケース)を実現する層です。自らビジネスルールを持たず、あくまで「シナリオの調整役」として振る舞います。
ユースケース層の責務と特徴を以下に示します。
ユースケース層の責務
- アプリケーションのシナリオ(ユースケース)を実現する
- アプリケーションにおける一連の処理(ユースケース)を表現する。
- 例:ユーザー登録ユースケース → 「ユーザーが存在するか確認 → 存在しなければ作成 → 保存する」
- ドメイン層にアプリで扱うルールを委譲する
- ビジネスルールの判断やロジックはドメイン層に任せる。
- ドメイン層のモデルやサービスを利用して処理を組み立てる。
- 自身は「どのモデルやサービスを使って処理を組み立てるか」に集中する。
- 自分自身はビジネスロジックを持たず、調整役に徹する
- 入力値を受け取り、ドメイン層に渡す。
- 処理の結果をコントローラー層に返す。
ユースケース層の特徴
- アプリケーションが「何を達成するか」を定義する層
- ドメイン層を呼び出して処理を組み立てる「シナリオ担当」
- 自身はアプリケーションの知識を持たず、処理の流れだけを管理する
- 複数のドメイン操作やリポジトリ操作を組み合わせ、ユースケースを完結させる
ドメイン層(リポジトリ層インターフェース含む)
ドメイン層は、アプリケーションの中心となる層で、ビジネスルールそのものを表現する層でアプリケーションで扱うルールや知識を表現します。例えば、
- メールアドレスには必ず「@」が含まれていること
- ユーザーIDは重複してはいけない
といったルールを、技術的な処理と切り離して表現します。こうしたルールは、DB操作やWeb API呼び出しといった技術的な処理に依存しません。その代わりにドメイン層では、リポジトリのインターフェースを定義し、「保存」「検索」といった処理自体はインフラストラクチャ層に任せます。
ドメイン層には、エンティティ(Entity)、値オブジェクト(Value Object)、ドメインサービス(Domain Service)、リポジトリインターフェース(Repository Interface)などが含まれます。
ドメイン層の責務と特徴を以下に示します。
ドメイン層の責務
- エンティティ(Entity)を定義する
- 一意な識別子を持ち、ライフサイクルの中で状態が変化するオブジェクト
- 例:User(
id
,name
,email
)
- 値オブジェクト(Value Object)を定義する
- 識別子を持たない不変オブジェクト
- 値が同じであれば同一とみなされる
- 不変なので外部から変更できないように定義して、コンストラクタで初期化する
- 例:メールアドレス(
Email
)、金額(Money
)
- ドメインサービス(Domain Service)を定義する
- 複数のエンティティや値オブジェクトにまたがるビジネスルールを表現
- 特定のエンティティに属さない振る舞いを切り出す場所
- 例:パスワードのハッシュ化、ユーザーのロール確認
- リポジトリインターフェース(Repository Interface)を定義する
- エンティティのライフサイクルを制御する操作を提供(CRUD操作など)
- 「どのように保存するか」は知らず、あくまで抽象契約のみ定義する
- 例:
UserRepository
(find_by_id
,save
を定義)
ドメイン層の特徴
- アプリケーションの中核:最も重要な層であり、アプリ全体のルールや知識を保持
- ビジネスルールを直接表現する:アプリに関わる判断・ロジックは必ずここに集約
- 外部技術に依存しない:DBやフレームワークが変わってもドメイン層は変更しない
- 純粋でテストしやすい:外部との依存がないためユニットテストが容易
インフラストラクチャ層(リポジトリ実装クラス)
インフラストラクチャ層は、DB操作やWeb API呼び出しといった技術的な処理を担当する層です。ドメイン層で定義されたリポジトリインターフェースを実際に実装します。
ドメイン層との関係
- ドメイン層ではリポジトリインターフェースを定義します(「保存」「検索」といった操作の仕様だけ)。
- インフラストラクチャ層は、そのインターフェースを実際に実装します(例:MySQLを使ってユーザー情報を保存する)。
インフラストラクチャ層の責務と特徴を以下に示します。
インフラストラクチャ層の責務
- ドメイン層のリポジトリインターフェースを実装する
- ドメイン層が定義した抽象契約(例:
UserRepository
)を具体的に実装する。 - DBや外部APIを利用してCRUD操作を実現する。
- ドメイン層が定義した抽象契約(例:
- 技術的な処理を担当する
- 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)」と呼びます。
ここで重要なのは、ドメイン層は上位層を一切知らないという点です。ドメイン層は純粋に「アプリケーションで扱うルール」を表現するだけで、コントローラーやユースケースの存在を意識しません。
また、ユースケース層はドメイン層のインターフェースに依存する形をとります。例えばUserUseCase
はUserRepository
に依存しますが、それが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はすべて文字列扱いになりがち。
- 例えば
id
はstr
で来るが、ユースケース層は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
を属性に持ち、ビジネスルールに基づいて操作される- メールアドレスの変更メソッドを持っている → 「状態変化が許される」ことがエンティティの特徴
- 一意なID(
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や外部サービスとの接続を担う。
今回のサンプルは「ユーザー登録」というシンプルなユースケースでしたが、同じ考え方を応用すれば、より複雑なアプリケーションにも対応できます。
お読み頂きありがとうございました。