web-dev-qa-db-ja.com

リポジトリパターンでPHPのクラスを設計する方法は?

私は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のプロパティがあり、それらすべてにsettergetterがあります(プロパティに公開アクセスしたくない)ため、30 methodsといくつかのactions and additional getters。いくつかの例は、getInactiveUsersgetActiveUsersregisterAsAdminなどです。これにより、これらのエンティティークラスが巨大になります。一部のエンティティが1000行を超えています。

getters and settersのため、これらは最初は大きいです。logics or queriesをこのクラスのユーザーに直接関連させておきます。

これらのクラスを短く簡潔にする方法を教えてください。

2
developernaren

最初にすべきことは、モノリスを複数のモジュールに分割することです( 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 [〜#〜] を確認する必要があります。これは、モデルを書き込みで分割することにより、モデルをより簡潔にするのに役立ちます。 /コマンドモデルと読み取り/クエリモデル。

5

さて、ここで一歩戻り、解決策について話す前に問題を定義してみましょう。 本当に理解することが重要なぜ将来の関連する問題が発生し始めたときに通知する手段として、この問題に取り組んでいます。

クラスを短く簡潔にすることは、DDDの基本原則の1つです。これは、DDDの目的が、ビジネスドメインの機能要件を満たす方法でシステムの動作をモデル化することであるためです。ドメインオブジェクトは、単にデータを保持するだけでなく、タスクを実行するために存在する必要があります。 Userモデルがある唯一の理由は、データストアに_[User]_テーブルが存在するためです。ここでの問題は、オブジェクトに含まれるデータ/属性が、システムの機能要件をモデル化しようとするときの出発点としてはめったにないということです。 Userのようなデータベースオブジェクトは、動作を意味しないことを除いて、抽象化されすぎて知識が多すぎるため、特定のドメインでの再利用には適していません。

より適切な名前は、BuyerまたはShopperです。ドメインBuyerは物理的に_[User]_テーブルにマッピングされる場合がありますが、支払いプロセスreallyが販売を実行するために15以上の属性を必要とすることは信じられないほど考えられません(それはidPaymentMethod!)が必要なだけかもしれません。このモデルは、テーブル内のフィールドのサブセットの管理のみを担当します。 _[User]_のAccountManagerおよびemailを管理するpasswordという名前の別のドメインオブジェクトがある場合があります。私がここに行くところがわかりますか?データストアは、ドメインに関係する必要のない実装の詳細です。

したがって、この問題が発生する理由whyは、単一のオブジェクトの詳細ではなく、ドメインモデルの欠陥に関係しています。

@Constantin Galbenuに同意しますが、プロジェクトはBounded Contextに従って動作をモジュール(ディレクトリ)に整理することでメリットを得られますが、DDDビルディングブロックにちなんで名付けられたサブディレクトリにさらに分類することには同意しません。それは本当に多くの組織的価値を追加せず、サブ名前空間を完全に防ぎ、物理ディレクトリ構造を論理モデルに結合します。クラス名自体は、オブジェクトがどの「レイヤー」に該当するかを明確にする必要があります。名前空間をさらにネストすると、意味のある場合は、さらにパーティションを提供できます。

最後に、Payサービスに関するコメントをさせていただきます。その名前のサービスは確かにシステムに存在する必要があるかもしれませんが、製品を購入するビジネスプロセスを他の人に説明しているとき、どう思いますか? 「A UserProductを購入します」と言った場合、$user->purchase($product)のようなコードが期待されます。私は、あなたのアプローチを簡単かつ簡潔に説明したり、コードを要約したりする方法を理解しようとする言葉に困惑しています。 DDDでは、コードがdo正しいことだけでは不十分です。それはsays正しいことであることが重要です。

@Constantin Galbenuの例でさえ、プロセスがモデルの上に適用される高レベルの関数に分離され、オブジェクトが(タイプセーフな)データの単純なバッグに展開されて引数として渡される貧血モデルの悲鳴を上げます。それはDDDではありません!それは機能的なアプローチです。それは間違いではありません、違うだけです。オブジェクトはpairedである必要があり、分離されていない。

1
king-side-slide