blogこの広告は、90日以上更新していないブログに表示しています。『実践ドメイン駆動設計』を読んでまとめなおす。前に話題になっていたDDDは『実践ドメイン駆動設計』で述べられている、より最近の知見に対するものが多い感じ(ヘキサゴナルアーキテクチャ等)。そんなこともわかっていなかったのでエヴァンス本を読んでよかった。第1部ドメイン駆動設計とは、ビジネスで本当に役に立つソフトウェアを解決するためのパターンランゲージ。その基本は開発者とドメインエキスパートが同じ言葉(ユビキタス言語)で会話し、その言葉でモデリングすること。モデルと実装は完全に一致させること。開発者はドメインに関係ない技術的な問題に取り組むことを好みがちだが、設計も高度な能力が要求されるやりがいのある仕事である。ドメインを隔離するモデル要素を見た際に一つの体系として理解できるようにすべき(関心の分離)。(上のレイヤから順に列挙)ユーザインタフェース層(プレゼンテーション層)ユーザに情報を表示し、ユーザのコマンドを解釈する責務を負うアプリケーション層と厳密に区別しないこともあるアプリケーション層ソフトウェアが行うことになっている仕事を定義し、ドメインオブジェクトが問題を解決するように導くビジネスルールや知識を含まず、やるべき作業を調整するだけ。資金振替2つの口座番号と金額を入力するか選択した後に送金を開始するビジネスルール「全ての振り込みには、それに対応する引き落としがある」ビジネスルールに関する責務を負っているのはドメイン層。アプリケーション層ではない。インフラストラクチャ層によるメッセージ送信インタフェースの提供。モデルとモデルに直接関係する設計上のすべての要素が現れる場所。洗練とは程遠い設計アプローチ。利口なUIが適用されない状況。利口なUIの欠点アプリケーションの統合は困難で、データベースを経由させるしかない振る舞いが再利用されない。ビジネスの問題が抽象化されないビジネスルールは適用先の操作それぞれで複製される抽象化されないためリファクタリングの選択肢が制限される複雑さによってすぐに覆いつくされてしまうので(??)単純な機能の追加しかできない利口なUIを適切な状況下で適用すれば、他のアプローチで必要なオーバーヘッドを避けられるやり遂げられない高度な設計アプローチに手を出すのはやりがちな間違いプロジェクトに対して過剰に複雑なインフラストラクチャを構築するのもやりがちな間違い利口なUIを適用するようなアプリケーションにとっては、Javaなどの柔軟な言語は過剰。蒸留ドメインについての本質的な概念を他の詳細から解放これらについて議論するのはまだ先。次はドメインモデルと実装をともに進化させるための土台を見ていく。ソフトウェアで表現されたモデルこの章ではモデルを構成する詳細な個々の要素を扱う。具体的には以下についてドメイン駆動設計の文脈でとらえ、議論する。Objects))多くの物事が定義されるのは同一性によってであり、属性によってではない。人は月日がたてばあらゆる属性が変化するが同一性がある。エンティティの識別や検索・突き合わせに使用されるもの(ID、名前など)本質的な振る舞い・振る舞いが必要とする属性については他のオブジェクトに移動できないかも調べること。Objects)「何」であるかが関心あることで、「誰」であるかや「どれ」であるかは問われないような設計要素を表現するオブジェクトを値オブジェクトと呼ぶ色、文字列、数など住所は?使われ方による。同じ住所に複数人が住んでいることを想定する。通販ではその複数人が別々に注文をしても、その複数人が同じ場所に住んでいることを認識する必要はなく、ただ指定された住所に送れば良い。このような場合、住所は値オブジェクト。他方、電力サービスの目的地として住所が使われる場合は、同じ住所に住む複数人が別々に依頼を出した場合にその複数人が同じ場所に住んでいることを認識する必要がある。このような場合、住所はエンティティ。後者の場合でも例えば住居エンティティが存在し、住居エンティティに関連づける形で住所が存在する場合には、住所は値オブジェクトになりうる。属性しか関心の対象にならない要素は値オブジェクトにすることなんでもエンティティにしてしまうとシステムが複雑になるし、どのオブジェクトも似たような見た目になりモデルにおいて何が重要かわからなくなる値オブジェクトには属性の意味、属性に関係した機能を持たせ、同一性は与えない値オブジェクトは不変なものとして扱う値オブジェクトを設計する不変にする多数のインスタンスを作成するより1つのインスタンスを共有することでパフォーマンスを改善できる場合がある。逆に分散システムなどではインスタンスを共有することで通信が増えパフォーマンスが悪くなるかもしれない。値を共有することがあまりない、あるいは技術的理由から値の共有をしない場合エンティティであることを宣言して同一性の演算をプログラミング言語やツールの力で強制することは(現在のところ)できない(値オブジェクトで実現したい)属性やオブジェクトの不変性は言語や環境によって宣言できたりできなかったりするモデルで行う区別の多くは実装において明示的に宣言することはできない。これらの区別を言語が直接サポートしないからといって区別しなくて良いというわけではないし、実装を以って暗黙的に区別することは可能。実装によって暗示されているに過ぎないルールを守るためには規律が必要。命名規則や多くの議論など。ドメインサービス重要なビジネスルールを組み込んでいる「資金振替」は意味のある銀行用語サービス自体は2つの口座オブジェクトに処理のほとんどを依頼するかたちになるが、口座オブジェクトに「振替」の操作を入れてしまうと扱いにくい。振替は2つの口座と何らかのルールを伴うため。通知送信サービスアプリケーションの指示に従って、電子メールや手紙、その他のメッセージを送信するサービスによるドメイン層のインタフェース粒度の制御サービスはドメイン層のインタフェースの粒度を制御する手段としても有用ドメインオブジェクトの粒度が細かすぎると、ドメイン層からアプリケーション層に知識が流出してしまうドメインオブジェクトの振る舞いを組み合わせることで問題を解決するのがアプリケーション層ドメインオブジェクトの粒度が細かいと、アプリケーション層でたくさんのドメインオブジェクトを詳細なレベルで相互作用させることになる。実質的にアプリケーション層が詳細に関する知識を持ち、処理を行うかたちになるサービスは用途の広さよりもインタフェースのシンプルさを優先する。することで再利用性が高まるし分散システムでも扱いやすくなる。パッケージ(Packages))モジュールもモデルの一部。いくつかのクラスを一つのモジュールに入れるということは、それらをひとまとめに考えるよう伝えているということ。技術的な評価と概念上の関係性の明確さはどちらも達成されるべきだが、技術的な視点では分割すべきでも、分割しないほうが概念的に明確であるといった場合は概念的な明確さを優先するほうが良い。モジュールもリファクタリングを行っていくべき。大変でもやらなければより大変になるのでなんとかやるしかない。architecture)技術駆動でパッケージングするアーキテクチャ。データのカプセル化程度ならある程度有用だが...概念的には一つのはずのオブジェクトが複数のティアに分割されてしまい、コードがモデルを明らかにしなくなるバラバラになっているティアを頭の中で一つにするのは大変なので、アーキテクチャが強制する分割に加えて意味的な分割をしようとはしなくなる・分割しても理解できなくなる。別々のサーバにコードを分散させるには有用だが、本当にそうするのか?技術的な分割ルールは本質的、あるいは開発の助けになる最小限のものにすべき。別々のサーバにコードを分散させるなどの理由がない限り、単一の概念オブジェクトを実装するコードはすべて(同一オブジェクトにはならなくても)同一モジュールにまとめること。ドメイン層を他のコードから分離するためにパッケージングをすべき。パッケージング可能にしておくこと。コードが宣言的設計にって(自動的に)生成される場合はこの限りでない(のちの章で説明)。ドメインオブジェクトに、それが表現している概念と密接に関係しないものを追加するのは避ける。ドメインオブジェクトはモデルの表現に専念すべき。ドメインに関する実行すべき責務や管理すべきデータはそのドメインオブジェクトでなく補助的なオブジェクトで実現する(のちの章で説明)。モデリングパラダイムオブジェクト指向設計が主流。シンプルで理解しやすいが、ドメインの知識をとらえ表現できるレベルの力はある。開発者コミュニティや設計文化も成熟しているし、インフラストラクチャやツールも成熟しており恩恵を受けられる。いえドメインによってはオブジェクト指向によるモデリングが不自然で、別のモデリングパラダイムを用いるほうが自然な場合もある(高度な数学的ドメインなど)。この場合もドメイン駆動設計は可能。ドメインの中には他のパラダイムのほうがはるかに簡単に表現できる場所があるものだが、僅かなら我慢すればよいし、逆にそういった場所が大半ならパラダイムを切り替えてしまうとよいかもしれない。相互依存関係が少なければ、別のパラダイムでサブシステムを作ってカプセル化することも可能。いえパラダイムを混在させるならより一層モデル駆動設計に忠実でないと破綻する。可能な限り支配的なパラダイムにある選択肢を検討すべき。一般的な非オブジェクト技術である関係データベースはパラダイムの混在の中でも特殊。オブジェクトそのものを構成するデータの永続ストレージとして機能するため。オブジェクトデータを関係データベースに格納することについてはのちの章で議論する。ドメインオブジェクトのライフサイクル生成されアクティブになるアクティブな状態から削除されるアクティブな状態で修正が加えられるアクティブな状態からデータベース表現として格納されるデータベース表現から再構成されアクティブになるアクティブな状態からデータベースまたはファイル表現としてアーカイブされるアーカイブから削除される寿命が長く、他のオブジェクトと複雑に相互作用するドメインオブジェクトの課題:ライフサイクルを通じて整合性を維持することライフサイクル管理が複雑でも、モデルが侵食されないようにすることこれらの課題に対応する3つのパターンが以下。集約ファクトリリポジトリ集約(Aggregates)ルートとなるエンティティをたて、関連するオブジェクトを集めて境界を作る。境界外からはルートエンティティの参照のみ保持できるようにする。内部のエンティティへの参照は一時的な使用のためだけなら渡してもよい。値オブジェクトのコピーであれば、単なる値で集約とは関連がないため渡してよい。ルートエンティティはグローバルな同一性を持つが、集約内部のルート以外のエンティティは、その集約内部でのみ識別可能なレベルのローカルな同一性があればよい。ルートエンティティが不変条件をチェックする最終的な責務を負う。不変条件とはデータが変更される際に常に維持されなければならない一貫性のルールのこと。これは集約のメンバ間の関係も含む。後述するファクトリとリポジトリも集約を対象としている。ファクトリ(Factories)ドメインオブジェクトの生成や再構成のためのパターン。複雑なドメインオブジェクトの生成(例えば複合オブジェクトなど)は、生成されるオブジェクトの責務には合わず、分離したい。生成の責務をクライアントオブジェクトに移してしまうと、クライアントがオブジェクトの内部構造を知る必要が出てきてしまう。そのため複雑なドメインオブジェクトの生成もやはりドメイン層の責務ではあるものの、オブジェクトの生成と組み立ては実装のために必要だがドメインにおいて意味を持たない。この問題を解決するために、より抽象的なオブジェクト構築の仕組みとしてファクトリをドメインの設計に追加する。ファクトリは他のオブジェクトの生成を責務とするプログラム要素である。ファクトリの設計方法は数多くある(ファクトリメソッド、抽象ファクトリ、ビルダなど)が、ここでは詳細には触れない。優れたファクトリに共通する要件:生成メソッドはそれぞれアトミックであり、生成するオブジェクトや集約の不変条件をすべて強制する。一貫した正しい状態のオブジェクトを生成する。不変条件が満たされずに生成が失敗した場合どうなるかを決めておくべき。不変条件のロジックはどこへ置くべきか?ファクトリは不変条件がすべて満たされていることを保証する責務を負うが、不変条件の検証は生成物に委譲することができるし、それが最善であることも多い。生成時のみチェックが必要で、アクティブな状態の間は適用されない不変条件ロジックであれば、オブジェクトが持っている必要はなく、ファクトリに置くのが理にかなっている。リポジトリ(Repositories)クライアントコードがデータベースを直接使用すると、モデルの機能は失われ、ドメインのルールがクエリのコードに埋め込まれたり失われてしまう。そこでリポジトリパターンを使用する。リポジトリはデータベースクエリやメタデータマッピングの仕組みをカプセル化し、クライアントコードからはあたかもオブジェクトがメモリ上にあるかのように、モデルの言葉で必要なものを要求できる、シンプルでドメインモデルと概念的に結びついたインタフェースを提供する。リポジトリを用いることによって、アプリケーションとドメインの設計を、永続化技術やデータベース戦略、データソースから分離できる。リポジトリの実装は多岐にわたるが、以下は覚えておくべき。型を抽象化することクライアントから切り離す利点を生かすことトランザクション制御をクライアントに委ねることまた、データベースをオブジェクトの格納先としてみる場合は、データモデルとオブジェクトモデルを可能な限り近づけておくべき。関係モデルと近づけるためにオブジェクトの関係性を制限することや、オブジェクトとのマッピングを単純化するために正規化をある程度妥協したりすることは認められる。ファクトリとの関係ファクトリは新しいオブジェクトを生成する。リポジトリは古いオブジェクトを見つけ出す。データからオブジェクトへの再構成が必要になる場合は、データを取得するまでをリポジトリが行い、再構成はファクトリに委譲すればよい。このように明確に分離することで、ファクトリから永続化に関する責務を取り除くことができる。「探して、なければ生成する」機能は避けるべき。エンティティと値オブジェクトの区別がついていない。これらを正しく区別すれば、このような機能が便利な場面はほとんどない。通常、新しいオブジェクトと既存のオブジェクトを区別することはドメインにおいて重要で、これらを透過的に組み合わせてしまうとかえって混乱を招く。継続的なリファクタリングによってコードとモデルを改善するたびに、開発者の視界が明確になる。するとモデルの複雑さが大幅に解消し、かつ用途の広さや説明力が急激に増大する(より「深い」モデルになる)ことがある。これがブレイクスルー。深いモデルへのブレイクスルーが起こりそうなときは恐怖を感じることが多い。設計の大幅な変更を必要とし、リスクも大きいため。ブレイクスルーは引き起こそうとしてもうまくいくものでない。適度なリファクタリングを何度も行えばブレイクスルーが「起こり得る」。わずかな改良をためらわずに行うこと。暗黙的な概念を明示的にする設計の中に暗に存在する概念に気づき、モデルの中で明示的に表現し、リファクタリングを繰り返すことで、より深いモデルへとつながるブレイクスルーが起こる土壌ができる。概念を掘り出す暗黙的な概念に気づくためにはどうしたらよいか:ドメインエキスパートの使う言葉に注意する。複雑なものを簡潔に述べる用語がないか。開発者がドメインエキスパートに話す際、表現を修正されることはないか。開発者があるフレーズを用いると、ドメインエキスパートに困惑されることはないか。ドメインに関係する文献を読む。複雑な制約は独立したメソッドに分解し名前を付けることで、設計上でも制約を明示できるようになる。関連するルールが複数のオブジェクトに出てきて、コードを重複させるか、同じ系統でないオブジェクトを継承しなければならない設計や要求に関する多くの会話が制約をめぐって行われるのに、実装では制約が手続型のコードの中に隠されてしまっているこのような場合は制約をくくりだすべき。ドメインオブジェクトとしてのプロセスサービスによって複雑なアルゴリズムをカプセル化するほかに、アルゴリズムを独立したオブジェクトにするアプローチもある。プロセスの実行方法が複数ある場合にそれぞれをストラテジーとして表現するなど。ドメインエキスパートが話題に挙げるものは明示すべきプロセスで、そうでないものは隠蔽すべきプロセス。仕様ルール評価コードはドメイン層に入れるべき。ルール評価が複雑すぎて、どのエンティティや値オブジェクトの責務とも合致しない場合がある。そのような場合は論理プログラミングにおける「述語」の概念を利用して、評価結果を返す独立したオブジェクトを作成するとよい。このオブジェクトが「仕様」。深いモデルによりドメインにおける幅広いシナリオを表現できること、設計が容易に理解でき変更できること、どちらも実現するしなやかな設計をする決まった方法はないが、役に立つパターンを紹介する。意図の明白なインタフェースクラスと操作には効果と目的を表す名前をつけ、内部を理解する必要がないようにする。ユビキタス言語に従って名前付けすること。すれば意味を推測できる。表明(Assertions)操作の副作用が明確でない場合、抽象化の意味はなく、結局具体的な実行を追跡する必要が出てきてしまう。操作の事後条件とクラスおよび集約の不変条件を明確に宣言することで、開発者が操作やオブジェクトを使用した結果を理解できるようになる。言語が表明を直接サポートしていない場合はユニットテストを代わりに書く。Contours)モデルや設計の要素の一つ一つが大きすぎると機能が重複する(必要な機能だけを切り出せない、あるいは見つけられないため)。いって分割しすぎるとクライアントが無意味に複雑になりかねない。設計要素(操作、インタフェース、クラス、集約)を凝集した単位に分割すること。概念的に意味のある機能単位を見つけるとよい。Classes)相互依存関係があるとモデルと設計は理解が難しくなる。依存関係を取り除き低結合にすること。Operations)依存関係を減らしつつ豊かなインタフェースを維持する方法。操作の戻り値の型が引数の型と同じにできる場合は同じにする。設計全体に取り組むのは現実的でない。システムのどの部分をしなやかにするかというと、サブドメインを切り取って取り組むとよい。設計の一部を本当にしなやかにするほうが、広く薄く行うよりよい。可能な場合は、形式化された概念フレームワークを用いる。アナリシスパターンを適用する深いモデルとしなやかな設計のためにはドメインについて学ぶ必要がある。対象によっては先人の知恵が使える。アナリシスパターンは活用すべき。デザインパターンをモデルに関係づける書籍『デザインパターン』に書かれたパターンの一部はドメインパターンとして使用可能。先の書籍ではこれらのパターンは技術的なコンテキストで語られている。ドメインモデリングや設計といったより広範なコンテキストで適用可能ということで、ドメイン駆動設計ではこれらのパターンを技術的なデザインパターンと概念的なパターンの2つのレベルで同時に考えないといけない。ポリシー(Policy))技術的に動機づけられたプロセスでなく、ドメインにおいて意味を持つプロセスもあり、こちらにもストラテジーが適用可能。プロセスの中で変化する部分を独立したストラテジーオブジェクトにくくりだす。ストラテジーデザインパターンに従って実装すること。コンポジット(Composite)コンポジットを構成するすべてのメンバを包括的に含む抽象型を定義する。深い洞察へ向かうリファクタリング主要なポイントドメインに馴染む。常に、物事に対して違う見方をする。ドメインエキスパートとの対話を途切れさせない。(以下、印象に残った個所を記載)車輪の再発明が常に必要とは限らないソフトウェアは開発者のものでもある。しなやかな設計の恩恵は開発者も受けられる。結果が予想しやすくなり変更が容易になるなど。「変更を完全に正当化できるまで待つのは、待ちすぎというものである」ソフトウェア開発は変更による利益や変更しないことによるコストを正確に割り出せるようなプロセスではないとはいえリリース前日にリファクタリングしたり、技術を披露するためだけの変更をしたりしてはいけないブレイクスルーが起きる状況は好機というよりむしろ危機に感じられるもの。戦略的設計モデリングプロセスをスケールアップさせて、大規模なシステム、複雑なドメインに対応させる原則を説明する。規模が大きくても、ドメイン駆動設計が実装と結びつかないモデルを作ることはない。モデルに集中しかつプロジェクトが行き詰らないようにするため、3つのテーマを考察する。Context)別々のモデルに基づくコードが組み合わされるとバグの温床になるし理解しにくくなる。モデルが適用される際のあらゆる条件の集合がコンテキスト。モデルが適用されるコンテキストを明示的に定義すること。Integration)多くの人が同一のコンテキストで作業するとモデルが分裂しがち。継続的にすべての作業をマージして一貫性を保ち、分裂の兆しを検知すること。コンテキストマッププロジェクトのコンテキストとコンテキスト間の関係を概観するためのマップを作る。常にありのままの状況を表すこと。境界づけられたコンテキスト間の関係コンテキスト間でやりとりする必要は当然出てくる。そのパターンを紹介する。Kernel)ドメインモデルのサブセットを2つのチームが合意の上共有する。サブセットに対して変更を加える際は他チームに相談の上行う。統合の際には両方のチームのテストを実行する。Teams)2チーム間に顧客/供給者関係を確立する。下流のチームが顧客として要求し、上流チームが供給する。インタフェースを検証する受入テストは共同で開発すること。すればテストによって上流チームは下流への副作用を心配することなく変更を行える。下流(顧客)の要求が優先。上流チームにとって下流の要求にこたえる動機づけがないとうまくいかない。順応者(Conformist)2チーム間に上流/下流の関係があるが、上流が下流の要求に応えない場合のパターン。上流の機能を使用しない上流との依存関係を維持する必要があるが上流の設計を使うのが(品質の都合等で)難しい場合、独自のモデルを作る。この場合、下流はモデルの変換層にも責任を持つ必要が出てくる。具体的には腐敗防止層によって下流のモデルを守るといったこと。上流チームのモデルに隷従する。これによってコンテキスト間の複雑な変換を回避する。下流のモデルが制限される可能性はあるが、結合が大幅に簡略化されること、隷属により上流チームとユビキタス言語を共有することになるためコミュニケーションしやすくなる等の利点はある。Layer)他システムとのやり取り、および内部モデルのための変換を隔離する。Ways)結合しない。Service)サブシステム間の結合それぞれに対して変換サービスを作るのではなく、サブシステム自身が自身にアクセスするためのプロトコルを定義・公開する。Language)ドキュメント化された共有言語(XML等)を使用してやりとりする。蒸留ドメインの中でも特に価値ある部分「コアドメイン」に集中したい。蒸留は混ざり合ったコンポーネントを分離し、本質を役立つ形式で抽出すること。コアドメインアプリケーションにとって中心的なモデルの部分がコアドメインを構成する。コアドメインは最大の価値を付加すべき場所。コアドメイン選択・作成を外部の専門家を短期で雇って行うのはうまくいかない(教育や指導目的なら価値はある)。安定したチームがドメイン知識を累積し、設計スキルも磨くことで行っていくしかない。Metaphor)メタファを使うと理解の共有が楽だが、不適切でないか絶えず調べなおすこと。Layers)ドメインに自然な階層がある場合、階層を表す幅広い抽象的な責務を割り当てる。Level)リフレクションパターンをドメイン層に適用したもの。Framework)インタフェースや相互作用にある抽象化されたコアを蒸留し、そのインタフェースの多様な実装を自由に置換できるようにするフレームワークを作成すること。同様に、抽象化されたコアのインタフェースを経由して操作される限り、あらゆるアプリケーションがこのコンポーネントを使用できるようにすること。本書にあるテクニックをすべて採用するプロジェクトはないだろうが、ドメイン駆動設計を真剣に行っているプロジェクトはすべて、対象となるドメインを理解し、その理解をソフトウェアにおいて具現化することを重視しているはず。どこかのWeb記事で薦められていたのがきっかけ。月別アーカイブはてなブログをはじめよう!vastmudaさんは、はてなブログを使っています。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。