読者です 読者をやめる 読者になる 読者になる

実践ドメイン駆動設計 5.1 ~ 5.2 メモ

開発 DDD

実践ドメイン駆動設計 (Object Oriented Selection)

実践ドメイン駆動設計 (Object Oriented Selection)

実践ドメイン駆動設計

実践ドメイン駆動設計

  • ドメイン駆動設計ではドメインよりデータを気にすると失敗する
  • データ・モデルがオブジェクトにそのまま反映されてしまう
  • DDDのエンティティがやるべきことは、それだけではない

5.1 なぜエンティティを使うのか

  • ドメインの概念をエンティティとして設計するのは、その同一性を気にかけるとき
  • システム内の他のオブジェクトとの区別が必須の制約となっているとき
  • エンティティは一意なものであり、長期にわたって変わり続けることができる
    • 一意な識別子があって変化する => 値オブジェクトと異なる点
  • エンティティが適切でない場合もある
    • 値としてモデリングすべきものをエンティティとする
    • DDDが業務に適切で無い場合もある => CRUDがあうのであればそっちのほうがいい
    • CRUDで十分ならGroovyやGrailsRoRでなどを使って時間と資金を節約する
    • DDDを使うべき場所でCRUD使うと後でなくことになる
      • 複雑性がますごとにツールの選択を間違えたことにる制約に直面する
    • CRUDシステムはデータをとらえるだけであり、洗練された業務モデルを作ることはできない
  • DDDが投資に見合うか、考える必要がある
  • あるオブジェクトが同一性によって識別されるのであれば、モデルでこのオブジェクトを定義する際にはその同一性を第一とすること
  • エンティティを適切に重視する方法
  • エンティティの設計に関する様々なテクニック

