ホームページ >バックエンド開発 >PHPチュートリアル >総根のコレクションの処理 - リポジトリパターン

総根のコレクションの処理 - リポジトリパターン

Joseph Gordon-Levitt
Joseph Gordon-Levittオリジナル
2025-02-27 10:46:10325ブラウズ

Handling Collections of Aggregate Roots – the Repository Pattern

コアポイント

  • ドメイン駆動型設計(DDD)の倉庫モデルは、ドメインモデルとデータマッピングレイヤーの間の仲介者として機能し、データのクエリ管理を強化し、重複を最小限に抑えます。
  • 倉庫は、ドメインモデルからのデータレイヤーの複雑さを抽象化し、DDD原則に沿った懸念と持続性の無知の明確な分離を促進します。
  • 倉庫の実装には、ドメインモデルとの相互作用を簡素化できるコレクションのようなインターフェイスの背後にあるデータアクセスと操作のロジックをカプセル化することが含まれます。
  • ウェアハウジングは、管理ドメインの複雑さと分離ドメインのロジックとデータの永続性の詳細に大きな利点を提供しますが、その実装は単純なアプリケーションには複雑すぎる場合があります。
  • 倉庫の実際の使用は、複雑なクエリとデータ操作を必要とするシステムで観察できます。そこでは、よりドメイン中心の言語を提供し、インフラストラクチャの漏れをドメインモデルに削減します。

従来のドメイン駆動型デザイン(DDD)アーキテクチャの最も典型的な側面の1つは、ドメインモデルによって示される必須の持続性です。アクティブレコードまたはデータテーブルゲートウェイに基づいたいくつかの実装を含む、より保守的な設計では(非常に欺cept的なシンプルさを追求するために、多くの場合、インフラストラクチャ汚染ロジックになります)。一方、ドメインモデルは、最初から厳密に「不可知論的」な性質に概念的に設計されているため、その境界を超えて持続ロジックのいずれかをシフトします。 DDDは「データベース」を直接参照するときにややとらえどころのないことを考慮しても、現実の世界では、ドメインモデルを何らかの形で持続する必要があるため、少なくとも1つのデータベースが舞台裏で実行されている可能性があります。したがって、モデルとデータアクセスレイヤーの間にマッピングレイヤーを展開することが一般的です。これは、レイヤー間のかなりの程度の分離を維持することを積極的に促進するだけでなく、問題レイヤーのギャップ間でドメインオブジェクトを行き来することを含むクライアントコードのすべての複雑な詳細を保護します。 Mea culpa公平を期すために、データマッパー層の特異点を処理することはかなりの負担であり、「1回/永続的な使用」戦略が頻繁に採用されると言うのは公平です。それにもかかわらず、上記のパターンは、少数のマッパーによって処理される少数のドメインクラスのみがかなり単純な条件下でよく機能します。ただし、モデルが膨張し始めてより複雑になると、時間の経過とともに追加のマッパーが追加されるため、状況はより厄介になる可能性があります。これは、少なくとも複数の場所で高価なオブジェクトグラフを作成しない場合、または繰り返し実装の罪の経路に乗り出す場合、複数の複雑な総根で構成されるリッチドメインモデルを使用する場合、永続的なネグレクトへの扉を開くことは実際に実装するのが難しいことを示唆しています。さらに悪いことに、さまざまな条件に一致するデータベースから高価な集約されたルートセットを抽出する必要がある大規模システムでは、クエリプロセス全体が、単一のエントリポイントで適切に集中していない場合、そのような欠陥のある重複のアクティブで多作なイネーブラーになります。この複雑なユースケースでは、データマッパーとドメインモデルの間で仲裁する追加の抽象化層(多くの場合、DDD専門用語で倉庫と呼ばれる)を実装することで、モデルに設定された実際のメモリのセマンティクスを公開しながら、クエリロジックの複製を最小限に抑えることができます。ただし、マッパー(インフラストラクチャの一部)とは異なり、倉庫自体はモデルと密接に結びついているため、モデルの言語によって特徴付けられます。また、マッパーへの暗黙の依存のために、持続性の無知も保持し、ドメインオブジェクトに近いレベルの抽象化を提供します。残念ながら、すべての可能なアプリケーションが倉庫の利点を簡単に実装できるわけではないため、状況が必要な場合にのみ実装する価値があります。いずれにせよ、小さな倉庫をゼロから構築することは非常に有益であるため、内部でどのように機能するかを確認し、かなり難解なシェルの下にあるものを正確に明らかにすることができます。

