コンテンツにスキップ

インフラストラクチャ層@クリーンアーキテクチャ

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. インフラストラクチャ層

記入中...


02. リポジトリ

Repository

リポジトリパターンとは

デザインパターンの一種。

一例として、以下の関数を持つ。

具体的な実装については、インターフェースリポジトリの実装を参考にせよ。

CREATE 処理と UPDATE 処理を SAVE 処理としてまとめてもよい。

関数名 引数型 返却値型 処理内容
findById() Id 型 ドメインモデル型 ルートエンティティのドメインモデルを取得する。
findAll() なし ドメインモデル型を持つ配列 すべてのドメインモデルを取得する。
findAllByCriteria() Criteria 型 ドメインモデル型を持つ配列 条件に合致したすべてのドメインモデルを取得する。
findWith<異なる境界づけられたコンテキストにあるほかの集約名>() 異なる境界づけられたコンテキストにあるほかの集約 ドメインモデル型 異なる境界づけられたコンテキストにある集約を特定のアプリケーションで扱うことはよくないが、扱わないといけない場合に使用する。異なる境界づけられたコンテキストにあるほかの集約を含むルートエンティティのドメインモデルを取得する。特定の集約 A に紐づくリポジトリでほかの集約 B を取得したい場合、集約 B を直接取得するのはよくない。集約 A と B 間のリレーションを使用し取得することで、取得する集約 A の主体はそのままで、関連する集約 B も一緒に取得できる。取得した集約 A から集約 B を取り出し、必要なデータを操作する。
create() ルートエンティティのドメインモデル型 void 型 ルートエンティティのドメインモデルを作成する。
update() ルートエンティティのドメインモデル型 void 型 ルートエンティティのドメインモデルを更新する。
save() (upsert()) ルートエンティティのドメインモデル型 void 型 ルートエンティティのドメインモデルを作成/更新する。SELECT 文の IN 句を使用して、同じ識別子のエンティティを DB から読み込めるか否かを確認する。取得できない場合は、更新処理を実行する。<br>https://github.com/little-hands/ddd-q-and-a/issues/241<br>https://github.com/little-hands/ddd-q-and-a/issues/129
delete() Id 型 void 型 ルートエンティティのドメインモデルを削除する。

他の類似するデザインパターンとの比較

デザインパターン 駆動の種類 ドメインモデルとテーブルの関連度合い 採用パッケージ例 適所 補足
Active Record DB 駆動 ・非常に強い。<br>・手順としてテーブル設計が先にあり、1 個のドメインモデルが 1 個のテーブルに対応している。<br>・テーブル間のリレーションシップによって、ドメインモデル間の依存関係が決まる。 ・Eloquent (PHP)<br>・Active Record (Ruby) <br>・Hibernate (Java) ビジネスロジックが複雑でないアプリケーション <br>https://www.informit.com/articles/article.aspx?p=1398618&seqNum=3 DataMapper パターンと同じく、ORM の実装方法の 1 つである。<br>https://culttt.com/2014/06/18/whats-difference-active-record-data-mapper/
Data Mapper ドメイン駆動 ・弱い <br>・Entity マネージャを使用して、ドメインモデルを DB に永続化する。 Doctrine ビジネスロジックが複雑なアプリケーション <br>https://www.informit.com/articles/article.aspx?p=1398618&seqNum=3 ActiveRecord パターンと同じく、ORM の実装方法の 1 つである。<br>https://culttt.com/2014/06/18/whats-difference-active-record-data-mapper/
Repository ドメイン駆動 ・弱い <br>・手順としてドメインモデルの依存関係の設計が先にあり、テーブル間の関係性は自由である。1 個のドメインモデルが複数のテーブルを参照してもよい。 ビジネスロジックが複雑なアプリケーション DB、RDBMS、NoSQL、なんでもでもよい。
なし なし 非常に弱い DB ファサード


リポジトリの粒度

操作するルートエンティティの単位でリポジトリを実装する。


実装リポジトリ

リポジトリパターンを使用する。

責務として、DB に対してデータの書き込み/読み出しのトランザクション処理を実行する。

トランザクションはルートエンティティを単位として定義する必要があるため、リポジトリも同じくルートエンティティを単位として定義づけることになる。

そのため、引数の型はルートエンティティのドメインモデル型になる。

リポジトリではルートエンティティを意識して実装する必要がある一方で、DB のどのテーブルにデータが存在しているかを問わない。