5.2 一意な識別子

  • エンティティの設計の初期段階では、そのエンティティを一意に特定するためにポイントとなる属性や振る舞いだけに注目する
    • エンティティの問い合わせのときにも使える
  • 主たる属性や振る舞いが定まるまでは、その他の属性や振る舞いについては意図的に無視する
    • エンティティオブジェクトの定義を最も本質的な特徴まで削ぎ落とすこと
    • 本質的な特徴 => 識別するものや検索し突き合わせるのに通常しようするもの
  • 識別子の実装に関して、様々な選択肢を持っておくこと、長期にわたって一意性を保証できることが重要
  • エンティティの一意な識別子 == 検索やマッチングのキー ではない => 使いづらいこともある
  • キーとして使えるかどうかに大きく関わってくるのが人間にとっての可読性
  • 一意な識別子の例
    • 一意でない => Personエンティティの名前 => 同姓同名
    • 一意 => 納税者IDをキーにして企業を検索 => Companyエンティティの一意な識別子
  • 値オブジェクトは一意な識別子の受け皿として使える
    • 不変なので、識別子が変わらないことを保証できる
    • 識別子に固有な振る舞いも一元管理できる
  • 一般的な作成方針
    • ユーザが入力 => アプリケーション側で一意性を保証する
      • シンプルしかし込み入ったことにもなりえる
      • よくできた識別子がユーザ次第になる => 一意だけど間違った識別子を作るかも
      • 識別子は変更不能なので、ユーザで変更できてはいけない
        • 修正できるようにしておくメリットもある => 一意な識別子としてタイトルを採用した場合、タイトルを間違えたら?
        • ユーザに一意な識別子を設定させるときの、うっかりミスを防ぐ方法を見当する必要がある
        • 作成と承認に人手間かけた識別子がビジネス全体に長年に渡って使われるようであれば、多少手間はかけていい
      • マッチングには使うけど、一意な識別子には使用しないという判断もある
    • アプリケーション内部で生成 => 一意性を保証できるアルゴリズムを使う
      • 信頼性の高い方法はいろいろある
      • 分散環境上は注意が必要
      • UUID, GUID
        • 処理を実行するノード上の時刻(ミリ秒単位)
        • IPアドレス
        • Javaだと
          • オブジェクトの識別子
          • 同一のジェネレータで生成した乱数
        • これらの結果を一つの文字列にまとめたものを識別子とする(これはひとつの例)
        • 128bitの一意な文字列 => 32バイト(or 36バイト)の16進数の文字列で表すことが多い
          • 32, 36の違いは間にハイフンがはいるかどうか
          • 人が読めるもんではない
        • JavaにはUUIDを生成するクラスがあるのでそれを使う
          • 他のプラットフォームでも多分あるよね
        • サーバの再起動でUUIDのキャッシュが失われても、識別子に穴が空くことはない => ランダムで作られているため
      • メモリのオーバーヘッド => DBが生成する8バイトの識別子を使えばまし
      • UUIDそのままはユーザに嬉しくない => 他の情報と合わせてUUIDの一部を表示する
      • 文字列そのままで扱うのは好ましくない => 値オブジェクトで扱うといい
      • クライアント側で識別子を知っておく必要はない => 集約ルートで識別子に仕込んだ情報をとれるようにしておく
      • 永続化ストアにも生成できるものがある => Riak, MongoDB
      • リポジトリに識別子のファクトリを任せることが多い
    • アプリケーションがデータベースなどの永続化ストアに任せる
      • ドメインモデルの知識がデータベースに流出してしまう
      • DBのシーケンスなどを使えば一意であることが保証
      • 弱点としてパフォーマンス => 問い合わせは時間がかかる、キャッシュなどが必要(リポジトリでやる)
        • サーバノードの再起動で未使用の値を失うことが考えられる
        • シーケンス値に空きが許容できない場合などキャッシュが現実的でないこともある
        • 識別子の生成を後回しにすると、これらの問題は起きない => 生成のタイミングは考える必要がある
      • 識別子の早期生成 => エンティティを永続化する前に生成と割り当てを行う
      • 識別子の遅延征性 => エンティティを永続化するときに生成と割り当てを行う
      • Oracleのシーケンス、MySQLの自動インクリメントによるシーケンスの模倣
      • Hibernateのシーケンス => 遅延生成のみ
    • 別の境界づけられたコンテキストが、すでに一意な識別子を定めており、ユーザがその識別子を入力する
      • 別の境界づけられたコンテキストに識別子を割り当てさせるには、識別子の検索とマッチング、そして割り当てといった機能をそこに組み込む必要がある
      • 性格なマッチングが最重要、口座番号やユーザ名、メールアドレスなどの情報を指定子て望む結果を特定する必要がある
      • あいまいな入力にもとづいた複数の結果を提示して、その中からユーザに選択してもらうということも多い
      • 例として、ユーザ名のあいまい検索から外部の境界づけられたコンテキストAPIにアクセスする
      • 参照している外部のオブジェクトでローカルのエンティティに関わる変更があれば、イベント駆動アーキテクチャドメインイベントを組み合わせれば対応できる
        • ドメインイベントを購読させ、関連するイベント通知を受け取ったらローカルシステム側でもエンティティを更新、外部の変更を反映
        • 逆にローカルの変更を外部にプッシュする必要もあるかもしれない
      • この手法はどうしても必要な場合にだけ使うようにしよう
    • 識別子生成のタイミング
      • 早期生成 => オブジェクト生成時
      • 遅延生成 => 永続化時
      • シンプルなケース
      • あたらしいエンティティを永続化するまで識別子の割り当てを先送りしても構わない
        • ドメインイベントを受け取った段階では識別子が存在せずエンティティをリポジトリに登録できない*
        • ドメインイベントを正しく初期化するには、識別子が早期生成されている必要がある
        • あたらしいエンティティをArrayなどに追加する必要があるのに、識別子が割り当てられてないため、nullなど同じ識別子になってしまう
        • 設計を変更して早期生成に対応する
        • equals()メソッドリファクタリングして、ドメインの識別子以外で同一性を判断する
    • 代理識別子
      • ORMツールの中にはHibernateのように識別子を自分たちの流儀で管理したがるものもある
      • ドメイン側で別の形式の識別子を使っているようだと、好ましくない衝突が起こりうる
      • ドメインの流儀にしたがった識別子とHibernate用の識別子2つ用意 => Hibernate用の識別子を代理識別子と呼ぶ
      • 簡単に作れる、エンティティに一つ属性を追加して代理識別子用の型を持たせる => 一般的にはlong, intなど
        • ドメイン固有の識別子と関係なくORMのためだけに用意したもの
        • 外部からは見えないようにしておく
        • レイヤスーパータイプがセーフガードにつかえる
          • 代理主キーをクライアントから隠蔽、例ではprotectedで見えないようにしておく
            • Hibernateメソッドやフィールドの可視性は何でも問題ない
            • 追加すると楽観的並行制御に対応できる => 10章で説明
        • ドメインの識別子は必ずしもDBの主キーでなくてもいい
        • データベースの代理主キーはデータモデル全体を通して他のテーブルの外部キーとしてもつかえる
          • 参照整合性
    • 識別子の普遍性
      • 一位な識別子は一度定めたら変更してはいけない
        • 変更を防ぐ簡単な対策 => 識別子のセッターをクライアントからみえなくする、セッターの中で対策しておく(例外あげるなど)
        • 永続されたオブジェクトを再構築するときも邪魔にはならない => オブジェクトを最初につくったときは識別子がnullになっている、初期化時にセッターが呼ばれる
        • ドメイン識別子ってなんぞや? => エンティティの一位な識別子について話していたはず
          • 別の境界づけられたコンテキストが識別子を割り当てる場合のこと? つまりそのドメイン内での識別子とその別の境界づけられたコンテキスト内における識別子の違い?