明日から使えるアーキテクチャ 独立したコアレイヤパターン / independent-core-layer-pattern-phpconsen2019

Ca17a082a30f4cbfed1d0a6dacbe3af2?s=47 shin1x1
January 26, 2019

明日から使えるアーキテクチャ 独立したコアレイヤパターン / independent-core-layer-pattern-phpconsen2019

PHP カンファレンス仙台 2019

Ca17a082a30f4cbfed1d0a6dacbe3af2?s=128

shin1x1

January 26, 2019
Tweet

Transcript

  1. 明日から使えるアーキテクチャ 独立したコアレイヤパターン 2019/01/26 PHP カンファレンス仙台 2019 @shin1x1

  2. https://speakerdeck.com/shin1x1/phpcon2018- independent-core-layer-pattern

  3. Agenda 独立したコアレイヤパターン 既存アプリケーションへの適用 モチベーション

  4. 独立したコアレイヤパターン

  5. 独立したコアレイヤパターンとは https://blog.shin1x1.com/entry/independent- core-layer-pattern アーキテクチャパターン 2 つのシンプルなルール コアレイヤとアプリケーションレイヤに分ける コアレイヤはコアレイヤのみに依存する

  6. None
  7. コアレイヤ コアロジック(What )の実装 Plain PHP で実装 フレームワークに依存しない データ構造や操作などの一部ライブラリは利用

  8. アプリケーションレイヤ 利用技術(How )の実装 データベース、外部 API 等の外部リソース操作 フレームワーク、ライブラリを活用 コアレイヤの実行(HTTP, CLI など)

  9. None
  10. 課題 コアレイヤがアプリケーションレイヤに依存 Eloquent が変わるとコアレイヤも変更が必要

  11. コアレイヤ インターフェイス コアレイヤで必要な外部リソースの操作を インターフェイスで定義 データベース、メール、外部 API など 実装はアプリケーションレイヤで行う DIP を利用

    依存関係逆転の原則( SOLID 原則の D )
  12. UseCase は UseCasePort に依存 Eloquent の変化の影響を受けない アプリケーションレイヤで UseCasePort を実装

  13. Eloquent が PDO に変わっても良い

  14. フレームワークが CakePHP になっても コアレイヤは影響を受けない

  15. 既存実装への適用

  16. https://github.com/shin1x1/phpconsen2019

  17. 実装 API 顧客サービスのポイント加算 API 顧客情報 = customers テーブル 顧客ポイント =

    customer_points テーブル 対象顧客に顧客ポイントを加算 PUT /customers/add_point customer_id = 顧客 ID add_point = 加算ポイント 更新後のポイントを JSON で返す
  18. curl で API 実行 $ curl "http://localhost:8000/api/customers/add_point" -X PUT -d

    '{"customer_id": 1, "add_point": 1}' -H "Content-Type: application/json" -H "Accept: application/json" | jq . { "customer_point": 101 }
  19. ユースケース 事前条件 加算ポイントが 1 以上の整数である 顧客 ID がデータベースに存在する 顧客ポイントを加算 更新後の顧客ポイントを返す

  20. routes/api.php (ルーティング) <?php use App\Http\Actions\AddPoint\AddPointAction; $router->put('/customers/add_point', AddPointAction::class);

  21. AddPointAction <?php namespace App\Http\Actions\AddPoint; (snip) class AddPointAction { private $customer;

    private $customerPoint; public function __construct( EloquentCustomer $customer , EloquentCustomerPoint $customerPoint ) { $this->customer = $customer; $this->customerPoint = $customerPoint; }
  22. public function __invoke(AddPointRequest $request): JsonResp { $customerId = filter_var( $request->json('customer_id'),

    FILTER_VALIDATE_INT); $addPoint = filter_var( $request->json('add_point'), FILTER_VALIDATE_INT); // 事前条件の検証 if ($addPoint <= 0) { throw new DomainRuleException('add_point should be e } if (!$this->customer->existsId($customerId)) { $message = sprintf('customer_id:%d does not exists' throw new DomainRuleException($message); }
  23. // ポイント加算 $this->customerPoint->addPoint( $customerId, $addPoint ); // 加算ポイントの取得 $customerPoint =

    $this->customerPoint ->findPoint($customerId); return response()->json([ 'customer_point' => $customerPoint ]); } }
  24. 独立したコアレイヤパターンの適用 [core] コアロジックの抽出 [core] コアロジックで必要なインターフェイスを定 義 [app] アダプタの実装 [app] コアロジックの実行

  25. [core] コアロジックの抽出 コアロジックをユースケースクラスに移動 簡易的には、Action から HTTP に関連する処理 以外を移動 HTTP レスポンスに必要な値を返す

  26. [core] AddPointUseCase public function run(int $customerId, int $addPoint): int {

    if ($addPoint <= 0) { throw new DomainRuleException( 'add_point should be equals or greater than 1'); } if (!$this->customer->existsId($customerId)) { $message = sprintf( 'customer_id:%d does not exists', $customerId); throw new DomainRuleException($message); } $this->customerPoint->addPoint($customerId, $addPoint); return $this->customerPoint->findPoint($customerId); }
  27. AddPointUseCasePort interface AddPointUseCasePort { public function existsId(int $customerId): bool; public

    function findPoint(int $customerId): int; public function addPoint( int $customerId, int $addPoint): void; }
  28. [core] AddPointUseCase インターフェイス実装のインスタンスに与える run メソッドで $port を使うように変更 final class AddPointUseCase

    { /** @var AddPointUseCasePort */ private $port; public function __construct( AddPointUseCasePort $port) { $this->port = $port; }
  29. [core] ユースケーステスト モックを与えればコアレイヤのみでテストできる /** * @test */ public function run_()

    { $useCase = new AddPointUseCase( $this->mockAdapter, ); $actual = $useCase->run(1, 100); $this->assertSame(200, $actual); }
  30. [app] アダプタの実装 定義したインターフェイスをアダプタで実装 Eloquent を利用してデータベースアクセス 実装方法は何でも良い QueryBuilder or PDO or

    pg_*()
  31. final class AddPointAdapter implements AddPointPort { // (snip) public function

    existsId(int $customerId): bool { return $this->customer->existsId($customerId); } public function findPoint(int $customerId): int { return $this->customerPoint ->findPoint($customerId); } public function addPoint( int $customerId, int $addPoint): void { $this->customerPoint->addPoint( $customerId, $addPoint); } }
  32. [app] サービスコンテナに登録 ユースケースクラスのコンストラクタに アダプタを与えるようにサービスコンテナに登録 public function register(): void { $this->app->bind(AddPointUseCase::class,

    function () { $adapter = $this->app->make( AddPointAdapter::class); return new AddPointUseCase($adapter); }); }
  33. [app] ユースケースを利用 アクションからユースケースを実行 HTTP の入出力とユースケース実行のみを担う

  34. [app] AddPointAction public function __invoke(AddPointRequest $request): JsonResponse { $customerId =

    filter_var( $request->json('customer_id'), FILTER_VALIDATE_INT); $addPoint = filter_var( $request->json('add_point'), FILTER_VALIDATE_INT); // ユースケース実行 $customerPoint = $this->useCase->run( $customerId, $addPoint); return response()->json( ['customer_point' => $customerPoint]); }
  35. モチベーション

  36. What と How の分離 How の更新の影響を What が受けにくくなる フレームワークバージョンアップ等 What

    が明確になる 要件がコードから読み取れる テストがしやすい レビューの視点が明確になる How は定められた役割に専念できる
  37. 既存パターン ドメインを中心にしたアーキテクチャ レイヤードアーキテクチャ クリーンアーキテクチャ 有用なパターンだが、too much に感じる時も アレンジして使っている現場も多い レイヤ構造とドメイン分析 それぞれ独立した話

  38. 既存パターンとの違い シンプルで適用しやすいルールに絞る これさえ守れば ok というもの レイヤ構造のみ モデリング云々は含まない DDD + コアレイヤパターン

    クリーンアーキテクチャの一種とも言える Simpli ed Clean Architecture
  39. 機械的に分離できる 既存アプリケーションを機械的に分離 コアロジックを抽出 外部リソース依存をインターフェイス化 インターフェイスをアダプタで実装 コアロジックの呼び出し レイヤ分けや複数のアダプタをどうまとめるか などを試行錯誤する場面はある

  40. さらなるアイデア コアレイヤの依存を CI でチェック コアレイヤ以外に依存していればエラー コアレイヤを Composer パッケージ化 アプリケーションは別パッケージとして読む コアレイヤの

    DSL 化 より抽象化して、PHP 以外の言語でも動作
  41. さいごに レイヤ分けを身近なものに スーパーヒーローがいなくても使える 簡単な API のリファクタリングから 自分たちのコードでやると分かりやすい ハンズオンとかもやりたい

  42. Q? @shin1x1