/note/tech

MM2025-03 技術講演2:生成AI時代のドメインモデリング ― OOPとFPを超えて

要約:

■ 1. ドメインモデルの定義と階層

  • ドメインモデルの基本概念:
    • 現実世界の複雑さを抽象化して表したものである
    • 枝葉となる環境や実装技術に依存した部分を切り落として業務の本質的なコアの部分を抽象化したものである
  • ドメインモデルの3つのレベル:
    • データベースやデータモデリングに概念モデル、論理モデル、物理モデルというレベルがあるように、ドメインモデルにも概念モデル、仕様モデル、実装モデルというレベルがある
    • この区別は2000年頃の論文「Modeling with sense」(マーティン・ファウラーのサイトに掲載)で提唱されている
  • 現状の課題:
    • 実際にはこの仕様モデルと実装モデルがあまりはっきりした区別をされずに今まで来ている
    • ドメインモデルは抽象概念なので仕様レベルでの話が重要である
    • しかしオブジェクト指向プログラミングのエッセンスなど実装の都合や技術都合に引きずられてどう実装するかの話で片付けられることが多い
    • DDDも実装論ばかりされているように見える

■ 2. 仕様モデルの定義

  • 仕様モデルの役割:
    • ソフトウェアシステムが何をしなければいけないか、どのような情報を保持しなければならないか、どのような振る舞いを示さなければならないかを定義する
    • 端的に言えばデータと振る舞いを定義する
  • 仕様ドメインモデルの構成:
    • 仕様ドメインモデルは工学的な抽象レベルで記述された業務のデータと振る舞いで構成される
    • 主にはデータと振る舞いで構成されるものである

■ 3. オブジェクト指向と関数型のドメインモデリング

  • オブジェクト指向のドメインモデリング:
    • データと振る舞いの関係性をマッピングしていく
    • 行列を並び替えながらうまいことクラスだったっぽいものを見つけてクラスとして整理していく
    • クラスターの決め方は自由度が高い
    • 人によって設計できる人と設計できない人のばらつきが出る
  • 関数型のドメインモデリング:
    • 基本的には同じであるがそれぞれの振る舞いができる限り全域性を満たすようにする
    • 対象とその全ての入力データに関して定義できない振る舞いは実装しない、別の振る舞いにするかデータ型を分ける
    • 例外を発生させないように振る舞いやデータを調整していく
    • 全域性を保つことで組み合わせ可能性(コンポーザビリティ)が高まる
  • 本質的な違い:
    • 仕様ドメインモデルのレベルでは本質的な違いはない
    • オブジェクト指向の方が自由度が高い
    • 関数型モデリングはさらに全域性を保つなどの制約が加わるためより厳密になる

■ 4. 仕様モデルの記述方法

  • 従来ツールの問題:
    • 従来のモデリングツールでは仕様のモデルを書いているのか実装のモデルを書いているのかが曖昧である
    • モデル駆動開発(MDA)で仕様書いたらそこからコードがジェネレートされるという夢の世界の影響があった
    • 実装モデルに近い設計要素が入ってしまっている
  • 記述のアプローチ:
    • 従来のツールは一旦置いといて純粋にデータと振る舞いだけを書き出すことにフォーカスする
    • DSL的に簡単なシンタックスで記述する
    • データの定義はデータ型で名前をつけて何から構成されるかを書く(代数データ型と同じ)
    • ANDで直積とORで直和を使ってデータと振る舞いを表していく
  • データ定義の例:
    • データはANDで複数の要素を組み合わせる
    • ORで複数の選択肢から1つを選ぶ(例:連絡先はメールアドレスか電話番号か住所)
    • 振る舞いは基本的に入力から出力への変換である
    • 入力のデータとそれが出力のデータとして何が出てくるのかを定義していく

■ 5. 抽象化の重要性

  • 振る舞い抽象:
    • 実装の詳細を知らなくてもその振る舞いが提供する機能を利用できることである
    • 実装の用語で言えばインターフェースに対して実装していくことと同義である
  • データ抽象:
    • 対象のデータの詳細を知らなくてもそのデータに対しての振る舞いを実行できることである
  • 仕様モデルの要件:
    • 仕様モデルはこれらの振る舞い抽象とデータ抽象でなくてはならない
    • 振る舞いがない場合は実装の詳細を仕様として書いてしまいがちである
    • SI現場で見られる詳細設計書のように実装レベルの処理順を書いてしまうと、中身の実装を全部知らないとロジックを安全に呼び出せなくなる
    • プログラム修正においても詳細全部を知らないと何も修正できなくなる

■ 6. 仕様記述の要素

  • バーバラ・リスコフの手続き抽象の仕様構成要素:
    • 入力と出力(データ抽象が入力と出力になる)
    • Requires(事前条件):入力が受け付けられないものがある場合にその条件を書く
    • Modifies(変更条件):中で書き換えられる入力があったらそれについても書く
    • Effects(効果):入力についての書ききれない振る舞いの詳細を書く
  • 良い仕様の3つの条件:
    • Restrictiveness:使う側にとって受け入れられない実装が全て排除されていなければならない
    • Generality:受け入れられる実装を仕様として排除してはならない(実装の選択肢を勝手に削らない)
    • Clarity:仕様の読み手がその意味を十分理解できること