予備準備を実施します

倉庫を実装するプロセスは、実際に、いくつかの種類の永続的なアダプターなどを注入する単純化されたコレクションのようなAPIの後に、データマッパーを注入および処理するすべての詳細を隠すため、非常に複雑です。依存関係のこの連続的な注入は、大量の論理と相まって、現在、いくつかの視点が概念とは異なる場合でも、倉庫が単純な外観と見なされる理由を説明しています。どちらの場合でも、機能的な倉庫を起動して実行するために実行する最初のステップは、基本的なドメインモデルを作成することです。ここで使用する予定のモデルは、基本構造を次のようにモデリングする責任があります。

<code class="language-php"><?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();

    public function setRole($role);
    public function getRole();
}</code>
<code class="language-php"><?php namespace Model;

class User implements UserInterface
{
    const ADMINISTRATOR_ROLE = "Administrator";
    const GUEST_ROLE         = "Guest";

    protected $id;
    protected $name;
    protected $email;
    protected $role;

    public function __construct($name, $email, $role = self::GUEST_ROLE) {
        $this->setName($name);
        $this->setEmail($email);
        $this->setRole($role);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
                "The user ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = htmlspecialchars(trim($name), ENT_QUOTES);
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setRole($role) {
        if ($role !== self::ADMINISTRATOR_ROLE
            && $role !== self::GUEST_ROLE) {
            throw new InvalidArgumentException(
                "The user role is invalid.");
        }
        $this->role = $role;
        return $this;
    }

    public function getRole() {
        return $this->role;
    }
}</code>
この場合、ドメインモデルはかなり骨格層であり、自己検証できる単純なデータホルダーよりもかろうじて高くなります。シンプルで理解しやすいままにするために、このモデルをこれを合理化します。モデルはすでに簡単に隔離されているため、ユーザーオブジェクトのコレクションを処理する別のクラスを追加することで、より豊かにしましょう。この「アドオン」コンポーネントは、カスタブル、ArrayAccess、およびIteratorAggregate SPLインターフェイスを実装するクラシックアレイラッパーにすぎません。

実際には、通常の配列を使用するとほぼ同じ結果が生成されるため、この配列のセットをモデルの境界内に配置することは完全にオプションです。ただし、この場合、独立したコレクションクラスに依存することにより、オブジェクト指向のAPIを介してデータベースから抽出されたユーザーオブジェクトのセットに簡単にアクセスできます。さらに、ドメインモデルがインフラストラクチャに設定された基礎となるストレージを完全に無視する必要があることを考慮すると、次の論理的なステップは、データベースから適切に分離するマッピングレイヤーを実装することです。以下は、このレイヤーを構成する要素です。
<code class="language-php"><?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();

    public function setRole($role);
    public function getRole();
}</code>
<code class="language-php"><?php namespace Model;

class User implements UserInterface
{
    const ADMINISTRATOR_ROLE = "Administrator";
    const GUEST_ROLE         = "Guest";

    protected $id;
    protected $name;
    protected $email;
    protected $role;

    public function __construct($name, $email, $role = self::GUEST_ROLE) {
        $this->setName($name);
        $this->setEmail($email);
        $this->setRole($role);
    }

    public function setId($id) {
        if ($this->id !== null) {
            throw new BadMethodCallException(
                "The ID for this user has been set already.");
        }
        if (!is_int($id) || $id             throw new InvalidArgumentException(
                "The user ID is invalid.");
        }
        $this->id = $id;
        return $this;
    }

    public function getId() {
        return $this->id;
    }

