クリーンアーキテクチャとオニオンアーキテクチャの違い

ドメイン駆動設計で一緒に用いられるアーキテクチャ、奥が深い。
そりゃ皆が色々本を出すわけですよ…。

今回は表題の通り、クリーンアーキテクチャとオニオンアーキテクチャの違い、二つのアーキテクチャ名はよく見るけど、ソースで見たら具体的にどう変わるの？ってのを調べたのでメモ。

# 二つの違い

まずは有名な各アーキテクチャの図を見てみる。

![cleanarch.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39934/7b913760-6234-8c2a-f6ea-0b291b4166c0.jpeg)

![onionarch.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/39934/cd52e2d0-73b7-fab2-a354-4bda08d1002c.png)

次に、文言で二つの違いを書いてみる

:::note info
<b>クリーンアーキテクチャ</b>
クリーンアーキテクチャは円構造、以下の4つのレイヤーを持つ
* <b>エンティティ層</b>
　アプリケーションのビジネスルールや状態を表すモデル
* <b>ユースケース層</b>
　特定のユースケースに従ってエンティティを操作する
* <b>インターフェース層</b>
　外部への入出力（インフラ、UI）とのやり取り
* <b>インフラ層</b>
　DB、ファイルシステム、外部サービスとの接続
:::
図にEntitiesと書かれているのでEntityしか入れちゃダメなの？となりそうだがそういう訳ではない事に注意。
Entitiesに入れて良いものは以下の通り
| 名前 | 理由 |
| --- | --- |
| Entity | 識別子を持ちドメイン内で変化する物（ユーザー情報等） |
| ValueObject | 振る舞いと等価性で表現される値 |
| DomainService | 1つのEntityだけでは表現できないドメインロジック |
| ドメインに属するinterface | 外部に依存しない抽象化 |
| ポリシーに関するinterface | 認可・判断などのドメイン的な抽象ルール |

Q. 図には `Interface Adapter`って書かれてますけど？
A. このInterface Adapterってのは、interfaceの実装クラスです。

具体的にはこんな感じ。ValueObject(VO)も使っていいものとして書いたのでVOを使ったサンプル
```php
// Domain層（ValueObject）
final class UserId
{
    private string $value;

    public function __construct(string $value) {
        // UUID形式などをバリデーションしても良い
        $this->value = $value;
    }

    public function equals(UserId $other): bool {
        return $this->value === $other->value;
    }

    public function getValue(): string {
        return $this->value;
    }
}

// Domain層
interface IUserRepository
{
    public function getUserId(UserId $userId): ?User;
    public function save(User $user): bool;
}

// Interface Adapter(インフラ層)
class UserRepository implements IUserRepository
{
    public function getUserId(UserId $userId): ?User
    {
        $userInfo = DB::table('users')->findById($userId->getValue();

        if( is_null($userInfo) ) return null;

        return new User(
            new UserId($userIndo->id),
            ...ダルいので略
        );
    }

    public function save(User $user): bool
    {
        // 手抜き
        // !!は結果を2回NOT(結果が0 ->TRUE->FALSE, 結果が1以上 ->FALSE->TRUE)へ
        return !!(new App\Models\User())->fill($user->toArray())->save();
    }
}
```
VOはドメイン層のクラスなので「ドメイン知識の流出だ！」ってなるかもだけど、外側から内側に依存してる分には問題ないです念のため。気になるならVOもinterface書けば？（投げやり）

クリーンアーキテクチャ一つで本が一冊書けるくらい
めんどくｓ
情報があるのでとりあえずそろそろオニオンアーキテクチャについて書く。

:::note info
<b>オニオンアーキテクチャ</b>
同じくレイヤー構造を持つが、ドメインモデル（ビジネスルール）を中心に捉えた構造

* <b>ドメインモデル</b>
　ビジネスのコア。ドメインというキーワード自体はドメインサービスも含むがここはinterfaceやEntityなどが焦点。
* <b>ドメインサービス</b>
　ビジネスに関するロジック処理を記述する層
* <b>アプリケーションサービス</b>
　ユースケースやアプリケーションサービスが属し、ドメインモデルを利用してビジネスロジックを実行
* <b>外部依存層</b>
　レポジトリや外部のインフラに関する依存が存在し、最外層に配置
:::

こっちは明確にドメインが大きく二つに分かれてる。
結論的な事は最後にしたかったけど、ここで書かないといけなくなりそうなので、とりあえず、一回ソース書く。