■ 7. 全域性の追求

  • 全域性の定義:
    • 取りうる入力の全てに対して対応する出力が必ず存在することである
  • 全域性の実現方法:
    • 振る舞いが受け入れられない入力を仕様として示すのではなく、型として調整する
    • 例:割り算で除数が0ではないことをRequiresとして仕様に書くのではなく、NonZeroInteger(0を除いた整数)という型を定義する
    • これにより自然言語として書かなければいけない仕様が少なくなっていく
  • ステータスの問題:
    • ステータスは全域性を損なうサインである
    • 注文ステータスがあるということは、そのステータスによって適用できる振る舞いが違うことを意味する
    • ステータスではなくデータ抽象で表すことで適用できる振る舞いの条件が明示される
    • 仕様レベルではステータスがなくなるようにデータ抽象を作っていくことが重要である

■ 8. 実装の選択肢を残す

  • Generalityの実現:
    • 良い仕様は実装者が要求を満たす上で最も最適な実装が選べるように最大の選択肢を残してあげなければならない
    • SI現場では実装の話まで仕様にたくさん書いてしまいがちである(例:ループ処理、データベースアクセスの方法など)
    • これは性能問題に直結する
  • データ抽象の適切な設計:
    • テーブル駆動で設計するとデータ抽象が適切に設計されないことがある
    • 例:受注生産の場合だけサプライヤーIDが入るという仕様で、このフィールドがNULLかどうかで受注生産商品を判定するロジックが各所に埋め込まれる
    • これは抽象が漏れ出している(Leaky Abstraction)状態である
    • 対策はデータ抽象をちゃんと分けること(通常商品と受注生産商品を別のデータ型にする)

■ 9. Clarityの向上

  • スタンプ結合の廃止:
    • 入力のデータの全部をその振る舞いで使うわけではないことがよくある
    • 大きなデータを渡しがちだがそうするとテストしにくくなる
    • 使ってないデータに依存した振る舞いがあるので修正時の影響調査が必要になり修正コストが高くなる
    • データ抽象の粒度が大きいと何をやっているかが分からなくなる
  • ビジネス不変条件の保証:
    • 仕様としてのデータ抽象はビジネス不変条件をちゃんと保証するものでなければならない
    • 例:チェックイン日はチェックアウト日よりも前の日付でなければならない
    • これを型として明示しておくとより不変条件が明らかになる
    • バラバラのフィールドではなく「宿泊」というデータを別に作り、それを「日付の範囲」として定義する
  • 単一責務の原則:
    • 振る舞い抽象は単一責務でなければならない
    • 複数の責務が混ざっている振る舞いは正確に名前に反映させることで発見できる
    • 名前に「〜の場合は」「〜をして〜をします」「そして〜をします」(英語だとAND/OR)が入る場合は複数の責務が混ざっている
    • 正直正確で完全な名前を一回つけてみて、そこにAND/ORが繋がるようだったらそこで分割する

■ 10. 仕様モデルから実装モデルへ

  • 良い仕様モデルの条件:
    • スタンプ結合を除去する
    • 振る舞いが全域性を持つようにする
    • 振る舞いを単一責務にする
    • これらは疎結合や高凝集を目指す良い設計と全く同じ話である
    • これを仕様レベルでちゃんと保とうということである
  • 実装モデルへの変換:
    • 良い仕様ドメインモデルが書ければ実装言語に合わせて実装ドメインモデルにエンコーディングすればよい
    • データと振る舞いをクラスごとに整理するのがオブジェクト指向言語向けのドメインモデルである
    • データを型に、振る舞いを関数にエンコーディングするのが関数型のドメインモデルである

■ 11. 開発アプローチの転換

  • アウトサイドイン開発の問題:
    • 現状のSI現場で多い開発スタイルはアウトサイドイン開発である
    • 業務コアの概念が分からないまま画面設計書を作り、テーブル設計を作り、そこで開発をスタートする
    • 外側(実装の詳細)から詰めていって中(業務コア)に向かって開発を進めていく
    • 仕様モデルを書く隙間がこのプロセスにはない
    • 結果として配線プログラミング(画面項目とデータベースのマッピング線だけ設計する)になる
    • システムの理解容易性と変更性が低下しやすい
  • インサイドアウト開発への転換:
    • 仕様モデルをちゃんと書いて、その仕様モデルに沿ったプレゼンテーションのモデルや永続化のモデルを書いて画面と繋げていく
    • インサイドアウトの開発にすべきである
    • 仕様モデルがちゃんと書けていればこの外側に向かってコードを書く部分は生成AIがうまいことやってくれる

■ 12. 仕様モデル駆動設計

  • 従来の仕様駆動開発(SDD)の問題:
    • 仕様として何が必要かという定義がない
    • 従来の開発延長線上に仕様駆動開発をやってみようとなるとアウトサイドインになる
    • AIが勝手に想像して変なコードを出してしまう
  • 仕様モデル駆動設計の提案:
    • ちゃんと仕様モデルを先に書いてインサイドアウトで開発すると、当然ながらそのモデルが先に作られている状態である
    • AIはそれをちゃんと理解した状態で画面やDBにマッピングしてくれるので割と綺麗に作ってくれる
    • これを仕様モデル駆動設計と呼んで広めていく