ページ一覧を開く

PRACTICAL GUIDE

ドメインモデルの作り方

ドメインモデルは、最初にクラスを並べて作るものではありません。具体的に起きることを確認し、そこで使う言葉、守るルール、システムが担う判断を整理しながら作ります。

業務に登場するもの、それらの関係、守るルールや判断を、開発に関わる人たちが共有できる形で表したものがドメインモデルです。

決まった一つの手順があるわけではありません。今回は、フットサルNOWの「参加者募集」を題材にドメインモデルを作ります。主催者が募集を作り、参加希望者が申し込むまでを扱います。

具体例で整理する

まず、実際に起こりそうな例を一つ作ります。誰が何をするのか、定員は何人かを具体的にすると、「申し込んだ時点で参加は決まるのか」「定員が埋まっていたらどうなるのか」など、確認すべきルールが見えてきます。

主催者が、今日20時開催、定員2人、申込期限18時の募集を作る。

player-1が17時に、その募集へ参加を申し込む。

この例から、次のことが分かります。

  • ・募集は主催者が作る
  • ・募集には開催日時、定員、申込期限がある
  • ・参加希望者は募集へ参加を申し込む

また、具体例を出したことで、募集に必要な情報や申込みのルールについて、まだ分からないことも見えてきます。例えば、次のようなことです。

  • ・どこで開催するのか
  • ・参加費はいくらか
  • ・男性と女性のどちらも参加できるのか
  • ・参加できる年齢に条件はあるか
  • ・申込期限を開催日時より後に設定できるか
  • ・主催者自身も参加を申し込めるか
  • ・申込期限を過ぎても申し込めるか
  • ・同じ募集へ二重に申し込めるか

これらの疑問を主催者に確認します。ここでは、「募集には開催場所が必要」「申込期限を過ぎたら申し込めない」「同じ募集へ二重に申し込めない」と分かったものとして進めます。

言葉の意味を揃える

業務に詳しい人(ドメインエキスパート)や開発者など、開発に関わる人たちの認識を揃えるために、会話に出てきた言葉の意味を確認します。同じ言葉でも、人によって捉え方が異なることがあるためです。

DDDでは、このように開発に関わる人たちが共通して使う言葉をユビキタス言語と呼びます。

先ほどの具体例では、「募集」「参加申込み」「申込期限」という言葉が使われていました。それぞれの意味と、コードで使う名前を整理すると、次のようになります。

募集

Recruitment

主催者が開催日時、場所、定員、申込期限を決めて参加者を募るもの。

参加申込み

RecruitmentApplication

参加希望者が、参加したい募集に対して行う申込み。

申込期限

applicationDeadline

その募集へ参加を申し込める期限。

コードで表す

整理した言葉をクラスやメソッドの名前に使い、確認したルールをコードに表します。例えば、「申込期限を過ぎたら申し込めない」「同じ人は二重に申し込めない」と分かった場合は、次のように表せます。

TypeScript

// 「参加申込み」が持つ情報を表す型
type RecruitmentApplication = {
  // 参加申込みを識別するID
  id: string

  // 申し込んだ参加希望者のID
  applicantId: string
}

// 「募集」の情報と、募集が守るルールを表すクラス
class Recruitment {
  // この募集で受け付けた参加申込み
  private applications: RecruitmentApplication[] = []

  constructor(
    // 募集を作った主催者
    readonly organizerId: string,

    // フットサルを開催する日時
    readonly eventDateTime: Date,

    // フットサルを開催する場所
    readonly location: string,

    // 参加を申し込める期限
    readonly applicationDeadline: Date,

    // 参加できる人数
    readonly capacity: number,
  ) {}

  // 参加申込みを受け付けるメソッド。申込み内容と申込時刻を受け取る
  apply(application: RecruitmentApplication, now: Date) {
    // 申込期限を過ぎたら申し込めない
    if (now > this.applicationDeadline) {
      throw new Error("申込期限を過ぎています")
    }

    // 同じ人は二重に申し込めない
    const alreadyApplied = this.applications.some(
      current => current.applicantId === application.applicantId,
    )
    if (alreadyApplied) {
      throw new Error("すでに申し込んでいます")
    }

    // ここで確認したルールを満たした参加申込みを受け付ける
    this.applications.push(application)
  }
}

この例では、話し合って整理した「募集」の情報と申込みのルールを、Recruitmentクラスで表しています。新しいルールや認識の違いが見つかったら、整理した内容とコードの両方を見直します。