りゅうじの学習blog

学習したことをアウトプットしていきます。

6/5 ドメイン駆動設計Chapter13 13-1~13-1-2

13-1 仕様

  • オブジェクトの評価は単純なものならメソッドで定義されるが、すべてが単純なものではなく、メソッドとして定義するべきものでないものもある
    • アプリケーションサービスに記述されがちだが、オブジェクトの評価はドメインの重要なルールなので、サービスに記述するべきではない
      • この対策として仕様がある、あるオブジェクトがある評価基準に達しているかを判定するオブジェクトが仕様です

13-1-1 複雑な評価処理を確認する

簡単な評価処理、これまでに出てきた30名が上限でその評価を真偽値で返していたisFullメソッドのようなものであれば、メソッド定義でよかったのですが。

以下のように複雑なものはメソッド定義でなく、仕様オブジェクトとして切り出しましょう。切り出さずにいるとアプリケーションサービスに記述してしまうことになり、再利用性が低く、変更に対して脆弱です。

  • ユーザにはプレミアムユーザと呼ばれるタイプが存在する
  • サークルに所属するユーザの最大数はサークルのオーナーとなるユーザを含めて30名まで
  • プレミアムユーザが10名以上所属しているサークルはメンバーの最大数が50名に引き上げられる

仕様オブジェクトを使わずアプリケーションサービスに記述する悪い例

class CircleService {
  addMember(circle: Circle, user: User) {
    const premiumUsers = circle.members.filter(member => member.isPremium).length;
    const maxMembers = premiumUsers >= 10 ? 50 : 30;

    if (circle.members.length >= maxMembers) {
      throw new Error('Cannot add more members');
    }

    circle.members.push(user);
  }
}

仕様オブジェクトに切り出した良い例

class CanAddMemberSpecification {
  isSatisfiedBy(circle: Circle, user: User): boolean {
    const premiumUsers = circle.members.filter(member => member.isPremium).length;
    const maxMembers = premiumUsers >= 10 ? 50 : 30;

    return circle.members.length < maxMembers;
  }
}

class CircleService {
  private canAddMemberSpec = new CanAddMemberSpecification();

  addMember(circle: Circle, user: User) {
    if (!this.canAddMemberSpec.isSatisfiedBy(circle, user)) {
      throw new Error('Cannot add more members');
    }

    circle.members.push(user);
  }
}

複雑な処理はカプセル化され、コードの意図が明確になっています。

趣旨の見えづらいオブジェクト

オブジェクトの評価処理を安直にオブジェクト自身に実装すると、オブジェクトの趣旨はぼやけてしまう。オブジェクトが何のために存在し、何をなすのかが見えづらくなる。

評価メソッドに塗れた定義

class Circle {
  public isFull(): boolean {
    //省略
  }
  public isPopular(): boolean {
    //省略
  }
  public isPrivate(): boolean {
    //省略
  }
}

これを放置しておくとオブジェクトに対する依存は手に負えないほど増加して、変更が大変になります。外部にオブジェクトとして切り出すことで扱いやすくなることも知っておくといいでしょう。

参考

ドメイン駆動設計入門 ボトムアップでわかる!ドメイン駆動設計の基本