# ソースサンプル
[前回](https://qiita.com/remi_ts/items/9bed4d1a4ad224354c41) みたいに簡単すぎるサンプルだと差異がないじゃん…ってなりそうなので、ECサイトを例にして書いてみる。

## クリーンアーキテクチャ

```php
<?php
// エンティティ（ドメイン層）
namespace Package\Domain\Entity;

class Order {
    private $items = [];

    public function addItem($item): void {
        $this->items[] = $item;
    }

    public function getItems(): array {
        return $this->items;
    }
}
//----------------------------------------------------------------------
// ユースケース層
namespace Package\UseCase;

class CreateOrderUseCase {
    public function __construct(
        private InventoryChecker      $inventoryService,
        private EmailServiceInterface $emailService
    ) {
    }

    public function execute($order) {
        // 在庫確認
        foreach ($order->getItems() as $item) {
            if (!$this->inventoryService->checkStork($item)) {
                throw new Exception('在庫不足');
            }
        }
        // その他、注文処理（決済処理、在庫数変更など）

        // 注文処理成功後にメール送信
        $this->emailService->send($order);
    }
}
//----------------------------------------------------------------------
/* これらはシステムの機能を提供するサービスのインターフェース。
 * アプリケーションロジックやインフラロジックに該当する。
 * クリーンアーキテクチャの場合、このインターフェースはドメイン層かアプリケーション層に属する
 * （例：エンティティが参照するサービスならドメイン層）
 */
namespace Package\Application\Service;

interface InventoryChecker {
    public function checkStock($item): bool;
}


interface SendConfirmationInterface {
    public function send($order): bool;
}
//----------------------------------------------------------------------
// インフラ層
namespace Package\Infra;

class InventoryService implements InventoryChecker{
    public function checkStock($item): bool {

        // 在庫確認ロジック
    
        return true;
    }
}

class EmailService implements SendConfirmationInterface {

    public function send($order): bool {

        // メール送信処理

        echo "確認メールを送信しました。";
    }
}
```
:::note warn
<b>特徴</b>
* ビジネスロジックが<b>ユースケースに集中</b>し、エンティティは単純なデータ保持(<b>Entity, ValueObject</b>)や簡単なロジック(<b>interface</b>)を持つ。
* 各ユースケースに応じて、複数のサービスを利用してビジネスロジックを実行する
:::

## オニオンアーキテクチャ

```php
<?php
// ドメイン層
namespace Package\Domain\Entity;

class Order {
    private $items = [];

    public function addItem($item): void {
        $this->items[] = $item;
    }

    public function getItems(): array {
        return $this->items[];
    }
}
//----------------------------------------------------------------------
// ドメインサービス（ビジネスロジック）
namespace Package\Domain\Service;

class OrderService {
    public function __construct(
        private InventoryChecker          $inventoryService,
        private SendConfirmationInterface $emailService
    ) {
    }

    public function createOrder(Order $order) {
        // 在庫チェック
        foreach ($order->getItems() as $item) {
            if (!$this->inventoryService->checkStock($item)) {
                throw new Exception('在庫不足');
            }
        }

        // その他、注文処理（決済処理、在庫数変更など）
        
        $this->emailService->send($order);
    }
}
//----------------------------------------------------------------------
// アプリケーションサービス層
namespace Package\Application\Service;

use Package\Domain\Service\OrderService;
use Package\Infra\UnitOfWork; // アプリケーション層がインフラ層に依存するのは許容される範囲
use Package\Domain\Repository\OrderRepositoryInterface; //ホントはこうする

class CeateOrderApplicationService {
    public function __construct(
        private OrderService $orderService,
        private OrderRepositoryInterface $orderRepository,
        private UnitOfWork $unitOfWork
    ){
    }

    public function execute(CreateOrderRequest $request): CreateOrderResponse {
        $this->unitOfWork->beginTransaction();
        try {
            // ビジネスロジックを呼び出す
            $order = $this->orderService->createOrder($request->items);

            // 注文の保存
            $this->orderRepository->save($order);

            // トランザクションのコミット
            $this->unitOfWork->commit();

            return new CreateOrderResponse($order);
        } catch (Exception $e) {
            $this->unitOfWork->rollback();
            throw $e;
        }
    }

}

//----------------------------------------------------------------------
/* これらはシステムの機能を提供するサービスのインターフェース。
 * アプリケーションロジックやインフラロジックに該当する。
 * オニオンアーキテクチャではこれらのインターフェースはドメイン層に属する
 */
namespace Package\Application\Service;

interface InventoryChecker {
    public function checkStock($item): bool;
}


interface SendConfirmationInterface {
    public function send($order): bool;
}
//----------------------------------------------------------------------
// インフラ層
namespace Package\Infra;

class InventoryService implements InventoryChecker{
    public function checkStock($item): bool {

        // 在庫確認ロジック
    
        return true;
    }
}

class EmailService implements SendConfirmationInterface {
    public function send($order): bool {
        // メール送信処理

        echo "確認メールを送信しました。";
    }
}
```

:::note warn
<b>特徴</b>
* ビジネスロジックはドメインサービスで実行され、注文作成に必要な処理がサービスに集約される。
* エンティティはデータを保持するだけで、ロジック自体(<b>interface, Service</b>)はドメインサービスにある
:::

ざっくり書くと、クリーンアーキテクチャはユースケース単位でクラスを作成し、ユースケース層でEntityの生成や操作、永続化を行う。
ドメインサービスを無駄に作ろうとしない。誤解を恐れず書くと、基本的にユースケースに書けばいいじゃんって考え方。

オニオンアーキテクチャはアプリケーションサービスにはUserManagerみたいな、ある情報を管理するクラスがあって、そのクラスが登録だったり更新だったりのサービス（メソッド）を提供していて、そのクラスは更にドメインサービスという機能を複数呼び出したり組み合わせてサービスを提供している感じ。

そしてサンプル見て気付いた方はいると思うけど、オニオンアーキテクチャはドメインサービスとドメインモデルが外側に依存する事は絶対禁止である一方、アプリケーションサービスが外側の層に依存するのは許容される範囲（その分載せ替えの時大変だけど）

### 二つのアーキテクチャの違い


他にもいろいろ書きたい事あるけど、とりあえず。