明日から使えるアーキテクチャ 独立したコアレイヤパターン / 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. 6.
  2. 9.
  3. 17.

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

    customer_points テーブル 対象顧客に顧客ポイントを加算 PUT /customers/add_point customer_id = 顧客 ID add_point = 加算ポイント 更新後のポイントを JSON で返す
  4. 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 }
  5. 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; }
  6. 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); }
  7. 23.

    // ポイント加算 $this->customerPoint->addPoint( $customerId, $addPoint ); // 加算ポイントの取得 $customerPoint =

    $this->customerPoint ->findPoint($customerId); return response()->json([ 'customer_point' => $customerPoint ]); } }
  8. 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); }
  9. 27.

    AddPointUseCasePort interface AddPointUseCasePort { public function existsId(int $customerId): bool; public

    function findPoint(int $customerId): int; public function addPoint( int $customerId, int $addPoint): void; }
  10. 28.

    [core] AddPointUseCase インターフェイス実装のインスタンスに与える run メソッドで $port を使うように変更 final class AddPointUseCase

    { /** @var AddPointUseCasePort */ private $port; public function __construct( AddPointUseCasePort $port) { $this->port = $port; }
  11. 29.

    [core] ユースケーステスト モックを与えればコアレイヤのみでテストできる /** * @test */ public function run_()

    { $useCase = new AddPointUseCase( $this->mockAdapter, ); $actual = $useCase->run(1, 100); $this->assertSame(200, $actual); }
  12. 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); } }
  13. 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]); }
  14. 36.

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

    が明確になる 要件がコードから読み取れる テストがしやすい レビューの視点が明確になる How は定められた役割に専念できる