TypeScript上級

上級 TypeScriptで学ぶドメイン駆動設計|アンチパターン編

導入

ドメイン駆動設計(DDD)は、複雑なビジネスロジックを持つシステムを構築する際に強力なアプローチです。しかし、実際の業務での実装では、意図せずアンチパターンに陥ることがあります。本記事では、TypeScriptを用いた具体的なシナリオを通じて、よくある失敗例とその改善方法を探ります。

教科書レベルの解説(ドメイン駆動設計)

重要な概念の整理

ドメイン駆動設計では、ビジネスドメインに基づいたモデルを作成し、それを中心にシステムを構築します。エンティティ、バリューオブジェクト、アグリゲート、リポジトリなどの概念が重要です。これらの要素がどのように相互作用し、ビジネスロジックを実現するのかを理解することが、成功するシステム設計の鍵となります。

コード例(TypeScript)


class User {
    constructor(public id: string, public name: string) {}
}

class UserRepository {
    private users: User[] = [];

    addUser(user: User) {
        this.users.push(user);
    }

    findUserById(id: string): User | undefined {
        return this.users.find(user => user.id === id);
    }
}

const userRepo = new UserRepository();
userRepo.addUser(new User('1', 'Alice'));
const user = userRepo.findUserById('1');
console.log(user);

コードの行ごとの解説

  1. class User { ... }: ユーザーを表すエンティティクラスです。
  2. class UserRepository { ... }: ユーザーの永続化を担当するリポジトリクラスです。
  3. addUser(user: User): void { ... }: ユーザーをリポジトリに追加するメソッドです。
  4. findUserById(id: string): User | undefined { ... }: IDでユーザーを検索するメソッドです。
  5. const userRepo = new UserRepository();: リポジトリのインスタンスを作成します。
  6. userRepo.addUser(new User('1', 'Alice'));: 新しいユーザーを追加します。
  7. const user = userRepo.findUserById('1');: IDを使用してユーザーを検索します。
  8. console.log(user);: 検索結果をコンソールに出力します。

アンチパターン編

ドメイン駆動設計の実装において、よく見られるアンチパターンの一つに「リポジトリの肥大化」があります。リポジトリが単なるデータアクセスの役割を超えて、ビジネスロジックを持ち始めると、コードの可読性と保守性が低下します。以下はその例です。


class UserRepository {
    // 省略
    findUserById(id: string): User | undefined {
        const user = this.users.find(user => user.id === id);
        if (user) {
            // 不適切なビジネスロジック
            if (user.name === 'Alice') {
                // 特別な処理
            }
        }
        return user;
    }
}

上記のように、リポジトリ内で特定のユーザーに対するビジネスロジックを持つことは問題です。これは、リポジトリの責任がデータアクセスに限定されなくなり、他の部分との結合度が高まります。改善策としては、ビジネスロジックをサービス層に移動させることが有効です。


class UserService {
    constructor(private userRepo: UserRepository) {}

    findUserWithSpecialLogic(id: string): User | undefined {
        const user = this.userRepo.findUserById(id);
        if (user && user.name === 'Alice') {
            // 特別な処理をここに移動
        }
        return user;
    }
}

このように、リポジトリは純粋にデータ操作に専念し、ビジネスロジックはサービス層に移すことで、コードの整合性を保つことができます。

まとめ

  • ドメイン駆動設計では、ビジネスロジックを適切に分離することが重要です。
  • リポジトリの肥大化を避けるために、ビジネスロジックはサービス層に移すべきです。
  • TypeScriptを用いた実装は、他の言語でも応用可能な設計思想を持っています。