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

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

shin1x1
January 26, 2019

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

PHP カンファレンス仙台 2019

shin1x1

January 26, 2019
Tweet

More Decks by shin1x1

Other Decks in Technology

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  9. コアレイヤ インターフェイス
    コアレイヤで必要な外部リソースの操作を
    インターフェイスで定義
    データベース、メール、外部 API
    など
    実装はアプリケーションレイヤで行う
    DIP
    を利用
    依存関係逆転の原則( SOLID
    原則の D

    View full-size slide

  10. UseCase
    は UseCasePort
    に依存
    Eloquent
    の変化の影響を受けない
    アプリケーションレイヤで UseCasePort
    を実装

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  13. 既存実装への適用

    View full-size slide

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

    View full-size slide

  15. 実装 API
    顧客サービスのポイント加算 API
    顧客情報 = customers
    テーブル
    顧客ポイント = customer_points
    テーブル
    対象顧客に顧客ポイントを加算
    PUT /customers/add_point
    customer_id =
    顧客 ID
    add_point =
    加算ポイント
    更新後のポイントを JSON
    で返す

    View full-size slide

  16. 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
    }

    View full-size slide

  17. ユースケース
    事前条件
    加算ポイントが 1
    以上の整数である
    顧客 ID
    がデータベースに存在する
    顧客ポイントを加算
    更新後の顧客ポイントを返す

    View full-size slide

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

    View full-size slide

  19. AddPointAction
    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;
    }

    View full-size slide

  20. 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);
    }

    View full-size slide

  21. //
    ポイント加算
    $this->customerPoint->addPoint(
    $customerId,
    $addPoint
    );
    //
    加算ポイントの取得
    $customerPoint = $this->customerPoint
    ->findPoint($customerId);
    return response()->json([
    'customer_point' => $customerPoint
    ]);
    }
    }

    View full-size slide

  22. 独立したコアレイヤパターンの適用
    [core]
    コアロジックの抽出
    [core]
    コアロジックで必要なインターフェイスを定

    [app]
    アダプタの実装
    [app]
    コアロジックの実行

    View full-size slide

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

    View full-size slide

  24. [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);
    }

    View full-size slide

  25. AddPointUseCasePort
    interface AddPointUseCasePort
    {
    public function existsId(int $customerId): bool;
    public function findPoint(int $customerId): int;
    public function addPoint(
    int $customerId, int $addPoint): void;
    }

    View full-size slide

  26. [core] AddPointUseCase
    インターフェイス実装のインスタンスに与える
    run
    メソッドで $port
    を使うように変更
    final class AddPointUseCase
    {
    /** @var AddPointUseCasePort */
    private $port;
    public function __construct(
    AddPointUseCasePort $port)
    {
    $this->port = $port;
    }

    View full-size slide

  27. [core]
    ユースケーステスト
    モックを与えればコアレイヤのみでテストできる
    /**
    * @test
    */
    public function run_()
    {
    $useCase = new AddPointUseCase(
    $this->mockAdapter,
    );
    $actual = $useCase->run(1, 100);
    $this->assertSame(200, $actual);
    }

    View full-size slide

  28. [app]
    アダプタの実装
    定義したインターフェイスをアダプタで実装
    Eloquent
    を利用してデータベースアクセス
    実装方法は何でも良い
    QueryBuilder or PDO or pg_*()

    View full-size slide

  29. 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);
    }
    }

    View full-size slide

  30. [app]
    サービスコンテナに登録
    ユースケースクラスのコンストラクタに
    アダプタを与えるようにサービスコンテナに登録
    public function register(): void
    {
    $this->app->bind(AddPointUseCase::class, function () {
    $adapter = $this->app->make(
    AddPointAdapter::class);
    return new AddPointUseCase($adapter);
    });
    }

    View full-size slide

  31. [app]
    ユースケースを利用
    アクションからユースケースを実行
    HTTP
    の入出力とユースケース実行のみを担う

    View full-size slide

  32. [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]);
    }

    View full-size slide

  33. モチベーション

    View full-size slide

  34. What
    と How
    の分離
    How
    の更新の影響を What
    が受けにくくなる
    フレームワークバージョンアップ等
    What
    が明確になる
    要件がコードから読み取れる
    テストがしやすい
    レビューの視点が明確になる
    How
    は定められた役割に専念できる

    View full-size slide

  35. 既存パターン
    ドメインを中心にしたアーキテクチャ
    レイヤードアーキテクチャ
    クリーンアーキテクチャ
    有用なパターンだが、too much
    に感じる時も
    アレンジして使っている現場も多い
    レイヤ構造とドメイン分析
    それぞれ独立した話

    View full-size slide

  36. 既存パターンとの違い
    シンプルで適用しやすいルールに絞る
    これさえ守れば ok
    というもの
    レイヤ構造のみ
    モデリング云々は含まない
    DDD +
    コアレイヤパターン
    クリーンアーキテクチャの一種とも言える
    Simpli ed Clean Architecture

    View full-size slide

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

    View full-size slide

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

    より抽象化して、PHP
    以外の言語でも動作

    View full-size slide

  39. さいごに
    レイヤ分けを身近なものに
    スーパーヒーローがいなくても使える
    簡単な API
    のリファクタリングから
    自分たちのコードでやると分かりやすい
    ハンズオンとかもやりたい

    View full-size slide