    public function setName($name) {
        if (strlen($name)  30) {
            throw new InvalidArgumentException(
                "The user name is invalid.");
        }
        $this->name = htmlspecialchars(trim($name), ENT_QUOTES);
        return $this;
    }

    public function getName() {
        return $this->name;
    }

    public function setEmail($email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException(
                "The user email is invalid.");
        }
        $this->email = $email;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setRole($role) {
        if ($role !== self::ADMINISTRATOR_ROLE
            && $role !== self::GUEST_ROLE) {
            throw new InvalidArgumentException(
                "The user role is invalid.");
        }
        $this->role = $role;
        return $this;
    }

    public function getRole() {
        return $this->role;
    }
}</code>

Unbox、UserMapperが実行するタスクのバッチは非常にシンプルで、データベースからユーザーを抽出し、CreateUSER()メソッドを介して対応するエンティティを再構築する責任のあるいくつかの汎用ファインダーを公開することに限定されています。また、以前にいくつかのマッパーを掘り下げ、自分のマッピングの傑作を書いた場合、上記は確かに理解しやすいです。強調表示する価値のある唯一の微妙な詳細は、おそらくモデルではなくマッピングレイヤーにusercollectionインターフェイスが配置されていることです。このようにして、ユーザーコレクションが依存する抽象化(プロトコル)は、依存関係反転の原則によって促進されるガイドと一致する、より高いレベルのUsermapperによって明示的に宣言および所有されているためです。マッパーが既にセットアップされていると、箱から出して直接使用し、ストレージからいくつかのユーザーオブジェクトを抽出して、モデルがすぐに水分補給できるようにします。これは一見正しい道であるように見えますが、実際にはマッパーがインフラストラクチャの一部であるため、インフラストラクチャを使用してアプリケーションロジックを不要にしています。将来的には、より詳細なドメイン固有の条件(マッパーのファインダーによって公開される一般的な条件だけでなく)に基づいてユーザーエンティティを照会する必要がある場合はどうなりますか?この場合、マッピングレイヤーの上に追加のレイヤーを配置する必要があります。これは、より高いレベルのデータアクセスを提供するだけでなく、単一のポイントを介してクエリロジックブロックを運ぶこともできます。最終的に、それは私たちが倉庫に期待する膨大な量の利点です。

ユーザーウェアハウジングを実装

生産環境では、倉庫はモデルに集約された根のメモリセットの幻想を明らかにするために、その表面に考えることができるほぼすべてを実装できます。ただし、この場合、私たちが構築する倉庫は、データベースからユーザーを抽出する責任のあるかなり人工構造になるため、この高価な贅沢を無料で楽しむことはできません。

<code class="language-php"><?php namespace ModelCollection;
use MapperUserCollectionInterface,
    ModelUserInterface;

class UserCollection implements UserCollectionInterface
{
    protected $users = array();

    public function add(UserInterface $user) {
        $this->offsetSet($user);
    }

    public function remove(UserInterface $user) {
        $this->offsetUnset($user);
    }

    public function get($key) {
        return $this->offsetGet($key);
    }

    public function exists($key) {
        return $this->offsetExists($key);
    }

    public function clear() {
        $this->users = array();
    }

    public function toArray() {
        return $this->users;
    }

    public function count() {
        return count($this->users);
    }

    public function offsetSet($key, $value) {
        if (!$value instanceof UserInterface) {
            throw new InvalidArgumentException(
                "Could not add the user to the collection.");
        }
        if (!isset($key)) {
            $this->users[] = $value;
        }
        else {
            $this->users[$key] = $value;
        }
    }

    public function offsetUnset($key) {
        if ($key instanceof UserInterface) {
            $this->users = array_filter($this->users,
                function ($v) use ($key) {
                    return $v !== $key;
                });
        }
        else if (isset($this->users[$key])) {
            unset($this->users[$key]);
        }
    }

    public function offsetGet($key) {
        if (isset($this->users[$key])) {
            return $this->users[$key];
        }
    }

