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. 実装 API 顧客サービスのポイント加算 API 顧客情報 = customers テーブル 顧客ポイント =

    customer_points テーブル 対象顧客に顧客ポイントを加算 PUT /customers/add_point customer_id = 顧客 ID add_point = 加算ポイント 更新後のポイントを JSON で返す
  2. 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 }
  3. 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; }
  4. 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); }
  5. // ポイント加算 $this->customerPoint->addPoint( $customerId, $addPoint ); // 加算ポイントの取得 $customerPoint =

    $this->customerPoint ->findPoint($customerId); return response()->json([ 'customer_point' => $customerPoint ]); } }
  6. [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); }
  7. AddPointUseCasePort interface AddPointUseCasePort { public function existsId(int $customerId): bool; public

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

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

    { $useCase = new AddPointUseCase( $this->mockAdapter, ); $actual = $useCase->run(1, 100); $this->assertSame(200, $actual); }
  10. 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); } }
  11. [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]); }
  12. What と How の分離 How の更新の影響を What が受けにくくなる フレームワークバージョンアップ等 What

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