👾

DDD(ドメイン駆動設計)とクリーンアーキテクチャのまとめ

に公開

DDD(ドメイン駆動設計)とクリーンアーキテクチャのまとめ

はじめに

自分なりに理解したDDD(ドメイン駆動設計)とクリーンアーキテクチャについて、まとめてみました! もし「ここは違うよ〜」「全然理解できていないよ!」という点があれば、厳しくフィードバックをもらえると嬉しいです🙇‍♀️
私に対しては「どんとこい!」という気持ちでコメントをお待ちしていますが、コメントしてくれた方に対して批判をすることは許しません。 批判ではなく、知識の共有を心がけてください!
皆で成長できるようにしたいという意図があります!

みんなでより良い技術を学び合い、気軽に意見を交換できるようにしたいので、どんな意見でも大歓迎です。どうぞ、よろしくお願いします!

DDD(ドメイン駆動設計)とは?

目的

  • ビジネスの複雑さ に立ち向かうためのアプローチ
  • ドメイン(業務知識)に集中した設計
  • 開発者とドメインエキスパート(業務担当者)が**共通言語(ユビキタス言語)**を使う

主な構成要素

要素 説明
エンティティ(Entity) 識別子を持ち、ライフサイクルのあるオブジェクト(例:User)
値オブジェクト(Value Object) 識別子を持たず、不変で意味を持つ値(例:Email)
ドメインサービス エンティティに属さない、複数のオブジェクトにまたがるドメインロジック(例:重複ユーザーの確認)
リポジトリ 永続化のための抽象的なインターフェース(例:ユーザーを保存・検索)
ユースケース(アプリケーションサービス) ビジネス操作の「操作単位」を表現。ドメインオブジェクトを使って処理を行う

クリーンアーキテクチャとは?

目的

  • ソフトウェアを 変更しやすく・テストしやすく するための構造
  • 関心の分離依存性の逆転(依存の方向は内向き) を徹底

レイヤー構成(円形)

[外側] ───────────────────────────────  
| プレゼンテーション層(例:React, Controller) ← UI, APIの入り口 |  
───────────────────────────────────────  
| アプリケーション層(UseCase) ← 何をするか(ビジネスフロー) |  
───────────────────────────────────────  
| ドメイン層(Entity, VO, Domain Service) ← ルール(業務知識) |  
[中心]───────────────────────────────

原則:依存の向きは内向き!

  • プレゼンテーション → アプリケーション → ドメイン には依存して良い
  • ドメイン → アプリケーション・プレゼンテーション には依存しない

両者の関係

観点 DDD クリーンアーキテクチャ
中心 ドメイン ドメイン
強調点 業務の複雑さの解決 構造と依存関係の明確化
共通点 ドメインモデルを中心に据える ドメインが外部に依存しない
補完関係 設計思想 アーキテクチャパターン

一緒に使うことでより強力な設計になります!


実装例のポイント(TypeScriptでの例)

  1. ドメイン層
    • User(エンティティ)
    • Email(値オブジェクト)
    • UserService(重複チェック)
// ドメイン層: User(エンティティ)
class User {
  constructor(
    public id: string,
    public name: string,
    public email: Email
  ) {}
}

// ドメイン層: Email(値オブジェクト)
class Email {
  constructor(private value: string) {}

  getValue() {
    return this.value;
  }
}
  1. アプリケーション層(ユースケース)
// アプリケーション層: RegisterUserUseCase(ユースケース)
class RegisterUserUseCase {
  constructor(private userRepository: IUserRepository) {}

  async execute(name: string, email: string): Promise<void> {
    const user = new User(generateId(), name, new Email(email));
    await this.userRepository.save(user);
  }
}
  1. リポジトリ層(インターフェース)
// リポジトリ層: IUserRepository(インターフェース)
export interface IUserRepository {
  findByEmail(email: Email): Promise<User | null>;
  save(user: User): Promise<void>;
}
  1. インフラ層(実装)
