アプリケーションをドメインモデルで設計する

親記事 : https://qiita.com/Regpon/items/1116679adadd8fb76f3f

https://qiita.com/Regpon/items/1116679adadd8fb76f3f

# ドメインモデルで設計する狙い

オブジェクト指向プログラミングにおいてかなり重要な内容となっているが如何せん概念的な内容となっている。ドメインモデルを設計するには幾度とない失敗の経験を重ねていき、常に改良していく精神が重要。そのための指針となる内容なので是非とも押さえておきたい。

それを踏まえてドメインモデルで設計する狙いは以下の通り。

- 業務的な判断・加工・計算のロジックを重複なく一元的に記述できる
- 業務の関心事(データ)とコードを直接対応させ、どこに何が書いてあるのかわかりやすく整理する
- 業務のルールの変更や追加の時に、変更の影響を狭い範囲に閉じ込める

# ドメインモデルの設計の難しさ

ドメインモデルの設計は手続き型(スクリプト型)のプログラミングと比べて設計がむずかしいとされる。その理由として、業務について深い理解が必要だからだ。

ただこの点については避けては通れない。
特に自分が新入りの時は以下の手順で理解を深めていく。

- マニュアルや利用者ガイド、Wikiなど(あれば)をよく読む
- その業務の一般的な知識を書籍などで勉強する
- 業務で利用しているデータは何があるのか画面やファイルなどを調べる
- 業務の経験者や詳しい開発者と会話する

## 業務知識がある程度ついたら設計してみる

ある程度業務の知識がついて、その業務の用語を使って整理ができる段階に来たらドメインモデルの設計に着手してみる。

その際、以下の点に注意する。

- 業務に使っている用語をクラス名やパッケージ名にする
- データモデルではなくオブジェクトモデルにする
- いきなり全体を設計せずに重要部分にフォーカスして設計し始める
- ドメインオブジェクトを機能の一部として設計しない

用語を統一することで、洗練されたドメインオブジェクトは仕様書にもなるレベルでその業務アプリケーションの理解の手助けにもなる。
また、データモデルではなくオブジェクトモデルにすることでロジックの重複をなくすことができる。

### ボトムアップのアプローチで設計に着手する

全体を設計に着手しようとすると網羅性や整合性を考慮しつつ設計することは至難の業。そのため全体を俯瞰した後で、重要な部分だけを切り出し、その部分の設計から着手し、ある程度進んだらまた全体を俯瞰してみて、共通化されそうなクラスやパッケージはゆくゆくはそうなるであろうことを視野に入れながらクラス図やパッケージ図に起こしてみる。

全体として間違った方向に進まないように、全体を俯瞰する手助けとして以下を先に大まかに作っておく

- パッケージ図
- 業務フロー図

その後、その中でも重要な部分を切り出して設計に着手してみる。

全体の中の重要な部分というのはそのアプリケーションにおいて、中核となるようなドメインオブジェクトが集まりがちなので、まずはそこから整理して、部分部分の集合として全体を組み上げていく手法を取ると、うまくいきやすい。

### 独立した部品を組み合わせて機能を実現する

機能とはその業務で行われる手順や条件、判断などを行なって行った結果となるものである。それを実装するのはドメインオブジェクトを使って実装した**アプリケーション層**の役割である。

あくまでドメインオブジェクトの役割は業務で扱うデータとそれを使った判断・加工・計算に関するロジックを整理することである。

つまりドメインオブジェクトをドメインモデルに追加したとしても、独立したものなので既存のドメインオブジェクトの修正が必要になることはなく、それ単体で動作しテストが行える疎結合な集合になる。

ドメインオブジェクトに機能を持たせてしまうと、他のドメインオブジェクトを追加したり、削除したり、ロジックを修正すると他のドメインオブジェクトにも修正範囲が及んだり、他のドメインオブジェクトのロジックに依存したりしてしまい、密結合になってしまう。

そうならないように単体で動作確認ができる独立性の高い部品として開発できる設計にすることが重要である。

# ドメインオブジェクトの見つけ方

業務の関心ごとを分類してみる

| 分類 | 例 |
|:-:|:-:|
| ヒト | 顧客、企業、ユーザーなど |
| モノ | 商品、サービス、場所など |
| コト | 注文、予約、キャンセルなど |

この中でも**コト**に注目すると全体の関係を整理しやすくなる。

なぜなら、コトはヒトがモノをどうにかするときに発生するものであり、時間軸に沿って明確な前後関係を持つものであるため、登場する業務の関心ごとの中心的存在のためである。

コトに注目することで、整理する糸口が見える。

例えば、注文というコトに関しては

| 分類 | 例 |
|--|--|
| ヒト | ユーザー |
| ヒト | 店舗 |
| モノ | 商品 |
| モノ | 金 |
| コト | 予約 |

など様々な業務的関心ごとが絡み合っている。
これを整理することで、有用なドメインモデルができ、次に予約やキャンセルなどを設計するときの手助けにもなってれる。

## ドメインオブジェクトの設計の基本パターン

ドメインモデルで設計してもアプリケーション層でドメインモデルを使用しようとしたら、トランザクションスクリプトになってしまうことが多々ある。

ドメインオブジェクトが使いにくいのに放置したり、アプリケーション層に条件分岐処理を書いた方が手っ取り早かったりするからロジックを書いてしまったりするパターンだ。

そうならないように、以下の基本パターンを**必ず**抑える。

