Upgrade to Pro — share decks privately, control downloads, hide ads and more …

クリーンアーキテクチャから見る依存の向きの大切さ

shimabox
February 21, 2025

 クリーンアーキテクチャから見る依存の向きの大切さ

PHPカンファレンス名古屋2025 の発表資料になります

shimabox

February 21, 2025
Tweet

More Decks by shimabox

Other Decks in Programming

Transcript

  1. 1. クリーンアーキテクチャと依存の向き • エンティティ ◦ ドメインモデル ◦ 最上位、一番内側 と言われる •

    ユースケース • インターフェイスアダプター • フレームワークとドライバー ◦ インフラストラクチャー ◦ 最下位、一番外側 と言われる クリーンアーキテクチャと言えば? 6
  2. レイヤー 説明 役割 エンティティ 企業のビジネスルール(方針)を表す。 業務そのもの。最重要。 • 方針 • ビジネスロジック

    ユースケース アプリケーション固有のビジネスルー ル。エンティティを組み合わせてやりた いことを達成する。 • 処理フローを管理 インターフェイス アダプター 外部と内部の仲介役。外部の要求を内部 に伝え、内部の結果を外部に返す。 • データのやり取りを橋 渡しする フレームワークと ドライバー フレームワーク, UI, DB, API, テスト, 外部ライブラリなど。技術詳細。 • 外部と連携 • 詳細(些細なもの) 1. クリーンアーキテクチャと依存の向き クリーンアーキテクチャと言えば? 7
  3. 1. クリーンアーキテクチャと依存の向き • エンティティ ◦ ドメインモデル のほうが馴染みある ◦ 最上位、一番内側 と言われる

    • ユースケース • インターフェイスアダプター • フレームワークとドライバー ◦ インフラストラクチャー のほうが馴 染みある ◦ 最下位、一番外側 と言われる 各レイヤーの話をしましたが... 8
  4. 1. クリーンアーキテクチャと依存の向き • エンティティ ◦ ドメインモデル のほうが馴染みある ◦ 最上位、一番内側 と言われる

    • ユースケース • インターフェイスアダプター • フレームワークとドライバー ◦ インフラストラクチャー のほうが馴 染みある ◦ 最下位、一番外側 と言われる 各レイヤーの話をしましたが... 9 “Only Four Circles?” “No, the circles are schematic. You may find that you need more than just these four. There’s no rule that says you must always have just these four. However, The Dependency Rule always applies. ” 「円は4つ?」 「いや、円はあくまで概念だよ。実際には4つ以上必要な場 合もあるし、4つだけじゃなきゃいけないって決まりはな い。ただし、依存性のルールだけはいつも守らなきゃね。」 https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html より意訳
  5. 1. クリーンアーキテクチャと依存の向き • エンティティ ◦ ちょうぜつだと、ドメインモデル ◦ 最上位、一番内側 と言われる •

    ユースケース • インターフェイスアダプター • フレームワークとドライバー ◦ ちょうぜつだと、インフラストラク チャー ◦ 最下位、一番外側 と言われる 依存の向きとは? これが依存の向き (依存性) 11
  6. 依存の向きが与える影響、関係性 状況 安定度 影響範囲 変更のしやすさ 依存している クラス 低い 依存先の影響を受ける 変更しやすい

    (変わる) 依存されている クラス 高い 依存元の影響を受けない 変更しにくい (変りにくい) 依存されている クラス (依存先) 1. クリーンアーキテクチャと依存の向き 依存している クラス (依存元) 12
  7. • Fooは安定度が低い • Barなどに変更が入るとFooに影響 が出る • FooはBarのことを知っている • FooはBarに依存する Baz

    Bar Foo ・・・ ❌ 変更 ❌ 影響 安定度が低いとは 1. クリーンアーキテクチャと依存の向き 14
  8. • Fooは安定度が高い • Barなどに変更が入ってもFooには 関係ない • FooはBarのことを知らない Baz Bar Foo

    ・・・ ❌ 変更 安定度が高いとは 1. クリーンアーキテクチャと依存の向き 16
  9. • 依存しているほうの変更は影響を 与えない ◦ 影響を与えないということは変更しや すい Baz Bar Foo ・・・

    ❌ 変更 依存の向きから見る影響 1. クリーンアーキテクチャと依存の向き 19
  10. ここまでをいったん整理 • 依存している ◦ 依存先の影響を受ける ▪ 安定度が低い ◦ 変更しても依存先に影響を与えない ▪

    変更しやすい • 依存されている ◦ 依存元の影響を受けない ▪ 安定度が高い ◦ 変更すると依存元に影響を与える ▪ なるべく変更したくない Bar Foo 依存している (依存元) 依存されている (依存先) 1. クリーンアーキテクチャと依存の向き 20
  11. ここまでをいったん整理 • 依存している (技術詳細) ◦ 依存先の影響を受ける ▪ 安定度が低い ◦ 変更しても依存先に影響を与えない

    ▪ 変更しやすい ◦ 変わる • 依存されている (ビジネスルール) ◦ 依存元の影響を受けない ▪ 安定度が高い ◦ 変更すると依存元に影響を与える ▪ なるべく変更したくない ◦ 変わりにくい 変わりにくい (ビジネスルール) 変わる (技術詳細) 依存している (依存元) 依存されている (依存先) 1. クリーンアーキテクチャと依存の向き 21
  12. 決済処理を行うSDK 2. 依存性逆転の原則(DIP) <?php namespace App\Vendor; /** * 決済SDK */

    class PaymentSDK { public function charge(float $amount, string $token): bool { // 決済処理(実装省略) return true; } } 28
  13. 2. 依存性逆転の原則(DIP) <?php namespace App\UseCase; use App\Vendor\PaymentSDK; readonly class OrderUseCase

    { public function __construct(private PaymentSDK $paymentSdk) { // App\Vendor\PaymentSDK に直接依存している } public function placeOrder(float $amount, string $token): void { $success = $this->paymentSdk->charge($amount, $token); if ($success) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } ユースケースが直接SDKに依存 29
  14. 2. 依存性逆転の原則(DIP) <?php require_once __DIR__ . '/../vendor/autoload.php'; use App\UseCase\OrderUseCase; use

    App\Vendor\PaymentSDK; $paymentSDK = new PaymentSDK(); $orderUseCase = new OrderUseCase($paymentSDK); $orderUseCase->placeOrder(1000, 'TOKEN'); // 注文処理に成功! 利用例 30
  15. 決済処理を行うSDKのバージョン2 2. 依存性逆転の原則(DIP) <?php namespace App\Vendor; /** * 決済SDKのバージョン2 *

    - メソッド名が charge() から pay() に変更 * - 戻り値も真偽値 (bool) ではなく、配列 (array) を返すように * - 例: ['success' => true, 'transaction_id' => 'txn_123456'] */ class PaymentSDK { public function pay(float $amount, string $token): array { // 決済処理(実装省略) // 配列が返るように変更された return [ 'success' => true, 'transaction_id' => 'txn_' . uniqid(), ]; } } 破壊的変更💣💥 • メソッド名の変更 • 戻り値の変更 32
  16. 2. 依存性逆転の原則(DIP) <?php namespace App\UseCase; use App\Vendor\PaymentSDK; readonly class OrderUseCase

    { public function __construct(private PaymentSDK $paymentSdk) { // App\Vendor\PaymentSDK に直接依存している } public function placeOrder(float $amount, string $token): void { $result = $this->paymentSdk->pay($amount, $token); if ($result['success'] ?? false) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } ユースケースを変更することになる 33
  17. 2. 依存性逆転の原則(DIP) <?php namespace App\UseCase; use App\Vendor\PaymentSDK; readonly class OrderUseCase

    { public function __construct(private PaymentSDK $paymentSdk) { // App\Vendor\PaymentSDK に直接依存している } public function placeOrder(float $amount, string $token): void { $result = $this->paymentSdk->pay($amount, $token); if ($result['success'] ?? false) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } ユースケースを変更することになる 34 技術詳細の変更で ビジネスルールに影響を 与えている
  18. 2. 依存性逆転の原則(DIP) <?php namespace App\UseCase; use App\Vendor\PaymentSDK; readonly class OrderUseCase

    { public function __construct(private PaymentSDK $paymentSdk) { // App\Vendor\PaymentSDK に直接依存している } public function placeOrder(float $amount, string $token): void { $result = $this->paymentSdk->pay($amount, $token); if ($result['success'] ?? false) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } ユースケースを変更することになる 35 • 要は「注文したら結 果が返ってくればい い」だけ • 振り回されたくない
  19. 2. 依存性逆転の原則(DIP) <?php namespace App\UseCase; use App\Vendor\PaymentSDK; readonly class OrderUseCase

    { public function __construct(private PaymentSDK $paymentSdk) { // App\Vendor\PaymentSDK に直接依存している } public function placeOrder(float $amount, string $token): void { $result = $this->paymentSdk->pay($amount, $token); if ($result['success'] ?? false) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } ユースケースを変更することになる 36 そこで 依存性逆転の原則 (DIP)
  20. ドメイン層に抽象と処理結果格納用クラスを用意 2. 依存性逆転の原則(DIP) <?php namespace App\Domain\Payment; interface PaymentInterface { public

    function processPayment( float $amount, string $token ): Result; } <?php namespace App\Domain\Payment; readonly class Result { public function __construct( private bool $success ) {} public function isSuccess(): bool { return $this->success; } } 39
  21. 2. 依存性逆転の原則(DIP) <?php namespace App\Infrastructure; use App\Domain\Payment\PaymentInterface; use App\Domain\Payment\Result; use

    App\Vendor\PaymentSDK; readonly class Payment implements PaymentInterface { public function __construct(private PaymentSDK $paymentSdk) {} public function processPayment(float $amount, string $token): Result { // 新バージョンのメソッド pay() に合わせた呼び出しをここで吸収 $result = $this->paymentSdk->pay($amount, $token); // 決済処理結果を生成して返す return new Result((bool)($result['success'] ?? false)); } } 抽象の実装をインフラストラクチャに用意 40
  22. 2. 依存性逆転の原則(DIP) <?php namespace App\Infrastructure; use App\Domain\Payment\PaymentInterface; use App\Domain\Payment\Result; use

    App\Vendor\PaymentSDK; readonly class Payment implements PaymentInterface { public function __construct(private PaymentSDK $paymentSdk) {} public function processPayment(float $amount, string $token): Result { // 新バージョンのメソッド pay() に合わせた呼び出しをここで吸収 $result = $this->paymentSdk->pay($amount, $token); // 決済処理結果を生成して返す return new Result((bool)($result['success'] ?? false)); } } 抽象の実装をインフラストラクチャに用意 41 • 外部ライブラリの処 理を吸収 • ドメインの型に変更 して返却
  23. 2. 依存性逆転の原則(DIP) <?php namespace App\UseCase; use App\Domain\Payment\PaymentInterface; readonly class OrderUseCase

    { public function __construct(private PaymentInterface $payment) {} public function placeOrder(float $amount, string $token): void { $result = $this->payment->processPayment($amount, $token); if ($result->isSuccess()) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } ユースケースは抽象に依存する 抽象に依存 42
  24. 2. 依存性逆転の原則(DIP) <?php namespace App\UseCase; use App\Domain\Payment\PaymentInterface; readonly class OrderUseCase

    { public function __construct(private PaymentInterface $payment) {} public function placeOrder(float $amount, string $token): void { $result = $this->payment->processPayment($amount, $token); if ($result->isSuccess()) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } ユースケースは抽象に依存する • 「注文したら結果が 返ってくればいい」 • 裏で何が行われてい るかには無関心 43
  25. 2. 依存性逆転の原則(DIP) <?php require_once __DIR__ . '/../vendor/autoload.php'; use App\Infrastructure\Payment; use

    App\UseCase\OrderUseCase; use App\Vendor\PaymentSDK; $paymentSDK = new PaymentSDK(); $payment = new Payment($paymentSDK); $orderUseCase = new OrderUseCase($payment); $orderUseCase->placeOrder(1000, 'TOKEN'); // 注文処理に成功! 利用例 44
  26. 2. 依存性逆転の原則(DIP) <?php require_once __DIR__ . '/../vendor/autoload.php'; use App\Infrastructure\Payment; use

    App\UseCase\OrderUseCase; use App\Vendor\PaymentSDK; $paymentSDK = new PaymentSDK(); $payment = new Payment($paymentSDK); $orderUseCase = new OrderUseCase($payment); $orderUseCase->placeOrder(1000, 'TOKEN'); // 注文処理に成功! 利用例 依存を生成して注入して いる(DI) 45
  27. <?php namespace App\UseCase; use App\Domain\Payment\PaymentInterface; readonly class OrderUseCase { public

    function __construct(private PaymentInterface $payment) {} public function placeOrder(float $amount, string $token): void { $result = $this->payment->processPayment($amount, $token); if ($result->isSuccess()) { echo "注文処理に成功!\n"; } else { echo "注文処理に失敗!\n"; } } } 再掲: 抽象に依存する • 「注文したら結果が 返ってくればいい」 • 裏で何が行われてい るかには無関心 3. クリーンアーキテクチャが伝えたいこと 54
  28. この考えはビジネスロジックにもあてはまる 3. クリーンアーキテクチャが伝えたいこと // CurrencyConverterクラス: 通貨換算処理 readonly class CurrencyConverter {

    public function convert(string $currency, float $amount): float { if ($currency === 'USD') { return $amount * 1.1; } elseif ($currency === 'EUR') { return $amount * 0.9; } // 新しい通貨が追加されるたびに条件が増える return $amount; } } CurrencyConverter 依存A (USD) 依存B (EUR) 依存C (?) • 対応通貨が増えるたびに Converterが修正されていく... • USDの修正なのに、EURに影響が でるかもしれない... • Converterを触るのが怖い... 58 CurrencyConverter
  29. 3. クリーンアーキテクチャが伝えたいこと // CurrencyConverterInterface: 通貨換算のためのイン ターフェース interface CurrencyConverterInterface { public

    function convert(float $amount): float; } // USD換算クラス: CurrencyConverterInterfaceを実装 class UsdConverter implements CurrencyConverterInterface { public function convert(float $amount): float { return $amount * 1.1; } } // EUR換算クラス: CurrencyConverterInterfaceを実装 class EurConverter implements CurrencyConverterInterface { public function convert(float $amount): float { return $amount * 0.9; } } readonly class CurrencyConverter { public function __construct(private CurrencyConverterInterface $converter) {} public function convert(float $amount): float { // 各通貨換算クラスが自身のconvertメソッドを // 実装しているため、条件分岐が不要 return $this->converter->convert($amount); } } // 使用例 $usdConverter = new UsdConverter(); $processor = new CurrencyConverter($usdConverter); echo $processor->convert(100); // 110 抽象に依存 60 変わるものに依存しない、抽象に依存する
  30. Q: 依存の制御が柔軟性と保守性にどうつながるのか 3. クリーンアーキテクチャが伝えたいこと A: • 柔軟性 ◦ ビジネスルールを守りつつ、技術の変 化に対応しやすくなる

    ◦ 新たな要求に対して柔軟に対応できる ようになる • 保守性 ◦ 技術的な変更の影響が局所化され問題 の特定がしやすくなる 61
  31. ただし、ドーナツは適切に 3. クリーンアーキテクチャが伝えたいこと A: • 柔軟性 ◦ ビジネスルールを守りつつ、技術の変 化に対応しやすくなる ◦

    新たな要求に対して柔軟に対応できる ようになる • 保守性 ◦ 技術的な変更の影響が局所化され問題 の特定がしやすくなる 62
  32. • Clean Architecture 達人に学ぶソフトウェアの構造と設計 ◦ https://www.amazon.co.jp/dp/B07FSBHS2V • ちょうぜつソフトウェア設計入門 ◦ https://www.amazon.co.jp/dp/B0BNH1J2W2

    • アーキテクトの教科書 価値を生むソフトウェアのアーキテクチャ構築 ◦ https://www.amazon.co.jp/dp/4798184772 • Software Design 2023年6月号 ◦ クリーンアーキテクチャとは何か? ◦ https://gihyo.jp/magazine/SD/archive/2023/202306 • smeghead/php-class-diagram ◦ https://github.com/smeghead/php-class-diagram 参考資料 65