エンティティとインタラクターまたはユースケース間のロジックの分離に苦労しています。 DDD原則を使用してエンティティを設計する場合、各エンティティには、セッターやゲッターではなく、ユースケースに対応するメソッドが含まれます。その場合、私は、インタラクタークラスとエンティティーメソッドのほぼ1対1のマッピング(おそらく、複数のエンティティーにまたがるインタラクターがあり、より複雑なシナリオをオーケストレーションする)を持っていると思います。
たとえば、次のエンティティクラスがあるとします。
Sale (entity)
+createSale()
+ammendSale()
+cancelSale()
+shipSale()
+collectSale()
そして次のコマンドクラス:
CreateSaleCommand
AmmendSaleCommand
CancelSaleCommand
ShipSaleCommand # (this command may interact with the inventory service in a microservices context, or with the ProductStock entity in a monolithic context)
CollectSaleCommand # (this command may interact with payment and accounting services, or with the corresponding entities)
このアプローチについてどう思いますか?ほとんどのコマンドは、エンティティにリクエストを渡してレスポンスを返すだけの貧弱なクラスであるため、多くのメリットがないアーティファクトの増殖につながる可能性があると思います。リポジトリと外部サービスにアクセスするためのロジックをカプセル化しますが、エンティティはドメインロジック(関連するビジネスアクションとイベントを表すメソッド、およびビジネスコンセプトとカテゴリを表すプライベートデータ)に専念できます。
インタラクターとエンティティーの1対1のマッピングは必要ありません。そのようなデザインは有害だと思います。
DDDは、コンテキストの境界と、その境界内のユビキタス言語についてすべてです。ビジネスの言語を表すオブジェクトの作成に本当に焦点を合わせると、すべてが少しずつ形を変え始めていることがわかります。
セールが自分で作成または出荷するのは奇妙に思えます。現実の世界では、販売員が販売をしているところを想像できました。次に、出荷部門がロジスティクスを管理して出荷します。おそらく、販売はオンラインのショッピングカートを介して作成され、電子メールで発送されます。この言語を例として使用して、可能な代替設計を検討してみましょう。
SalesPerson
+createSale(Customer, Product, Price)
+amendSale(Sale, Amendment)
ShippingDepartment
+shipSale(Sale)
一瞬待って!?通常、注文は発送されませんか?多分それはこのようになるはずです:
SalesPerson
+createOrder(Customer, Product, Price)
+amendOrder(Order, Amendment)
ShippingDepartment
+shipOrder(Order)
Order
+constructor(Customer, Product, Price)
+applyDiscount(Discount)
+updateShippingAddress(Address)
どちらも近づきつつありますが、実際には私たちの営業チームはそれらを注文とは呼ばず、私たちの輸送部門はそれらを注文としか呼びません。また、注文は常にセールの存在から作成されるため、それも反映する必要があります。
SalesPerson
+createSale(Customer, Product, Price)
+amendSale(Sale, Amendment)
Sale
+constructor(Customer, Product, Price)
+applyDiscount(Discount)
+updateShippingAddress(Address)
ShippingDepartment
+shipOrder(Order)
Order
+constructor(Sale)
+placeOnHold()
+shipped(TrackingNumber)
そして、それは発展し続けています。
ロジックの分離に対処する方法は、そのオブジェクトを実際の生活の中で試して視覚化することです。次に、ビジネスのニーズ/ユースケース/機能に関連するプロパティとアクションについて考えます。
編集:DDDでエンティティのメソッドをどのように操作しますか?
コマンドパターンや他のインタラクターを使用する代わりに、それらを直接使用するだけです。
salesPerson = new SalesPerson()
sale = salesPerson.createSale(...)
...
shippingDept.shipOrder(new Order(sale))
2つは相互に排他的ではないことに注意してください。必要に応じて、コマンドパターンまたはユースケース/インタラクターを利用できます。あなたはこのようなものがあるかもしれません(多くのエンティティとメソッドへの1つのインタラクター):
PartyUseCase(ShippingDepartment)
+prepareParty(SalesPerson, Customer)
plates = ProductRepo.findByName('plates')
forks = ProductRepo.findByName('forks')
platesSale = salesPerson.createSale(Customer, plates)
forksSale = salesPerson.createSale(Customer, forks)
ShippingDepartment.shipOrder(new Order(platesSale))
ShippingDepartment.shipOrder(new Order(forksSale))
...
または、これ以外に何もせずに1日に1回実行される単純なスクリプトがある場合があります(インタラクター0)。
orderRepo = new OrderRepository()
shippingDept = new ShippingDepartment()
for each order in orderRepo.getOrdersToShip()
shippingDept.shipOrder(order)
ただし、最終的には、クラスとメソッドの1対1のマッピングを作成している場合は、不要な複雑さが生じます。
サンプルアーキテクチャを見たときに最初に考えたのは、「セールはそれ自体を作成できますか?セールはそれ自体をキャンセルできますか?さらに重要なことに、セールはそれ自体を出荷したり、自分のお金を集めたりできますか?」
「真のオブジェクト指向は常にデータとロジックを結びつける」と誰かがどこかで決めたため、「貧血データモデル」という言葉は汚い言葉と見なされます。しかし、データ転送オブジェクトは常に境界を越えてデータを渡すために使用され、貧弱なオブジェクトのまさにその定義です。
基本的に、「クリーンアーキテクチャ」はレイヤードアーキテクチャのもう1つのフレーバーです。すべてのレイヤードアーキテクチャの管理原則は、依存関係は一方向にのみ流れる必要があります。たとえば、ビジネスロジックレイヤー(BLL)はデータアクセスレイヤー(DAL)について知っている必要がありますが、DALは知っている必要がありますnothing BLLについて。この配置により、たとえば、DAL自体を変更する必要なく、異なるBLL(会社の各部門に1つなど)をDALに接続できます。
このような設計概念を個別に説明することは難しいので、これをより具体的にしましょう。
これは ArchFirst と呼ばれるソフトウェア開発のベストプラクティスを示すサンプルトレーディングアプリケーションのアーキテクチャ図です。これは、注文管理システム(OMS)とExchangeの2つの境界コンテキストを示していることに注意してください。
次の図を見てください。
注文のメソッドは、それが使用されるコンテキストに依存することに注意してください。
また、これらのメソッドが、おそらくOrderManager
オブジェクトのように、orderオブジェクト以外の場所にあることを必ずしも嫌っていません。
私には気分が悪い。
少なくとも私に同意する2つの原則があります。最初は:Keep-It-Simpleです。メリットがないのに、純粋に技術的なレイヤーを導入するのはなぜですか?あなたの直感は正しいと思います。それは、将来の問題を解決するかもしれないし、しないかもしれないものの重複です。 Keep-It-Simpleアプローチは、問題が解決しない場合nowであれば、それを取り除くように指示します。
2つ目は、ビジネスドメインに集中することです。私が「コマンド」に関して持っている問題は、それらが純粋に技術的であるということです。つまり、ビジネス、ユビキタス言語、ドメインには属していません。それはくだらないです。
また、ボブおじさんのクリーンアーキテクチャを直接参照している場合は、以下に オブジェクト指向の観点からのクリーンアーキテクチャの分析 を示します。ここでは、これらのアイデアが基本的にオブジェクト指向。
インタラクター(つまり、それがCommandsの意味するところだと思います)は、DDD/Onion Architectureのアプリケーションサービスに似ています。ユースケースまたはビジネストランザクションを実行するためのホスティング環境を提供し、ドメインおよび外部コンポーネントへの呼び出しを調整します。
たとえば、次のシーケンスを持つことができます
Start unit of work
Get entity from repository
Call entity method
Send email notification
Commit unit of work
これは決して「貧弱でもなく、「エンティティメソッドを呼び出して応答を返す」ほど簡単でもありません。それはそれ自体の責任と見なすことができます。このロジックをどこかに配置する必要があり、それをプレゼンターに配置すると、肥大化して一貫性がなくなる可能性があります。