    public function offsetExists($key) {
        return ($key instanceof UserInterface)
            ? array_search($key, $this->users)
            : isset($this->users[$key]);
    }

    public function getIterator() {
        return new ArrayIterator($this->users);
    }
}</code>
<code class="language-php"><?php namespace Mapper;
use ModelUserInterface;

interface UserCollectionInterface extends Countable, ArrayAccess, IteratorAggregate 
{
    public function add(UserInterface $user);
    public function remove(UserInterface $user);
    public function get($key);
    public function exists($key);
    public function clear();
    public function toArray();
}</code>
<code class="language-php"><?php namespace Mapper;
use ModelRepositoryUserMapperInterface,  
    ModelUser;

class UserMapper implements UserMapperInterface
{    
    protected $entityTable = "users";
    protected $collection;

    public function __construct(DatabaseAdapterInterface $adapter, UserCollectionInterface $collection) {
        $this->adapter = $adapter;
        $this->collection = $collection;
    }

    public function fetchById($id) {
        $this->adapter->select($this->entityTable,
            array("id" => $id));
        if (!$row = $this->adapter->fetch()) {
            return null;
        }
        return $this->createUser($row);
    }

    public function fetchAll(array $conditions = array()) {
        $this->adapter->select($this->entityTable, $conditions);
        $rows = $this->adapter->fetchAll();
        return $this->createUserCollection($rows);

    }

    protected function createUser(array $row) {
        $user = new User($row["name"], $row["email"],
            $row["role"]);
        $user->setId($row["id"]);
        return $user;
    }

    protected function createUserCollection(array $rows) {
        $this->collection->clear();
        if ($rows) {
            foreach ($rows as $row) {
                $this->collection[] = $this->createUser($row);
            }
        }
        return $this->collection;
    }
}</code>
かなり軽量構造に加えて、UserrePositoryの実装は非常に直感的です。APIにより、モデル言語に密接に関連する微細な述語に適合するストレージからユーザーオブジェクトのコレクションを抽出できるためです。さらに、現在の状態では、リポジトリは一部の単純なファインダーのみをクライアントコードに公開し、データマッパーの機能を使用してストレージにアクセスします。より現実的な環境では、倉庫は総根を維持できるはずです。挿入()メソッドまたはその他の同様のメソッドをUserRePositoryに追加する場合は、お気軽にお問い合わせください。どちらの場合でも、例を介して倉庫を使用することの実際の利点をキャプチャする効果的な方法は次のとおりです。
<code class="language-php"><?php namespace Model;

interface UserInterface
{
    public function setId($id);
    public function getId();

    public function setName($name);
    public function getName();

    public function setEmail($email);
    public function getEmail();

    public function setRole($role);
    public function getRole();
}</code>
前述のように、倉庫は、低レベルの技術用語ではなく、著書「ドメイン駆動デザイン」でエリック・エヴァンスによって作成されたいわゆる「ユニバーサル言語」)とビジネス用語を効果的に交換します。一方、データマッパーファインダーに存在するあいまいさとは異なり、倉庫の方法は、ユーザーエンティティのモデリングのプロパティの一部である「名前」、「電子メール」、および「役割」でそれ自体を記述します。複雑なシステムのクエリロジックをカプセル化するときに必要なこのより細かい高レベルのデータ抽象化と必要な完全な機能セットは、間違いなく、ウェアハウジングがマルチレイヤーデザインでより魅力的である最も説得力のある理由の1つです。もちろん、ほとんどの場合、これらの利点を前もって取得する手間と追加の抽象化層を展開することとの間には暗黙のトレードオフがあります(より単純なアプリケーションではあまりにも肥大化している可能性があります)。

結論 ドメイン駆動型のデザインのコアコンセプトの1つとして、倉庫は、JavaやC#などのいくつかの言語で書かれたアプリケーションに記載されています。しかし、PHPでは、彼らはまだ比較的未知であり、世界で最初の一歩を踏み出しています。それでも、Flow3やもちろん、DDDパラダイムの採用に役立つDoctrine 2.xなどの信頼できるフレームワークがいくつかあります。既存の開発アプローチと同様に、アプリケーションでリポジトリを使用したり、DDDの背後にあるコンセプトヒープで不必要に粉砕する必要はありません。常識を適用して、それらがあなたのニーズに適していると思われる場合にのみ選択してください。そんなに簡単です。

