2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-10-16

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

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

二つの違い

まず、文言で二つの違いをおさらい

クリーンアーキテクチャ
クリーンアーキテクチャは円構造、以下の4つのレイヤーを持つ

  • エンティティ層
     アプリケーションのビジネスルールや状態を表すモデル
  • ユースケース層
     特定のユースケースに従ってエンティティを操作する
  • インターフェース層
     外部への入出力(インフラ、UI)とのやり取り
  • インフラ層
     DB、ファイルシステム、外部サービスとの接続

オニオンアーキテクチャ
同じくレイヤー構造を持つが、ドメインモデル(ビジネスルール)を中心に捉えた構造

  • ドメインモデル
     ビジネスのコアになる。エンティティとドメインサービスを含む
  • アプリケーションサービス
     ユースケースやアプリケーションサービスが属し、ドメインモデルを利用してビジネスロジックを実行
  • 外部依存層
     レポジトリや外部のインフラに関する依存が存在し、最外層に配置

次に各アーキテクチャの図を見てみる。

cleanarch.jpg

onionarch.png

さて、二つのアーキテクチャ別でソースを記載してみる。

ソースサンプル

前回 みたいに簡単すぎるサンプルだと差異がないじゃん…ってなりそうなので、ECサイトを例にして書いてみる。

クリーンアーキテクチャ

<?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 "確認メールを送信しました。";
    }
}

特徴

  • ビジネスロジックがユースケースに集中し、エンティティは単純なデータ保持や簡単なロジックを持つ。
  • 各ユースケースに応じて、複数のサービスを利用してビジネスロジックを実行する

オニオンアーキテクチャ

<?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 "確認メールを送信しました。";
    }
}

特徴

  • ビジネスロジックはドメインサービスで実行され、注文作成に必要な処理がサービスに集約される。
  • エンティティはデータを保持するだけで、ロジック自体はドメインサービスにある

サンプルソースでも書いたけど、オニオンアーキテクチャのアプリケーション層がインフラ層に依存するのは許される範囲らしい。クリーンアーキテクチャに慣れた自分からするとマジかよって感じだけど。
大事なのはドメイン層が内側から外側に依存しない作りって事。

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

まとめるとこうなる。

クリーンアーキテクチャ

  • ユースケース層がビジネスロジックを担い、その下にエンティティ層(いわゆるドメイン)がある
  • ユースケースはエンティティを操作する必要があるので、エンティティより上位の層にある

オニオンアーキテクチャ

  • ドメインモデル(エンティティやサービス)が中心になる
  • ドメインモデルが他の全ての層に依存しない
  • 他のレイヤーはドメインのサポートをするために存在する

他にもいろいろ書きたい事あるけど、とりあえず。
DTO、VOについても書きたい。クリーンアーキテクチャなのに外側の層に属するハズのDTOをアプリケーションサービス(ユースケース層)に普通に投げ入れている実装をたまに見かけるので。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?