これにより、ルートエンティティとテーブルを別々に設計できる。


インターフェースリポジトリ

依存性逆転の原則を導入する場合、『ドメイン層』にインターフェースリポジトリを配置する。

インフラストラクチャ層の実装リポジトリクラスと対応関係にある。

*実装例*

<?php

declare(strict_types=1);

namespace App\Domain\Foo\Repositories;

use App\Domain\Foo\Criterion\FooCriteria;
use App\Domain\Foo\Entities\Foo;
use App\Domain\Foo\Ids\FooId;
use App\Domain\Repository;

interface FooRepository extends Repository
{
    /**
     * @param FooId $fooId
     * @return Foo
     */
    public function findById(FooId $fooId): Foo;

    /**
     * @return array
     */
    public function findAll(): array;

    /**
     * @param FooCriteria $criteria
     * @return array
     */
    public function findAllByCriteria(FooCriteria $criteria): array;

    /**
     * @param Foo $foo
     * @return void
     */
    public function create(Foo $foo): void;

    /**
     * @param Foo $foo
     * @return void
     */
    public function update(Foo $foo): void;

    /**
     * @param FooId $fooId
     * @return void
     */
    public function delete(FooId $fooId): void;
}

DB に対する書き込み責務 (CREATE/UPDATE/DELETE)

ドメイン駆動設計_リポジトリ_データ更新

DB に対する書き込み操作をする。

(1)

GET/POST によって、ユースケース層から値を送信する。

(2)

ファクトリによって、送信された値からエンティティや値オブジェクトを作成する。加えて、それらからルートエンティティを作成する。

(3)

リポジトリにルートエンティティを渡す。

(4)

ルートエンティティをレコードとして DB に挿入する。

*実装例*

CREATE 処理のため、Laravel の insert() 関数を実行する。

<?php

namespace App\Infrastructure\Foo\Repositories;

use App\Domain\Dog\Entities\DogToy;
use Illuminate\Support\Facades\DB;

/**
 * 犬用おもちゃリポジトリ
 */
class DogToyRepository
{
    /**
     * ドメインモデルを作成する
     */
    public function create(DogToy $dogToy): void
    {
        DB::table("dog_toy_table")->insert([
            // ルートエンティティの要素をカラム値として設定する。
            // IDは自動増分
            "name"  => $dogToy->getName()->value(),
            "type"  => $dogToy->getType()->value(),
            "price" => $dogToy->getPriceVO()->value(),
            "color" => $dogToy->getColorVO()->value(),
        ]);
    }
}

UPDATE 処理のため、Laravel の update() 関数を実行する。

<?php

namespace App\Infrastructure\Foo\Repositories;

use App\Domain\Dog\Entities\DogToy;
use Illuminate\Support\Facades\DB;

/**
 * 犬用おもちゃリポジトリ
 */
class DogToyRepository
{
    /**
     * ドメインモデルを更新する
     */
    public function update(DogToy $dogToy): void
    {
        DB::table("dog_toy_table")
            // ルートエンティティの要素をカラム値として設定する。
            ->where("id", $dogToy->getId()->value())
            ->update([
                "name"  => $dogToy->getName()->value(),
                "type"  => $dogToy->getType()->value(),
                "price" => $dogToy->getPriceVO()->value(),
                "color" => $dogToy->getColorVO()->value(),
            ]);
    }
}

DELETE 処理 (論理削除) のため、Laravel の update() 関数を実行する。

<?php

namespace App\Infrastructure\Foo\Repositories;

use App\Constants\FlagConstant;
use App\Domain\Foo\Ids\ToyId;
use Illuminate\Support\Facades\DB;

/**
 * 犬用おもちゃリポジトリ
 */
class DogToyRepository
{
    /**
     * ドメインモデルを削除する
     */
    public function delete(ToyId $toyId): void
    {
        DB::table("dog_toy_table")
            // 論理削除
            ->where("id", $toyId->value())
            ->update([
                "is_deleted" => FlagConstant::IS_ON,
            ]);
    }
}

DB に対する読み出し責務 (Read)

ドメイン駆動設計_リポジトリ_データ取得

DB に対する書き込み操作をする。

(1)

ユースケース層から集約がリクエストされる。

(2)

DB からレコードを読み込む。

(3)

ファクトリによって、レコードからエンティティや値オブジェクトを作成する。

(4)

リポジトリからルートエンティティを返却し、ユースケース層に渡す。

