🦁

ドメインモデル貧血症を引き起こさないためになるべくクラスの属性をprivateにしている話

2021/06/28に公開

ドメインモデル貧血症

すでに過去の偉人が様々な解説記事を出してくれているので今更感はあるけど、最近自分が知らず知らずのうちにドメインモデル貧血症を引き起こしているエンティティを作成してしまっていたので自戒の意味をこめて書き残します。

そもそもドメインモデル貧血症とは本来ドメイン知識を保有するはずのドメインモデルがゲッター・セッターのみのDTOと化してしまう症状(と自分は理解しています)。

例えば

/**
  * 記事を表現するエンティティ
  */
class Article extends Entity {
  public readonly id: number;
  public title: ArticleTitle;
  public content: ArticleContent;
  public author: Author;

  constructor(
    id: number,
    title: string,
    content: string,
    author: Author
  ) {
    this.id = id;
    this.title = new ArticleTitle(title);
    this.content = new ArticleContent(content);
   this.author = author;
  }
}

/**
  * 執筆者を表現するエンティティ
  */
class Author extends Entity {
  public readonly id: number;
  private readonly _name: AuthorName;
  
  // 自身の過去に執筆した記事
  private readonly _pastWorks: Article[]; 
  
  constructor(
    id: number,
    name: string,
    pastWorks: Article[]
  ) {
    this.id = id;
    this._name = new AuthorName(name);
    this._pastWorks = pastWorks;
  }

  get name(): AuthorName {
    return this._name;
  }

  get pastWorks(): Article[] {
    return this._pastWorks;
  }
}

とかまあ、こんな感じのエンティティがあったとします。著者は複数の記事を執筆できる、みたいなイメージです。で、問題はArticleです。全ての属性がパブリックで書き換え可能になっています。もしこの先

  • 著者が自分の記事のタイトルを変更するメソッドが必要だな〜

と考えた時、あるエンジニアはこう実装するかもしれません。

class Author extends Entity {
  // ~ 省略 ~

  public changeArticleTitle(newTitle: string, article: Article): void {
    article.title = new ArticleTitle(newTitle);
  }
}

こんな感じでArticle以外のエンティティにArticleの属性を変更するメソッドを書くことが出来てしまいます。このままではArticleは自身が持つ属性のゲッター・セッターのみのモデルになりえてしまいます。典型的なドメインモデル貧血症です。これは良くない。

ではどうするか?そもそも外部のクラスに自身のゲッター・セッターが公開されているのが根本的な原因のようです。なら隠してしまえばいい。

/**
  * 記事を表現するエンティティ
  */
class Article extends Entity {
  public readonly id: number;
  private title: ArticleTitle;
  private content: ArticleContent;
  private author: Author;

  constructor(
    id: number,
    title: string,
    content: string,
    author: Author
  ) {
    this.id = id;
    this.title = new ArticleTitle(title);
    this.content = new ArticleContent(content);
   this.author = author;
  }
}

これなら先程のようにAuthorエンティティにArticleのtitleを変更するようなメソッドを直接実装することは出来ません。Articleのtitleを変更するメソッドを実装したければArticleエンティティ内にtitleを変更するメソッドを実装します。

class Article extends Entity {
  // ~ 省略 ~

  public changeTitle(newTitle: string): void {
    this.title = new ArticleTitle(newTitle);
  }
}

Authorエンティティに実装されていたメソッドがArticleエンティティに移動し、記事はタイトルを変更できるというドメイン知識を表現できるようになりました。

ここで

  • 記事のタイトルを変更するのは著者なんだからAuthorエンティティにあるのが正しいんじゃないの?

と思った人も多いと思います。

ドメインオブジェクトの責務について

その疑問についてはこちらの記事をご覧ください。自分の理解ではありますが、簡単に言うとこのケースでは著者はアクター、つまりシステムの外部の登場概念であって、ドメインモデルではないと言うことです。あくまで実装者が記事はタイトルを変更できるドメインモデルであるように設計・実装すべきだそうです。

簡単ではありますが以上です。間違っている点などございましたらコメントにて指摘して頂けると幸いです。

Discussion