| ドメインオブジェクト | 設計パターン |
|--|--|
| 値オブジェクト | 数値、日付、文字列をラッピングしてそれを使うロジックを持つ |
| コレクションオブジェクト | 配列やコレクションについて、値オブジェクト同様の整理をする |
| 区分オブジェクト | 区分の定義と区分ごとのロジックを整理する |
| 列挙型の集合操作 | 状態遷移のルールなどを列挙型の集合として整理 |

上記についての詳細は以下の記事を参照

https://qiita.com/Regpon/items/72377746abab0e75760a

https://qiita.com/Regpon/items/730ffe5ea06ce5667659

この４つのドメインオブジェクトを組み合わせて、以下の４つの関心ごとのパターンに分類して整理を行う。

| 関心ごとのパターン | 業務ロジックの内容 |
|--|--|--|
| 口座(Account)パターン | 現在の値(残高)を表現し、妥当性を管理する |
| 期日(DueDate)パターン |  期日と期日前、期限切れなどの判断を表現する |
| 方針(Policy)パターン | 様々なルールが複合する、複雑な業務ロジックを表現する |
| 状態(State)パターン | 状態と、状態遷移のできる・できないを表現する |

### 口座パターン

- 関心の対象を口座として用意
- 数値の増減の予定を記録
- 数値の増減の実績を記録
- 現在の口座の残高を算出

### 期日(DueDate)パターン

- 関心ごとの期限の設定
- 関心ごとが期限までに実行されることを監視
- 期限切れになりそうなときのアラート
- 期限切れのアラート

```Java
class DueDate {
  LocalDate dueDate;

  // 期限切れかどうか
  boolean isExpired() {
  }

  // その日は期限切れか
  boolean isExpiredOn() {
  }

  // 期限切れまでの日数
  int remainingDays() {
  }

  // 期限切れの警告レベル
  AlertLevel alertForExpired() {
  }
}
```

### 方針(Policy)パターン

- 複合したルールをコレクションオブジェクトで管理
- 一つでも適合してるかどうか（一つも適合していないかどうか）
- 全て適合しているかどうか

```Java
class Policy {
  Set<Rule> rules;

  // いずれかのルールに適合(falseなら全てのルールに適合しない)
  boolean matchSomeRule(Value value) {
  }

  // 全てのルールに適合
  boolean matchAllRule(Value value) {
  }

  void addRule(Rule rule) {
    rules.add(rule);
  }

  interface Rule {
    boolean ok(Value value);

    default boolean ng(Value value) {
      return ! ok(value);
    }
  }
}

```

### 状態(State)パターン

- 状態を列挙する
- ある状態からある状態へ遷移できるかを判定する
- ある状態からどの状態へ遷移できるかを提示

```Java
enum State {
  準備中,
  営業中,
  閉店,
  定休日
}

class StateTransitions {
  Map<State, Set<State>> allowed;

  {
    allowed = new HashMap<>();

    allowed.put(準備中, EnumSet.of(営業中));
    allowed.put(営業中, EnumSet.of(閉店, 定休日));
    allowed.put(閉店, EnumSet.of(準備中));
    allowed.put(定休日, EnumSet.of(準備中));
  }

  // 状態遷移可能かどうか
  boolean canTransit(State from, State to) {
    Set<State> allowedStates = allowed.get(from);
    return allowedStates.contains(to);
  }
}
```

関心ごとのを小さな単位に分けて、その狭い範囲で必要なデータとロジックを集め、このような基本設計パターンを駆使し、ドメインオブジェクトを探し、当てはめて、分類することを繰り返し行い、小さな独立性の高いドメインオブジェクトを揃えていく活動を行うことが、ドメインモデルの開発となる。

そうしていくと業務ロジックの大半が、アプリケーション層ではなく、ドメインモデルに集約されていくことがわかる。


# まとめ

- ドメインモデルは業務ロジックをオブジェクト指向で整理する技法
- 業務知識を深める努力は怠らない
- 業務の関心はヒト・モノ・コト
- コトの整理を軸にする
- 業務の言葉を使う
- 設計の基本パターンを覚えておく
- ドメインオブジェクトに機能は実装しない（機能はアプリケーション層で）

### 文献

本記事は [現場で役立つシステム設計の原則 変更を楽で安全にするオブジェクト指向の実践技法 　著：増田 亨](https://www.amazon.co.jp/%E7%8F%BE%E5%A0%B4%E3%81%A7%E5%BD%B9%E7%AB%8B%E3%81%A4%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E8%A8%AD%E8%A8%88%E3%81%AE%E5%8E%9F%E5%89%87-%E5%A4%89%E6%9B%B4%E3%82%92%E6%A5%BD%E3%81%A7%E5%AE%89%E5%85%A8%E3%81%AB%E3%81%99%E3%82%8B%E3%82%AA%E3%83%96%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E6%8C%87%E5%90%91%E3%81%AE%E5%AE%9F%E8%B7%B5%E6%8A%80%E6%B3%95-%E5%A2%97%E7%94%B0-%E4%BA%A8/dp/477419087X) を読んで(なるべく)自分の言葉でまとめたものです。

興味を持っていただけたらこちらの本を読んでみていただけたらと思います。(勝手に宣伝)


# 追記

https://twitter.com/masuda220/status/1411127201500524546

著者の増田氏にまで読んでいただけて感激致しました。
このような良書に出会えたこと、著者の増田さん、この本を紹介してくださった @jimpei さんに感謝いたします。