*実装例*

READ 処理のため、Doctrine の QueryBuilder クラスの select() 関数を実行する。

<?php

namespace App\Infrastructure\Foo\Repositories;

use App\Constants\FlagConstant;
use App\Domain\Dog\Entities\DogToy;
use Doctrine\DBAL\Query\QueryBuilder;

/**
 * 犬用おもちゃリポジトリ
 */
class DogToyRepository
{
     /**
     * ドメインモデルを全て取得する
     */
    public function findAll(): array
    {
        // クエリビルダー作成
        $query = $this->createQueryBuilder();

        // SQLを設定する。
        $query->select(
            "dog_toy.id    AS dog_toy_id",
            "dog_toy.name  AS dog_toy_name",
            "dog_toy.type  AS dog_toy_type",
            "dog_toy.price AS dog_toy_price",
            "dog_toy.color AS dog_toy_color"
        )
        ->from("dog_toy_table", "dog_toy")
        // 論理削除されていないもののみ
        ->where("dog_toy.is_deleted", FlagConstant::IS_OFF)
        ->getQuery();

        // Read処理を実行する。
        $entities = $query->getResult();

        $dogToys = [];
        foreach($entities as $entity){
            // 取得したエンティティをドメインモデルに変換する。
            $dogToys[] = $this->toDogToy($entity);
        }

        return $dogToys;
    }

    /**
     * ドメインモデルに変換する
     */
    private function toDogToy(array $entity): DogToy
    {
        $dogToy = new DogToy(
            new DogId($entity["dog_toy_id"]),
            new DogName($entity["dog_toy_name"]),
            new DogToyType($entity["dog_toy_type"]),
            new PriceVO($entity["dog_toy_price"],
            new ColorVO($entity["dog_toy_color"]
        );

        return $dogToy;
    }
}


テストリポジトリ

ホワイトボックスのテスト時に、実装リポジトリを静的な値を返信するインメモリなリポジトリに差し替えると、DB を使用せずにホワイトボックステストを実施できる。

ただ、ホワイトボックステスト時には、DB コンテナなどの一時的な DB を使用すれば、実装リポジトリをそのまま使用できる。


03. ファクトリ

ファクトリとは

責務として、新たな集約の作成や、既存の集約の再作成をする。

*実装例*

<?php

namespace App\Infrastructure\Foo\Factories;

use App\Domain\Dog\Entities\DogToy;
use App\Domain\Foo\Entities\DogFood;
use App\Domain\Foo\Entities\DogCombo;

/**
 * 犬用コンボファクトリ
 */
class DogComboFactory
{
    /**
     * 新たな集約を作成する
     */
    public static function createDogCombo($data): DogItem
    {
        return new DogCombo(
            new DogToy(
                $data["dog_toy_id"],
                $data["dog_toy_name"],
                $data["dog_toy_type"],
                $data["dog_toy_price"],
                $data["dog_toy_color"],
            ),
            new DogFood(
                $data["dog_food_id"],
                $data["dog_food_name"],
                $data["dog_food_type"],
                $data["dog_food_price"],
                $data["dog_food_flavor"],
            )
        );
    }
}


04. Web API

Web API とは

コントローラーにリクエストをルーティングする。


05. ミドルウェア

ミドルウェア処理とは

コントローラーの処理前に実行する BeforeMiddleware と、コントローラーとビューの処理後に実行する AfterMiddleware がある。

design-pattern_middleware


06. キュー操作処理

キュー操作処理とは

メッセージキューを操作する。

Saga パターンのクライアント


07. イベントリスナー (イベントハンドラー)

イベントリスナーとは

『イベントハンドラー』ともいう。

ドメインイベントが発生した場合、それに紐付く処理を実行する。

フレームワークの機能に依存するため、実装の詳細をインフラストラクチャ層へ置く。

命名規則

イベントでリスナーを使い回さずに、各イベントごとにリスナーを作成する。

そのため、名前は『イベント名』+ Listener (Handler) となる。


08. インフラサービス

インフラサービスとは

インフラストラクチャ層のなかで、汎用的なロジックが切り分けられたもの。

実装リポジトリと同様にして、ドメイン層にストラクチャサービスのインターフェースを設け、依存性逆転の原則を満たせるようにする。

ロギング

ファイル出力

ハッシュ化

パスワードのハッシュ化。


その他

gRPC の proto ファイルはインフラストラクチャ層に配置するとよい。