いまの英語力を整理フィリピン(バギオ)留学決めました。2022以下の本を読んだ。あとで見返したいところについてメモを残しておく。値からインスタンス再構成する場合のみ使用します。テストコードの中では、テストに必要な任意のインスタンスを作成するために使用できます。いう名称にしていますが、名前は任意のものをつけて構いません。#名前がつけられるということです。それ以外に大きな違いはありません。通常のインスタンス化と再構成によるインスタンス化の2つを持つ場合は、同列にファクトリーメソッドとして揃えた方がわかりやすければどちらもファクトリーメソッドとしてください。通常のインスタンス化だけであれば名前のないコンストラクタで実装しても問題ありません。再構成メソッドに関しては、名前をつけて専用のメソッドであることを示すのがよいでしょう。#画面表示に使用するのであればエンティティ/値オブジェクトに「作成日時」などの属性を定義し、値の設定はクラス内のメソッドで制御しましょう。1つは、値を設定するタイミングの問題です。マッパー側で設定できません。状態のインスタンスが存在することになりますが、これは「常に正しいインスタンスしか存在させない」という方針に反します。1つは、値の設定内容の問題です。マッパーが設定する日時とドメイン層のロジックで意図する日時が異なることがあります。マッパーは更新処理が走ったら毎回更新する一方、ドメイン層のロジックとしては特定のカラムの更新は更新日判定に含めない、といったことが考えられます。ここをドメイン層のロジックにしたがって制御したい場合は、ドメイン層に自分で実装する必要があります。#エンティティに5つの属性があった時、あるエンドポイントの処理ではそのうち2つしか必要になりません。取得するのは無駄ではないでしょうか。アクセスの効率性を多少犠牲にし、保守性を高めることを目指します。実装パターンの採用に関しては、このトレードオフを考慮して判断する必要があります。文1回で実行できます。実行にあたって分岐やバリデーションが入ってくると、ドメイン知識(ルール/制約)がドメイン層とインフラ層に散在して結合度が高まる、テスト時にインフラ層をモックしにくくなるといった問題が発生します。入出力だけに留めます。することで、高凝集/低結合となり、可読性やテスト容易性が向上します。反映する」という手法をとります。アクセス効率が下がる」というコストと「可読性やテスト容易性を上げる」というリターンを考えた時に、リターンの方が大きいと考えられるかです。そのように考えられる場合はこの方法を採用し、そうでない場合は目的にあった実装を検討しましょう。アクセス効率が下がる」ことが参照系処理で許容されない場合があります。その場合はドメイン(更新用)モデルとは別にクエリ(参照用)のモデルを使用するという手段があります(CQRS)。オートインクリメントの値を使用することは可能でしょうか。#可能ですが、考慮事項がいくつかあります。実装方法を整理します。オートインクリメントの値を識別子として扱う方針は次の2通りが考えられます。マッパーに慣れている人には馴染みやすい実装になります。存在しないインスタンスができてしまうため、「常に正しいインスタンスしか存在させない」という方針に違反します。オートインクリメントの値を使用するのであれば方針2の実装をおすすめします。アルゴリズムの仕様を検討してみてください。依存しなくなり、保守性が向上します。リポジトリはエンティティ/値オブジェクトの集合を表すオブジェクトであり、件数や存在チェックなど集合に関する情報を返すメソッドは責務として矛盾がないと考えられます。返すメソッドを別途用意してもよいでしょう。渡すのがよいでしょう。ドメイン層が持つ知識として問題ないのか、という疑問が生じます。テーブル定義されているカラム名だとすると、ドメイン層にインフラ層の知識を持つことになるので問題があります。並び替えて」というように、ドメイン層にある情報のみで表現されていれば問題ありません。句を使用する)」がドメイン層に漏れ出ていなければよいのです。受け取ったリポジトリがソートを実現するには、次のように実装します。マッパー)を使用しています。渡すカラム情報に変換することにより、指定した順序でのソートを実現します。渡す値で分岐した処理を記述できます。いうキーワードで定義されていることがあります。次のように、単一エンティティを処理するメソッドとは別に、複数エンティティをまとめて更新するメソッドを設けることができます。あれば、バッチインサートを行うなど)。メソッドの作成を検討します。実装の複雑度が少し上がり、必要なテストが増えるため、パフォーマンスに問題がなければ必ずしも作成する必要はありません。いったメソッドを作成してもよいでしょうか。メリットデメリットを考慮して使い所は検討しましょう。メリットとしては、呼び元のユースケースで新規保存か更新かを意識しないで使えるようになることです。」を判断し処理を分岐するため、実装が複雑になることです。凝集度の観点からすると、責務が2つになり低凝集になると説明できます。ユースケースの段階で新規保存か更新が分かれていることは多く、そのような場合はデメリットがメリットを上回ります。本当に1つのメソッドにする必要があるかは検討し、必要なところだけ統合するのがよいでしょう。持っているような場合ですね。テーブルに永続化しているとします。インスタンスを渡します。新規保存するのか、更新するのか、削除するのか、どのように判断すればよいでしょうか。全てインサートする実装です。いった自動設定カラムの値を保持する必要がある場合はこの手段が取れません。比較して「新規保存する値・変更する値・削除する値」を分岐する方法です。実装が複雑になりますが、1つ目の方法が取れない場合は避けられません。テストをしっかり書いて動作を保証しましょう。#性質が異なるものには、個別の名前をつけた方がよいでしょう。いった名称が考えられます。#集約を実装する上で難しいのは、複数集約にまたがった整合性を確保する方法です。複数集約間の整合性確保が必要な例として、次のようなものを考えます。とあるタスク管理アプリケーションで、タスクを作成したら「{タスク名}が作成されました」という活動履歴を作成することになりました。履歴に漏れがあると困るため、タスク作成時には確実に活動履歴が作成されるようにしたいです。タスク集約と活動履歴集約、という2つの集約間に確保したい整合性があるということです。実装方法は、主に次の3つに分かれます。ユースケースで複数集約に更新をかける。ドメインサービスを使用する。ドメインイベントを使用する。#一番シンプルな実装は、ユースケースで複数集約のインスタンスを生成し、それぞれをリポジトリに渡す方法です。ドメイン層のコードは次のようになります。ActivityHistory)}このクラスを使用するユースケースは次のようになります。}}ユースケースの記述で2つの集約の整合性が確保されています。導入初期にはまずこの方法が採用されることが多いです。デメリットがあります。別のユースケースでうっかりと集約間の整合性を破壊してしまうことが可能だということです。「タスクが作成されたら活動履歴も作成される」ということはドメイン層の知識として重要なのに、その知識がドメイン層に書かれておらず、ドメイン層のコードを読んでも認識できないという問題もあります。デメリットに対する改善案が、ドメインサービスを使用する方法です。ユースケースは次のように変わります。ドメインサービスです。ユースケースからはドメインサービスを呼ぶだけになり、2つの集約間の整合性を確保する責務はそちらに移譲します。}}ひとまず集約間の整合性の知識をドメイン層に移譲できました。これでも他のユースケースで整合性を破壊することは防げないのです。これを防ぐために1つの工夫をします。言語使用に依存した実装になっているため、異なる言語では必要に応じてカスタマイズしてください。すると、同じパッケージ内でしか実装クラスを作成できなくなります。すると、そのクラスを同じファイル内でしか参照できなくなります。これを組み合わせることにより、「絶対にタスクと活動履歴がセットで作られる」という制約をかけることができます。デメリット「集約間の整合性の知識がユースケースに漏れ出ている」「うっかり集約間の整合性を破壊できる」という点を克服できました。デメリットは、ドメイン層の「サービス」の濫用を招き、ファットなドメインサービス、ドメインモデル貧血症(ドメインロジックを持たない)なエンティティ/値オブジェクトを誘発する可能性があることです。#3つ目の方法では、ドメインイベントを使用します。全体の流れは次の3ステップになります。エンティティの特定の処理のタイミングでドメインイベントを作成し、エンティティ内に蓄積する。イベント発行とそれを拾う仕組みはフレームワークに依存した実装になります。仕組みを使って実装したサンプルを示しますが、他のフレームワークに適用する際には適宜カスタマイズしてください。#複数集約間の整合性を確保する実装方法を3つ紹介しました。この3つはどれが正解ということはなく、実装コストと整合性を守る重要性のバランスを考えてどれを採用するか決定します。バランスを考慮した結果、1つのプロジェクトの中で複数の実装方法が混在しても問題ありません。集約しか更新してはいけないという記述をみたことがあります。トランザクションにおける複数集約更新に関しては賛否両論がありますが、メリットデメリットを考慮した上で判断できれば問題ないと考えています。筆者は普段は許可する方針で開発しており、十分に実践的です。トランザクションで複数集約更新するデメリットは「トランザクションをかける範囲が大きくなり、ロック取得や排他エラーにする範囲が大きくなりすぎる場合がある」ことです。メリットは途中で例外が発生した場合ロールバックができることです。トランザクションでの複数集約を許容しています。#賛否両論ありますが、問題ないと考えています。ドメインサービスの定義から検討します。ドメインサービスは「エンティティ、値オブジェクトとしてモデリングすると不自然なドメイン知識(ルール/制約)を実現する物」という定義です。その代表例が、集合に対する操作です。「予約」というエンティティについて、「この時間に予約が既に埋まっているか」というのは予約エンティティ自体が知っているとするのは不自然です。そこで、予約有無に関するドメイン知識を実現するものとしてドメインサービスを定義し、予約の集合に対する操作を行うためにリポジトリを使用することになります。このような用途を考えるとドメインサービスがリポジトリを使用することは必要になると考えます。クラスに対するテストコード量がどんどん増大していきます。クラスのテストに書けるようになりました。この際、テスト観点に関係ない他の属性を記述する必要がないため、テストコードの記述を非常に簡潔にできました。クラスは「タスク名を表すクラス」と責務が明確で高凝集/低結合になっています。今回の事例のように、高凝集/低結合な実装にするとテスト容易性、可読性が高まります。サーバーでエラーが発生した場合のメッセージについて、サーバーからエンドユーザーに表示するメッセージを直接返してよいでしょうか。それとも、エラーコードのみ返してメッセージはフロントで定義するべきでしょうか。#どちらでも可能です。メリットデメリットがあるのでそれを考慮して決定します。サーバーサイドから表示用メッセージを直接返す方針のメリットは、実装がシンプルになり、工数が全体的に低くなる点です。デメリットは、メッセージ変更の際にサーバーサイドのコードを変更する必要があるため、フロントエンドの実装だけでメッセージを含めた見た目の開発を完結させられなくなる点です。これをどう評価するかは、チームの体制によって変わります。サーバーとフロントの開発チームがきっちり分かれて極力個別で開発を進めたい場合はエラーコードのみを返す方針にし、ある程度両方修正できるメンバーが存在し柔軟に対応できる場合はメッセージを直接返す方針にする、と判断してもよいでしょう。#外部システムと連携する場合、その処理はどの層に実装すればよいでしょうか。#外部システムのクライアントオブジェクトのインターフェイスはドメイン層、実装クラスはインフラ層に定義します。リクエスト/レスポンスの型はドメイン層です。通知をする実装を考えてみます。ドメイン層には、次のオブジェクトを定義します。Notification)}この際、ドメイン層の知識として「通知をどう実現するか」に関心がない場合は、その方法(Slack)の知識はぼかして、「通知」という抽象的なクラスとして実現します。インフラ層に次のクラスを実装します。リクエストを送信しますが、その実装の詳細(どのようなクライアントライブラリを使用するのか、など)はインフラ層に閉じ込めます。それによりドメイン層は純粋に「通知を送信する」ということだけを意識すればよく、詳細な実装方法の影響をうけなくなります。いう名称を出しても問題ありません。とメールの2つあり、それぞれのパターンを意識的に使い分けたい場合などです。その場合も、通知を実行するための詳細はインフラ層に閉じ込めることは同様です。いうクラスで定義しました。#ユースケースクラスと同じパッケージや同じファイルに定義しましょう。クラスは、「ユースケースの戻り値」を表すのが責務なので、そのユースケースの近くに定義するべきです。非推奨なのは「dto」といったパッケージをくくり出してしまうことです。ユースケースの関連が追いにくくなるというデメリットが生まれるのに対して、あまり大きなメリットはありません。#いいえ、責務違反のため、非推奨です。ユースケースの戻り値用クラスはユースケース層のクラスで、表示に関する処理はプレゼンテーションの責務です。対して変換処理をする形で実装します。ユースケースのクラスは、プレゼンテーション層が何であろうと影響がない実装にした方が保守性が高まります。#ドメイン層とユースケース層では、極力特定のライブラリ、フレームワークに依存しないようにしましょう。何かライブラリを使った処理を行いたい場合は、ドメイン層とユースケース層ではインターフェイスのみ定義し、インフラ層に定義する実装クラスの中でライブラリを使用します。#ユースケースクラスのメソッド入り口でトランザクションを開始し、メソッドが正常終了したらコミット、例外が発生したらロールバック、とします。#ユースケースクラスから別のユースケースクラスを呼んでもよいでしょうか。ユースケース同士の参照を許してしまうと、複雑な処理では1つのユースケースから別のユースケースを呼び出していく階層が深まり、コードを読む際に全体像を追うのが難しくなるためです。そのため、コントローラーから呼ばれるユースケースクラスは必ず1つのみ複数ユースケースクラスから呼ばれる処理はユースケース層に独立クラスを定義して、ユースケースクラスから参照させるという方針をおすすめします。#2つの方法があります。エンティティではある処理の分岐で使用しているとします。集約からはそれを参照しているという関係性になります。パッケージに配置し、どこで定義されているかをパッケージで示します。基底クラスなどは、「shared」といった共有パッケージを定義しそこに配置します。複数集約から参照するからと言ってなんでも共有パッケージに配置してしまうと、「定義する側」「参照する側」という関係が見えなくなってしまいます。どこで定義されているかを意識してパッケージ配置することが重要です。