私はlaravelをリポジトリパターンで使用します。
私のプロジェクトの構造は:
Entity
class(POPO
)を実装していて、Interface
を実装し、それに関連するrepository class
を持っています。
たとえば、User Entity
の場合、次のようになります。
App\Entities\User
-エンティティクラスApp\Repos\User
-Repository
クラスApp\Interfaces\User
-インターフェース
システムのほとんどのアクションは、通常、サービスクラス内で行われます。
たとえば、支払いプロセスの場合、Pay
というサービスがあります。支払いを完了するには、これらを呼び出す方法があります
$pay = new \App\Services\Pay;
$pay->asUser($user); //a user object
$pay->forItem($product); // a product class
$pay->pay(); //actually paid
私はこの実装に満足しており、今のところそれはうまく機能しています。
改善が見られるのは、Entity Class
の長さです。たとえば、User
クラスには約15のプロパティがあり、それらすべてにsetter
とgetter
があります(プロパティに公開アクセスしたくない)ため、30 methods
といくつかのactions and additional getters
。いくつかの例は、getInactiveUsers
、getActiveUsers
、registerAsAdmin
などです。これにより、これらのエンティティークラスが巨大になります。一部のエンティティが1000行を超えています。
getters and setters
のため、これらは最初は大きいです。logics or queries
をこのクラスのユーザーに直接関連させておきます。
これらのクラスを短く簡潔にする方法を教えてください。
最初にすべきことは、モノリスを複数のモジュールに分割することです( Bounded context ごとに1つのモジュール)。これは、あなたが行う最も重要なアーキテクチャ上の決定でもあります。これを正しく行うと、すべてのモデル(集合体)が可能な限り小さくなります。 PHPでは、モジュールを名前空間(トップレベルのディレクトリ)として実装できます。したがって、例は次のようになります。
App\Authentication
App\Authorization
App\Payment
App\Ordering
次に、各境界付きコンテキストで、(つまりApp\Ordering
):
App\Ordering
Domain
Entities
Buyer
Order
...
Repository
BuyersRepository (interface)
OrdersRepository (interface)
...
Infrastructure
Repository
BuyersSqlRepository
OrdersSqlRepository
コードの重複を減らすために、一般的なクラスは最上位の名前空間、つまりFramework
にとどまることができます。例は、クエリビルダー、GUIDファクトリーなどです。
$ pay =新しい\ App\Services\Pay
DDDでは、サービスは常にステートレスです。サービスはステートフルのようです。また、それをどのように使用できるか、どのフィールドがオプションであり、pay
メソッドに何が必要かは明確ではありません。次のようにする必要があります。
$pay->pay($user, $product); //it is now clear that user and product are required
... Userクラスには約15のプロパティがあり、そのすべてにセッターとゲッターがあります(プロパティに公開アクセスしたくない)。
関連する観点から見ると、public
セッター+ゲッターを持ち、プロパティをpublic
にすることは同じことです。代わりに、いくつかのspecificアクションを実行するか、何かを返すメソッドが必要ですに関連して命名されたユビキタス言語、 CQSの原則 。
... getInactiveUsers、getActiveUsers ...これらのエンティティクラスは巨大になります
そのようなメソッドを持つエンティティが表示されません。これらは、リポジトリインターフェースまたはドメインサービスの一部のようです。
ドメインが複雑な場合は、 [〜#〜] cqrs [〜#〜] を確認する必要があります。これは、モデルを書き込みで分割することにより、モデルをより簡潔にするのに役立ちます。 /コマンドモデルと読み取り/クエリモデル。
さて、ここで一歩戻り、解決策について話す前に問題を定義してみましょう。 本当に理解することが重要なぜ将来の関連する問題が発生し始めたときに通知する手段として、この問題に取り組んでいます。
クラスを短く簡潔にすることは、DDDの基本原則の1つです。これは、DDDの目的が、ビジネスドメインの機能要件を満たす方法でシステムの動作をモデル化することであるためです。ドメインオブジェクトは、単にデータを保持するだけでなく、タスクを実行するために存在する必要があります。 User
モデルがある唯一の理由は、データストアに_[User]
_テーブルが存在するためです。ここでの問題は、オブジェクトに含まれるデータ/属性が、システムの機能要件をモデル化しようとするときの出発点としてはめったにないということです。 User
のようなデータベースオブジェクトは、動作を意味しないことを除いて、抽象化されすぎて知識が多すぎるため、特定のドメインでの再利用には適していません。
より適切な名前は、Buyer
またはShopper
です。ドメインBuyer
は物理的に_[User]
_テーブルにマッピングされる場合がありますが、支払いプロセスreallyが販売を実行するために15以上の属性を必要とすることは信じられないほど考えられません(それはid
とPaymentMethod
!)が必要なだけかもしれません。このモデルは、テーブル内のフィールドのサブセットの管理のみを担当します。 _[User]
_のAccountManager
およびemail
を管理するpassword
という名前の別のドメインオブジェクトがある場合があります。私がここに行くところがわかりますか?データストアは、ドメインに関係する必要のない実装の詳細です。
したがって、この問題が発生する理由whyは、単一のオブジェクトの詳細ではなく、ドメインモデルの欠陥に関係しています。
@Constantin Galbenuに同意しますが、プロジェクトはBounded Contextに従って動作をモジュール(ディレクトリ)に整理することでメリットを得られますが、DDDビルディングブロックにちなんで名付けられたサブディレクトリにさらに分類することには同意しません。それは本当に多くの組織的価値を追加せず、サブ名前空間を完全に防ぎ、物理ディレクトリ構造を論理モデルに結合します。クラス名自体は、オブジェクトがどの「レイヤー」に該当するかを明確にする必要があります。名前空間をさらにネストすると、意味のある場合は、さらにパーティションを提供できます。
最後に、Pay
サービスに関するコメントをさせていただきます。その名前のサービスは確かにシステムに存在する必要があるかもしれませんが、製品を購入するビジネスプロセスを他の人に説明しているとき、どう思いますか? 「A User
はProduct
を購入します」と言った場合、$user->purchase($product)
のようなコードが期待されます。私は、あなたのアプローチを簡単かつ簡潔に説明したり、コードを要約したりする方法を理解しようとする言葉に困惑しています。 DDDでは、コードがdo正しいことだけでは不十分です。それはsays正しいことであることが重要です。
@Constantin Galbenuの例でさえ、プロセスがモデルの上に適用される高レベルの関数に分離され、オブジェクトが(タイプセーフな)データの単純なバッグに展開されて引数として渡される貧血モデルの悲鳴を上げます。それはDDDではありません!それは機能的なアプローチです。それは間違いではありません、違うだけです。オブジェクトはpairedである必要があり、分離されていない。