チャンスAgrella / freerangestock.comからの写真

集約されたルートコレクション(FAQ)

の取り扱いに関する

FAQ ドメイン駆動型のデザインの集合ルートは何ですか?

ドメイン駆動型デザイン(DDD)では、集約ルートはユニットと見なされる関連オブジェクトのコレクションです。これらのオブジェクトは、ルートエンティティ(総根としても知られています)によって結合されます。集約ルートは、メンバーへの参照を保持するために外部オブジェクトを禁止することにより、集約に加えられている変更の一貫性を維持します。

集計ルートと通常のエンティティの違いはどうですか?

総根と通常のエンティティの主な違いは、その責任です。通常のエンティティは動作と状態をカプセル化しますが、集約ルートはメンバーへのアクセスを制御することにより、集約全体の完全性を保証します。外部オブジェクトがそれらへの参照を保持できるようにする集約の唯一のメンバーです。

ドメインモデルで総根を識別する方法は?

集約ルートを特定するには、ビジネス分野を深く理解する必要があります。通常、グローバルなアイデンティティを備えた高レベルのエンティティであり、他のエンティティとバリューオブジェクトをカプセル化します。たとえば、eコマースの世界では、注文は、行のアイテムと配信情報をカプセル化する総合的なルートにすることができます。

集約された根のコレクションを扱う方法は?

集約された根のコレクションを処理するのは困難です。各集合ルートが一貫性の境界であることを覚えておくことが重要です。したがって、1つの集計ルートへの変更は、他の総根に影響しないはずです。したがって、コレクションを処理するときは、通常、各集合体ルートを個別にロードして持続することをお勧めします。

集約ルートは別の集計ルートを参照できますか?

はい、集計ルートは別の集計ルートを参照できますが、識別によってのみ参照する必要があります。これは、別の集計ルートオブジェクトではなく、IDへの直接的な参照を保持する必要があることを意味します。これにより、各集合ルートの一貫性境界を維持するのに役立ちます。

集計ルートは、DDDの倉庫にどのように関連していますか?

DDDでは、WareHousingは総根を取得して保存する方法を提供します。基礎となるストレージメカニズムを抽象化し、ドメインモデルがデータの持続性の詳細を無視できるようにします。各集約ルートには通常、独自のストレージがあります。

ビジネスルールの実行における集約ルーツの役割は何ですか?

集約ルーツは、ビジネスルールの実行において重要な役割を果たします。集約のすべての変更が有効な状態にあることを保証します。つまり、複数のエンティティまたはバリューオブジェクトにまたがるビジネスルールは、集計ルートによって実施される必要があります。

集約ルートは、ドメインモデルの複雑さをどのように減らしますか?

集約ルーツは、一貫性の境界として機能し、メンバーへのアクセスを制御することにより、ドメインモデルの複雑さを軽減するのに役立ちます。各集約に単一の相互作用ポイントを提供することでモデルを簡素化し、システムを理解しやすくします。

集合ルートは複数の集合体の一部になることができますか?

いいえ、集合ルートは複数の集合体の一部であってはなりません。これは、集合体の一貫性境界に違反し、ドメインモデルの矛盾につながる可能性があります。

総根の並行性の問題に対処する方法は?

さまざまな戦略を使用して、楽観的なロックや悲観的なロックなどの総根での同時性の問題に対処することができます。ポリシーの選択は、アプリケーションの特定の要件と、あなたが直面している並行性の問題の性質に依存します。

この改訂された出力は、元の画像のフォーマットと場所を維持し、盗作を避けるためにテキストを言い換え、常にソースを適切に引用することを忘れないでください

以上が総根のコレクションの処理 - リポジトリパターンの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。