まとめと次のステップ — DDD の段階的導入
本書で学んだこと
本書では、Laravel開発者のためのDDDとクリーンアーキテクチャについて学びました。
キーポイントの整理
DDDの本質
| 概念 | ポイント | 効果 |
|---|---|---|
| 値オブジェクト | 識別子なし、不変、全属性で比較 | 型安全、ビジネスルール集約 |
| エンティティ | 識別子あり、ライフサイクル | ビジネスルールの表現 |
| 集約 | 整合性の境界 | トランザクションの単位 |
| リポジトリ | 永続化の抽象化 | テスト容易性、関心の分離 |
アーキテクチャのルール
| 層 | 知っていること | 知らないこと |
|---|---|---|
| Controller | HTTPリクエスト/レスポンス | ビジネスルール、DB構造 |
| UseCase | ドメインオブジェクトの使い方 | HTTP形式、DB構造 |
| Domain Entity | ビジネスルール | 永続化方法、フレームワーク |
| Repository | テーブル構造、Eloquent | HTTP形式 |
トランザクション管理
| 場面 | トランザクションの場所 | 理由 |
|---|---|---|
| 1集約のみ更新 | Repository | 集約の整合性を保証 |
| 複数集約を更新 | UseCase | 複数集約の整合性を保証 |
段階的導入のロードマップ
DDDを既存プロジェクトに導入する際の推奨アプローチ:
フェーズ1: 値オブジェクト導入
リスク: 低 / 効果: 高 / 既存コードへの影響: 小
最も導入しやすく、効果が見えやすいフェーズです。
// Step 1: 値オブジェクトを作成
// app/Domain/Shared/EmailAddress.php
final class EmailAddress
{
public function __construct(private readonly string $value)
{
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException('無効なメールアドレスです');
}
}
public function value(): string { return $this->value; }
}
// Step 2: 新規コードから使い始める(既存コードは後回し)
// 新しいメソッドで値オブジェクトを使用
public function createUser(EmailAddress $email, UserName $name): User
{
// ...
}
// Step 3: 徐々に既存コードを置き換え
// Before
$user->email = $request->input('email');
// After
$user->email = (new EmailAddress($request->input('email')))->value();
導入対象の優先順位:
| 優先度 | 対象 | 理由 |
|---|---|---|
| 高 | 識別子(ID) | 型安全性の向上が大きい |
| 高 | 金額(Money) | 計算ロジックの集約 |
| 中 | メールアドレス | バリデーション集約 |
| 中 | 住所 | 複数フィールドの束ね |
フェーズ2: サービス層分離
リスク: 中 / 効果: 高 / 既存コードへの影響: 中
Fat ControllerをUseCaseに分離します。
// Step 1: ディレクトリ構造を準備
// app/Application/UseCase/User/CreateUserUseCase.php
// Step 2: 既存のControllerからビジネスロジックを抽出
// Before: Fat Controller
class UserController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([...]);
// このロジックをUseCaseに移動
$user = new User($validated);
$user->save();
if ($user->isNewCustomer()) {
Mail::to($user)->send(new WelcomeMail());
$this->pointService->grantWelcomePoints($user);
}
return response()->json($user);
}
}
// Step 3: UseCaseを作成
final class CreateUserUseCase
{
public function __construct(
private readonly Mailer $mailer,
private readonly PointService $pointService,
) {}
public function execute(CreateUserCommand $command): User
{
$user = new User([
'email' => $command->email,
'name' => $command->name,
]);
$user->save();
if ($user->isNewCustomer()) {
$this->mailer->to($user)->send(new WelcomeMail());
$this->pointService->grantWelcomePoints($user);
}
return $user;
}
}
// Step 4: Controllerを薄くする
class UserController extends Controller
{
public function __construct(
private readonly CreateUserUseCase $createUserUseCase
) {}
public function store(CreateUserRequest $request): JsonResponse
{
$user = $this->createUserUseCase->execute($request->toCommand());
return response()->json($user, 201);
}
}
移行のコツ:
- 新機能は最初からUseCase構成で作る
- 既存機能は修正のタイミングで徐々に移行
- テストを書いてからリファクタリング
フェーズ3: エンティティとリポジトリ
リスク: 中 / 効果: 高 / 既存コードへの影響: 大
EloquentモデルとDDDエンティティを分離します。
// Step 1: ドメインエンティティを作成(Eloquentとは別に)
// app/Domain/User/User.php
final class User
{
private function __construct(
private readonly UserId $id,
private EmailAddress $email,
private UserName $name,
private UserStatus $status,
) {}
public static function create(UserId $id, EmailAddress $email, UserName $name): self
{
return new self($id, $email, $name, UserStatus::ACTIVE);
}
// ビジネスロジックをエンティティに
public function deactivate(): void
{
if ($this->status === UserStatus::DEACTIVATED) {
throw new DomainException('既に無効化されています');
}
$this->status = UserStatus::DEACTIVATED;
}
}
// Step 2: リポジトリインターフェースを作成
// app/Domain/User/UserRepositoryInterface.php
interface UserRepositoryInterface
{
public function findById(UserId $id): ?User;
public function save(User $user): void;
public function nextIdentity(): UserId;
}
// Step 3: Eloquent実装を作成
// app/Infrastructure/Repository/EloquentUserRepository.php
final class EloquentUserRepository implements UserRepositoryInterface
{
public function findById(UserId $id): ?User
{
$model = UserModel::find($id->value());
return $model ? $this->toEntity($model) : null;
}
private function toEntity(UserModel $model): User
{
return User::reconstruct(
new UserId($model->id),
new EmailAddress($model->email),
new UserName($model->name),
UserStatus::from($model->status),
);
}
}
// Step 4: ServiceProviderで紐付け
// app/Providers/RepositoryServiceProvider.php
$this->app->bind(UserRepositoryInterface::class, EloquentUserRepository::class);
// Step 5: UseCaseでリポジトリを使用
final class DeactivateUserUseCase
{
public function __construct(
private readonly UserRepositoryInterface $userRepository
) {}
public function execute(UserId $userId): void
{
$user = $this->userRepository->findById($userId)
?? throw new UserNotFoundException($userId);
$user->deactivate();
$this->userRepository->save($user);
}
}
フェーズ4: ドメインイベント導入(オプション)
リスク: 低 / 効果: 中 / 既存コードへの影響: 小
集約間の連携をイベント駆動に。
// 既存のUseCase内の密結合を解消
// Before
final class ConfirmOrderUseCase
{
public function execute(OrderId $orderId): void
{
$order = $this->orderRepository->findById($orderId);
$order->confirm();
$this->orderRepository->save($order);
// これらの処理がUseCaseに密結合
$this->inventoryService->decrease($order);
$this->mailer->send(new OrderConfirmationMail($order));
$this->pointService->grantPoints($order);
}
}
// After: イベント駆動で疎結合に
final class ConfirmOrderUseCase
{
public function execute(OrderId $orderId): void
{
$order = $this->orderRepository->findById($orderId);
$order->confirm(); // 内部でOrderConfirmedイベントを記録
$this->orderRepository->save($order); // 保存時にイベントをディスパッチ
}
}
// 各処理は独立したリスナーに
class DecreaseInventoryOnOrderConfirmed { ... }
class SendOrderConfirmationMailOnOrderConfirmed { ... }
class GrantPointsOnOrderConfirmed { ... }
マイグレーションのベストプラクティス
| プラクティス | 説明 |
|---|---|
| ストラングラーパターン | 古いコードを一度に置き換えず、新機能から徐々に適用 |
| テストファースト | リファクタリング前にテストを書いて安全性を確保 |
| 小さなPR | 大きな変更は避け、レビュー可能な単位で分割 |
| 並行運用 | 新旧のコードを一時的に共存させる |
| 機能フラグ | 新しい実装への切り替えを制御可能に |
よくある質問
Q: すべてのプロジェクトでDDDを使うべき?
いいえ。DDDは複雑なビジネスルールを持つドメインに適しています。
| プロジェクトの特性 | 推奨アプローチ |
|---|---|
| CRUDが中心のシンプルなアプリ | Active Record(Eloquentそのまま) |
| 中程度の複雑さ | 必要な部分のみDDD適用 |
| 複雑なビジネスルールが多い | 本格的なDDD |
Q: Eloquent Modelを直接ドメインエンティティとして使ってはダメ?
技術的には可能ですが、以下の問題があります。
| 問題 | 説明 |
|---|---|
| ビジネスルールの分散 | テーブル構造と密結合 |
| テスト困難 | DBがないとテストできない |
| 不変条件の保証が難しい | publicプロパティで不正な状態を防ぎにくい |
Q: 集約が大きくなりすぎたらどうする?
集約が大きくなりすぎるのは、集約の境界が適切でないサインです。
対策:
- 結果整合性(集約の章参照)を許容できないか検討
- 集約を分割(Order集約からPayment集約を分離など)
- 読み取りモデルを分離(CQRS)
CQRS(Command Query Responsibility Segregation) とは、データの「書き込み(Command)」と「読み取り(Query)」を別々のモデルで扱うパターンです。複雑な集約でも、読み取り専用のビューモデルを用意することで、パフォーマンスと保守性を両立できます。
成功のための重要なポイント
- チーム全体の理解: DDDは個人技術ではなくチーム活動
- 段階的導入: 一度にすべてを変更せず、小さく始める
- ドメインエキスパートとの協働: 技術者だけで進めない
- 継続的リファクタリング: 理解が深まるにつれてモデルを改善
- 過度な抽象化を避ける: シンプルさを保つ
次のステップ
本書を読み終えた今、次にどのようなアクションを取るべきでしょうか?以下に具体的な学習パスを提案します。
ステップ1: 小さく始める(1〜2週間)
目標: 既存プロジェクトの一部に値オブジェクトを導入します。
やること:
□ 既存コードから「よく使う型」を特定(EmailAddress, UserId, Moneyなど)
□ 1つの値オブジェクトを実装
□ 新規機能で値オブジェクトを使ってみる
□ チームメンバーにレビューしてもらう
推奨リソース:
- 本書の第5章「値オブジェクト」を復習
- 成瀬允宣「ドメイン駆動設計入門」第2章
ステップ2: UseCase層を分離(1〜2ヶ月)
目標: Fat Controllerを1つリファクタリングします。
やること:
□ 最も複雑なControllerを1つ選ぶ
□ ビジネスロジックをUseCaseクラスに抽出
□ テストを書く(UseCase層のユニットテスト)
□ 同僚とペアプログラミング
推奨リソース:
- 本書の第11章「ユースケース層」
- 本書の第12章「プレゼンテーション層」
ステップ3: ドメインモデルを育てる(3〜6ヶ月)
目標: 1つの境界づけられたコンテキストを完成させます。
やること:
□ ドメインエキスパートと対話(週1回のミーティング)
□ ユビキタス言語を整理(用語集を作成)
□ エンティティと集約を設計
□ Repositoryパターンを実装
□ 継続的にリファクタリング
推奨リソース:
- 本書の第5〜7章(エンティティ、集約)
- エリック・エヴァンス「ドメイン駆動設計」第2部・第3部
ステップ4: チーム全体で実践(6ヶ月〜)
目標: DDDをチームのスタンダードにします。
やること:
□ チーム勉強会を開催(月1回)
□ コードレビューでDDD観点を共有
□ 設計パターンを文書化(チーム内Wiki)
□ 社内LT/ブログ記事で知見を共有
推奨リソース:
- 本書の全章
- ヴァーン・ヴァーノン「実践ドメイン駆動設計」
- 一度にすべてやろうとしない: DDDは段階的に導入するもの
- 失敗から学ぶ: 最初の設計は必ず変わる。それが正常
- 仲間を作る: チームメンバーやコミュニティと一緒に学ぶ
- アウトプットする: ブログやLTで学びを整理すると理解が深まる
参考資料
DDDについてさらに深く学びたい方へ、推奨する書籍・オンラインリソース・コミュニティを紹介します。
必読書籍
日本語書籍
1. 成瀬允宣「ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本」(翔泳社、2020年)
本書を読んだ後に最初に読むべき1冊です。
- 値オブジェクト、エンティティ、リポジトリなどの基礎概念を丁寧に解説
- C#のコード例が豊富で実践的
- 「なぜそうするのか」が明確に説明されている
- 軽量DDDからリッチなドメインモデルへの移行方法も紹介
おすすめポイント: 本書(Laravel DDD本)の内容と重複する部分もありますが、成瀬本はより体系的で、異なる視点からの説明が理解を深めてくれます。
2. エリック・エヴァンス「ドメイン駆動設計 モデリング/設計の統一言語への到達」(翔泳社、2011年)
DDDの原典。通称「エヴァンス本」または「青本」。
- DDDの哲学と背景を理解できる
- ユビキタス言語、境界づけられたコンテキストなどの概念の原典
- 実務経験を積んだ後に読むと、より深く理解できる
注意: 難易度が高いため、まずは成瀬本や本書で基礎を固めてから挑戦することを推奨します。
3. ヴァーン・ヴァーノン「実践ドメイン駆動設計」(翔泳社、2015年)
通称「ヴァーノン本」または「赤本」。エヴァンス本の実践版。
- より実装寄りで具体的な例が豊富
- 集約の設計、イベントソーシング、CQRSなど発展的トピックを扱う
- 大規模システムでのDDD適用事例
おすすめの読み方: 実際にDDDプロジェクトを始めた後、特定の課題に直面したときにリファレンスとして参照する。
4. ヴァーン・ヴァーノン「ドメイン駆動設計 サンプルコード&FAQ」(翔泳社、2018年)
- 「実践ドメイン駆動設計」の補足書籍
- よくある質問と回答形式で具体的な疑問を解消
- サンプルコードが豊富
英語書籍(余力があれば)
5. "Learning Domain-Driven Design" by Vlad Khononov (O'Reilly, 2021)
- 2021年出版の新しいDDD入門書
- モダンなアプローチとツールを紹介
- EventStorming、境界づけられたコンテキストの実践的な切り方など
6. "Domain-Driven Design Distilled" by Vaughn Vernon (Addison-Wesley, 2016)
- ヴァーノン本のエッセンス版
- 短時間(数時間)でDDDの全体像を把握できる
- チームメンバーへの紹介用にも最適
オンラインリソース
公式ドキュメント・技術サイト
Laravel公式ドキュメント
- サービスコンテナ、サービスプロバイダーの理解に必須
- Eloquent ORMの詳細な使い方
- テスト、イベント、キューなどDDDと組み合わせる機能の解説
Martin Fowler's Bliki https://martinfowler.com/bliki/
- DDDの基礎概念(Aggregate, Repository, Value Objectなど)の解説
- イベント駆動アーキテクチャ、CQRSパターンの解説
- マーティン・ファウラー氏の洞察に満ちた記事
特におすすめの記事:
DDD Community https://dddcommunity.org/
- DDDの公式コミュニティサイト
- Eric Evans本人も参加
- リソースやイベント情報
日本語のDDD関連リソース
1. little hands' lab
松岡幸一郎氏(@little_hand_s)による DDD 専門ブログ。集約設計、値オブジェクト、実装パターンなど、実践に踏み込んだ解説記事が多数あります。
https://little-hands.hatenablog.com/
おすすめトピック:
- 集約の設計と実装
- 値オブジェクトの導入方法
- ドメイン駆動設計 モデリング/実装ガイド(著書)
2. Qiita / Zenn のDDDタグ
多くの実践者が知見を共有しています。特に「失敗談」「リファクタリング体験記」は学びが多いです。
3. DDD Alliance
日本のDDD実践者コミュニティ。勉強会やワークショップを定期開催しています。
- 公式サイト: https://www.ddd-alliance.org/
- イベント情報(connpass): https://ddd-alliance.connpass.com/
4. カンファレンス発表
過去のカンファレンス資料(Speaker Deck, SlideShareなど):
- PHPカンファレンス
- Laravelカンファレンス(Laravel JP Conference)
- DDD Alliance主催イベント
検索ワード例: 「ドメイン駆動設計 実践」「Laravel DDD」「集約 設計」
5. YouTube チャンネル
- PHPカンファレンスの録画
- DDD Alliance のセッション録画
- 個人の技術解説チャンネル
ツール・フレームワーク
EventStorming オンラインツール
EventStorming(イベントストーミング)は、ドメインエキスパートと開発者が一緒にドメインモデルを探索するワークショップ手法です。
PHPStan / Psalm
- 静的解析ツール
- 型安全性を高め、ドメインモデルの整合性をチェック
コミュニティへの参加方法
DDDの学習は1人で進めるより、コミュニティで仲間と学ぶ方が効果的です。
オンラインコミュニティ
1. Slack / Discord ワークスペース
- PHPerKaigi Slack: 日本のPHPコミュニティ
- Laravel JP: Laravel日本ユーザーグループ
- DDD Alliance: DDD実践者コミュニティ
加入方法: 各コミュニティのWebサイトから招待リンクを取得します。
2. Twitter / X
DDDに関する情報を発信している方々をフォロー:
- 成瀬允宣氏(@nrslib)
- その他、#DDD, #ドメイン駆動設計 タグで活動している実践者
3. GitHub
オープンソースのDDD実装例をスター・フォロー:
- DDD実践プロジェクトのコードを読む
- Issue/Discussionで議論に参加
- 自分のコードをOSSとして公開
オフラインイベント
1. 勉強会・meetup
- DDD Alliance主催イベント: 定期的に勉強会を開催
- PHPカンファレンス: 年1回の大型イベント(11月頃)
- Laravel Meetup Tokyo: Laravel日本ユーザーグループの勉強会
イベント情報: connpass や TECH PLAY で検索できます。
2. 社内勉強会を立ち上げる
自社でDDD勉強会を開催する方法:
- 興味のある同僚2〜3人を誘う
- 週1回、1章ずつ書籍を輪読
- 実際のコードをレビューし合う
- 学んだことを社内Wikiにまとめる
アウトプット活動
1. ブログ記事を書く
おすすめプラットフォーム:
- Zenn: エンジニア向け、Markdown執筆、技術書も出版可能
- Qiita: 老舗の技術記事共有サイト
- note: より幅広い層に届けたい場合
記事テーマ例:
- 「LaravelプロジェクトにDDDを導入してみた」
- 「値オブジェクト導入前後のコード比較」
- 「集約の境界設計で悩んだ話」
2. 登壇(LT)にチャレンジ
5〜10分のライトニングトーク(LT)から始めましょう:
- 社内勉強会でのLT
- meetupでの発表
- カンファレンスのLT枠に応募
3. OSSへの貢献
- DDDライブラリへのPR
- サンプルコードの改善提案
- ドキュメントの翻訳
継続的な学習のために
定期的な振り返り
月次振り返り:
□ 今月導入したDDDパターンは何か?
□ うまくいったことは?
□ 改善すべき点は?
□ 次月の目標は?
コードレビューの観点を磨く
DDD観点でのコードレビューチェックリスト:
- ビジネスルールがドメイン層に集約されているか?
- 値オブジェクトが適切に使われているか?
- 集約の境界は適切か?
- 依存の方向は正しいか?(外側→内側)
読書ノートを作る
技術書を読む際:
- 重要な概念をメモ
- 自分のプロジェクトでどう使えるか考える
- 実際に試してみた結果を記録
- 定期的に見返して知識を定着させる
- 質問を恐れない: 「こんなこと聞いていいのか」と思う質問こそ価値がある
- 失敗談を共有する: 成功事例より失敗談の方が学びになる
- 小さくても貢献する: タイポ修正、ドキュメント改善も立派な貢献
- Give & Take: 情報を受け取るだけでなく、自分の経験も発信する
おわりに
DDDは「技術のための技術」ではなく、より良いソフトウェアを通じてビジネス価値を提供するための手段です。
Laravelの柔軟性を活かし、プロジェクトの成熟度に応じて適切なレベルでDDDの概念を取り入れることで、保守性と表現力に優れたアプリケーションを構築できます。
重要なのは、DDDの技術的パターンを機械的に適用することではなく、ドメインの本質を理解し、コードでそれを表現することです。
本書で学んだ知識を活かして、ぜひ実際のプロジェクトで実践してみてください。最初は小さく始め、徐々にチーム全体でDDDを実践していくことをお勧めします。
皆さんのLaravel開発がDDDによってさらに良いものになることを願っています。
ここまで読んでいただき、ありがとうございました。