// インフラ層: PrismaUserRepository(実装)
class PrismaUserRepository implements IUserRepository {
  async findByEmail(email: Email): Promise<User | null> {
    const result = await prisma.user.findUnique({ where: { email: email.getValue() } });
    return result ? new User(result.id, result.name, new Email(result.email)) : null;
  }

  async save(user: User): Promise<void> {
    await prisma.user.create({
      data: {
        id: user.id,
        name: user.name,
        email: user.email.getValue(),
      },
    });
  }
}
  1. プレゼンテーション層(例:Express Controller)
// プレゼンテーション層: Express Controller(例)
import express from 'express';
import { RegisterUserUseCase } from './application/RegisterUserUseCase';

const app = express();
app.use(express.json());

const userRepository = new PrismaUserRepository();
const registerUserUseCase = new RegisterUserUseCase(userRepository);

app.post('/register', async (req, res) => {
  const { name, email } = req.body;
  await registerUserUseCase.execute(name, email);
  res.send('User registered');
});

app.listen(3000, () => {
  console.log('Server started');
});

まとめ

ポイント | 内容
👍 DDD | 業務ロジックの複雑さを扱いやすくする
👍 クリーンアーキテクチャ | 変更しやすくテストしやすい構造にする
🎯 一緒に使うと最強 | DDDでロジック、クリーンアーキテクチャで構造を整理
🎯 依存は内向きに | ドメインが他に依存しないように設計する
🎯 Repositoryはポート、実装はインフラが担当 | IUserRepositoryを定義し、PrismaUserRepositoryなどで実装する

記事を読んでくれた方へ

DDD(ドメイン駆動設計)やクリーンアーキテクチャの思想に基づいて設計を行うことは、非常に有益なアプローチですが、注意が必要です。これらのアーキテクチャを厳密に適用しすぎると、プロダクトによっては以下のような問題が発生することがあります。

1. 過剰な抽象化

  • 問題: 初期の段階で過剰に抽象化してしまうと、シンプルな機能の開発でも過剰な設計が必要となり、余計なコードや複雑さが生まれます。これにより開発スピードが遅くなり、簡単な変更が難しくなることがあります。
  • 対策: 最初は必要最小限の抽象化を行い、プロジェクトが進行する中で必要に応じて抽象化を進めるようにすることが重要です。

2. ドメイン駆動設計の適用範囲

  • 問題: DDDは複雑なビジネスロジックを扱う場合に非常に強力ですが、単純なプロジェクトに適用すると逆に設計が過剰で、開発チームがその複雑さに対応しきれなくなることがあります。結果として、ドメインモデルやサービスが煩雑になり、保守性が低下します。
  • 対策: プロジェクトの規模や複雑さに応じて、DDDの適用範囲を見極めることが重要です。シンプルなプロジェクトであれば、ドメイン層を軽く設計し、必要な部分だけを強化するアプローチが有効です。

3. 開発チーム間のコミュニケーションコスト

  • 問題: DDDでは、ドメインエキスパートと開発者が共通言語を使って密に連携することが推奨されていますが、規模の大きなチームやプロジェクトでは、このコミュニケーションコストが増大し、ドメイン理解が遅れることがあります。特に、すべての開発者がドメインに対して深い理解を持つことが求められるため、初期段階での学習や調整が時間を取られる場合があります。
  • 対策: 小さなチームであれば比較的スムーズに進められますが、大規模なチームでは、ドメイン理解を進めるために定期的にコミュニケーションの場を設けたり、簡潔なドメインドキュメントを作成したりすることで、理解のギャップを縮めていくことが大切です。

結論

DDDやクリーンアーキテクチャは非常に強力なアーキテクチャパターンですが、すべてのプロジェクトに適用するのが最適とは限りません。開発するプロダクトの性質やチームの規模に合わせて、設計アプローチを柔軟に調整することが大切です。最終的には、プロジェクトの特性に合わせた最適な設計を選ぶことが重要です。
なんでもかんでもエンジニアの理想とするアーキテクチャにしすぎると問題も出る可能性があります。

Discussion