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

実践 Action Domain Responder

実践 Action Domain Responder

PHPカンファレンス福岡2017で発表したスライド

yuuki takezawa

June 10, 2017
Tweet

More Decks by yuuki takezawa

Other Decks in Technology

Transcript

  1. ࣮ફ
    Action Domain Responder
    yuuki takezawa
    PHP Conference Fukuoka 2017

    View Slide

  2. Action Domain Responder?

    View Slide

  3. View Slide

  4. Controller <--> Action
    Model <--> Domain
    View <--> Responder

    View Slide

  5. ΞϓϦέʔγϣϯϑϩʔ
    • RequestΛ͏͚ͱΓɺ

    Routeʹඥ͍ͮͨॲཧ(Action)ΛDispatch
    • Action͸ϏδωεϩδοΫ(Domain)ͱ΍ΓͱΓΛߦ͏
    • ϏδωεϩδοΫͰಘΒΕͨσʔλΛResponder΁
    • Responder͸ɺಘΒΕͨσʔλΛར༻ͯ͠ɺ

    ResponceΛ࡞੒
    • ResponseΛΫϥΠΞϯτʹฦ٫

    View Slide

  6. Controller
    • ControllerͰෳ਺ͷEndpointΛ࣋ͪɺ

    CRUDͳͲɺͦΕͧΕͷΞΫγϣϯΛController
    ͕ղܾ
    • ͦΕͧΕͷΞΫγϣϯ͕ར༻͢ΔΫϥεͳͲ͸

    ίϯτϩʔϥʹ
    • ϨεϙϯεΛฦ٫͢Δ੹೚Λड͚࣋ͭ

    (ςϯϓϨʔτ΍JsonͳͲͷAPIϨεϙϯεͳͲ)

    View Slide

  7. final class UserController extends Controller
    {
    public function show($id)
    {
    return view('user.profile', ['user' => User::findOrFail($id)]);
    }
    public function create(Request $request)
    {
    Validate::make(…);
    $this->fluent->append($request->all());
    return view('user.create');
    }
    }

    View Slide

  8. Action
    • 1ͭͷAction͸1ͭͷΫϥε͕୲౰
    • ϨεϙϯεΛฦ٫͢Δ੹೚͸ड͚࣋ͨͳ͍

    (ςϯϓϨʔτ΍JsonͳͲͷAPIϨεϙϯεͳͲ)
    • υϝΠϯͱϨεϙϯμΛ઀ଓ
    • Closure΍__invokeͳͲͰ࣮ߦ͞ΕΔϝιου
    Λهड़͢Δ͜ͱ͕ଟ͍(psr-15४ڌͷ΋ͷͳͲ)

    View Slide

  9. final class UserReadAction
    {
    public function __construct(Responder $responder)
    {
    $this->responder = $responder;
    }
    public function __invoke($id)
    {
    return $this->responder->emit(
    ['user' => User::findOrFail($id)]
    );
    }
    }

    View Slide

  10. class Task
    {
    public function process(callable $callable)
    {
    return call_user_func($callable);
    }
    }

    View Slide

  11. $task = new Task;
    echo $task->process(function () {
    return 1;
    });
    class Invoke
    {
    public function __invoke()
    {
    return 2;
    }
    }
    echo $task->process(new Invoke);
    echo $task->process(new class
    {
    public function __invoke()
    {
    return 3;
    }
    });

    View Slide

  12. final class Runner
    {
    protected $invoker;
    public function __construct(array $invoker = [])
    {
    $this->invoker = $invoker;
    }
    public function __invoke($request, $response)
    {
    $invoke = array_shift($this->invoker);
    if (is_null($invoke)) {
    return function ($request, $response) {
    return $response;
    };
    }
    return $invoke($request, $response, $this);
    }
    }

    View Slide

  13. Model, Domain
    • ϏδωεϩδοΫΛղܾ͢Δ૚
    • υϝΠϯۦಈͷύλʔϯͰ࣮૷͢Δɺ

    ͱ͍͏ҙຯͰ͸ͳ͍
    • ඞཁʹԠͯ͡ঢ়ଶ΍ӬଓԽΛ
    • ΤϯςΟςΟͳͲ͸ResponderͰར༻͢Δ͜ͱ͕͋
    Δ

    (renderͳͲ)
    • ORMͰදݱ͢Δ΋ͷ͕υϝΠϯͰ͋Ε͹ͦΕͰ΋Մ

    View Slide

  14. View
    • MVCͷView͸Controller͕ίϯςϯπͷੜ੒Λ
    ߦ͍ɺϔομͳͲΛૢ࡞ͯ͠ϨεϙϯεΛฦ٫
    • ίϯτϩʔϥ͕ड͚֤࣋ͭΞΫγϣϯͰϨεϙ
    ϯεฦ٫ʹ͍ͭͯͦΕͧΕҟͳΔ৔߹͕͋Δ
    • ͋ΔϝιουͰ͸Presenter͕ඞཁͰ͋ͬͨΓ
    ͱ༷ʑ
    • ςϯϓϨʔτͷΈΛࢦ͢Θ͚Ͱ͸ͳ͍

    View Slide

  15. Responder
    • ΞΫγϣϯʹରԠ͢ΔResponderΛݸผʹ༻
    ҙ
    • υϝΠϯͷσʔλΛResponderʹ౉͢
    • Responder͸ϔομͷઃఆ΍ɺςϯϓϨʔτ
    ΍ඳըํ๏ͳͲΛ୲౰
    • ҰൠతͳςϯϓϨʔτϏϡʔΛ૊ΈࠐΉ͜ͱ
    ΋Մೳ

    View Slide

  16. ෺ࣄΛখ͘͞ɺ࠷খ୯Ґʹ

    View Slide

  17. ୯Ұ੹೚ͷݪଇ
    Single Responsibility
    Principle

    View Slide

  18. View Slide

  19. Domain Layer

    View Slide

  20. ADR͸DDDͱ͸ؔ܎͋Γ·ͤΜ͕ɺ
    খ͘͞ߟ͑ΔͨΊͷཧղʹ
    ܨ͕Δ͔΋͠Ε·ͤΜ
    ؆୯ʹDDDͷ঺հΛ͠·͢

    View Slide

  21. αʔϏεཁ݅
    • Ϣʔβʔͷొ࿥৘ใ͕औಘͰ͖Δ
    • Ϣʔβʔͷߪಡ৘ใΛऔಘ͢Δ
    • ߪಡ͍ͯ͠ΔϢʔβʔʹϝʔϧΛૹ৴͢Δ
    • Ϣʔβʔ͕ୀձͰ͖Δ
    • Ϣʔβʔ͸ͭͿ΍͘͜ͱ͕Ͱ͖Δ
    • Ϣʔβʔ͸ΠΠωΛԡ͢͜ͱ͕Ͱ͖Δ
    • etc

    View Slide

  22. γεςϜཁ݅
    • RDBMSΛར༻͢Δ
    • NoSQLΛར༻͢Δ
    • ϑϨʔϜϫʔΫ͸ԿʑΛར༻͢Δ
    • طଘͷίʔυʹ֦ுͱ࣮ͯ͠૷͢Δ
    • ৽نʹ࣮૷͢Δ
    • etc

    View Slide

  23. View Slide

  24. View Slide

  25. $repository = new Repository(new Database());
    $entity = new Entity(1, ‘testing’);
    $repository->add($entity);
    $repository->save();
    // return Entity[]
    $repository->findAll();

    View Slide

  26. ґଘੑٯసͷݪଇ

    View Slide

  27. View Slide

  28. ࡉ෼Խ
    • ϏδωεϩδοΫʹ͓͚Δσʔλͷอ؅ઌ͸

    ີ݁߹Ͱ͋Δ΂͖͔ʁ
    • Ϣʔβʔ৘ใऔಘͱ͍͏ࣄฑɺಈ࡞ʹ͓͍ͯɺ

    σʔλϕʔε͔Ͳ͏͔ɺ

    ͳʹΛ࢖͏͔͸γεςϜཁ݅ʹ෼ྨ͞ΕΔ

    View Slide

  29. Business Logic
    Database
    DataMapper / ORM

    View Slide

  30. ϏδωεϩδοΫ࠶ߟ
    • ࣮૷ύλʔϯ͔Βߟ͑ΔͰ͸ͳ͘ɺ

    ΞϓϦέʔγϣϯΛטΈࡅ͘͜ͱ
    • ϏδωεϩδοΫͷొ৔ਓ෺͸Կ͔
    • ར༻ऀ͸୭͔ʁ
    • ϏδωεϩδοΫͷओ໾͸୭͔
    • ϏδωεϩδοΫ͕࡞༻͢Δ৚݅͸ʁ
    • σʔλϕʔε΍ϥΠϒϥϦʹनΘΕͣʹɺ

    ࠷খ୯ҐͰϏδωεϩδοΫʹؔ࿈͢Δ΋ͷΛ

    ෼ղ͢Δ

    View Slide

  31. ొ৔ਓ෺ͷ੔ཧ

    View Slide

  32. Entity
    • ΞϓϦέʔγϣϯͷओ໾
    • ݸମΛࣝผͰ͖Δ΋ͷ Θͨ͠ͱ͋ͳͨ
    • ͜Εͱ͋ΕͱͦΕ͸ಉ͡΋ͷ͔Ͳ͏͔
    • 5ࡀͷࠒͷࢲͱࠓͷࢲ͸ಉ͔͡Ͳ͏͔
    • ΞϓϦέʔγϣϯʹΑͬͯҟͳΔͨΊɺ

    ॻ੶Λݟͯࣸܦͯ͠ࡁΉྖҬͰ͸͋Γ·ͤΜ
    • σʔλϕʔεͷΧϥϜ ΠίʔϧͰ͸͋Γ·ͤΜ

    View Slide

  33. final class Person implements EntityInterface
    {
    /** @var int */
    private $id;
    /** @var string */
    private $name;
    public function __construct(int $id, string $name)
    {
    $this->id = $id;
    $this->name = $name;
    }
    }

    View Slide

  34. Value Object
    • ෆมͰ͋Δ͜ͱ
    • ೥ྸ΍Կ͔͕ҟͳ͍ͬͯΔ͕ݸ͸มΘΒͳ͍

    -> ೥ྸ΍Կ͔ Value Object
    • Entityͷ෇Ճ৘ใͰ͋Γɺຊ࣭తͳͱ͜ΖͰ
    ͸ͳ͍͔΋͠Εͳ͍
    • ը໘্ʹදࣔ͢Δ΋ͷɺͰ͸͋Γ·ͤΜ

    View Slide

  35. Repository
    • ӬଓԽΛߦ͏(อଘ)
    • ࣮ࡍʹ͸͜͜Λ࣮૷͢Δͷ͕Ұ൪ख͕͔͔Δ
    • DAOؾຯʹͳͬͯ͠·͏͜ͱ΋

    (QueryͳͲσʔλϕʔεͷ৘ใ͕هड़͞Ε
    Δ)
    • EntityͷCollection͔Β࢝ΊͯΈ·͠ΐ͏

    View Slide

  36. Specification
    • ࢓༷ύλʔϯͱΑ͹ΕΔ΋ͷ
    • Կ͔ͷ৚݅Λ࢓༷ͱͯ͠੾Γग़͢
    • ཁٻΛຬͨ͢΋ͷ͔Ͳ͏͔
    • Repositoryͱ૬ޓ࡞༻͢Δ΋ͷ
    • ϦϙδτϦͰඞཁͰ͋Ε͹ɺ

    ΫΤϦΛར༻ͳͲΛSpecificationͰ࣮ߦ

    View Slide

  37. class ActiveUserSpecification implements SpecificationInterface
    {
    public function isSatisfiedBy(EntityInterface $entity)
    {
    if ($entity->getIdentifier() === '') {
    return false;
    }
    return true;
    }
    public function satisfyingSpecification(TokenRepository $repository)
    {
    $repository->criteria($this);
    $result = $repository->queryBy($this->token);
    if (count($result)) {
    if ($this->isSatisfiedBy($result[0])) {
    return $result[0];
    }
    }
    return null;
    }
    }

    View Slide

  38. class UserRepository
    {
    public function findOne(ActiveUserSpecification $specification)
    {
    return $specification->satisfyingSpecification($this);
    }
    public function queryBy(string $token): array
    {
    $collection = new UserCollection([
    $this->criteria->retrieve($token)
    ]);
    return $collection->toArray();
    }
    }

    View Slide

  39. Domain Service
    • Entity΍Value ObjectͰදݱͰ͖ͳ͍ɺ

    ϏδωεϩδοΫʹج͍ͮͯॲཧΛߦ͏
    • ൚༻తͳΫϥεͰ͸ͳ͍ͨΊɺxxServiceͱ͍͏໊લ͸ආ͚
    ͨํ͕ྑ͍
    • Application Service͔Βར༻͞ΕΔ͜ͱ͕ଟ͍
    • υϝΠϯͷ஌͕ࣝ֎ʹᷓΕͳ͍༷ʹ஫ҙ͢Δ

    (Repository͕࿐ग़͍ͯ͠ΔͳͲ)
    • υϝΠϯʹ͸ؔ܎ͳ͍ϑϨʔϜϫʔΫͷػೳͳͲ΋ར༻͠
    ͳ͍༷ʹ஫ҙ͢Δ

    View Slide

  40. ҰͭͷΞΫγϣϯʹ
    ҰͭͷυϝΠϯ

    View Slide

  41. Responder Layer

    View Slide

  42. Responder
    • υϝΠϯͷ݁ՌΛड͚औΔ
    • ݁Ռঢ়ଶʹ߹ΘͤͯԠ౴Λ࡞Δඞཁ͕͋Δ͔ʁ-
    > υϝΠϯͱԠ౴ͷґଘʹ஫ҙ͢Δ
    • Responder͕ෳࡶԽ͠ͳ͍ͨΊͷ࢓૊ΈΛར༻
    ͢Δ
    • υϝΠϯϨΠϠʔͱಉ༷ʹ࠷খ୯Ґ΁

    View Slide

  43. class SampleResponder
    {
    protected $response;
    public function __construct(Response $response)
    {
    $this->response = $response;
    }
    public function __invoke(array $data = [])
    {
    if(!count($data)) {
    $content = $this->templateRenderer->render(‘not_found’, []);
    return $response->setStatus(404)->setContent($content);
    }
    $content = $this->templateRenderer->render(‘show’, [‘data’ => $data]);
    return $response->setStatus(200)->setContent($content);
    }
    }

    View Slide

  44. Domain Payload

    View Slide

  45. Domain Payload
    • https://vaughnvernon.co/?page_id=40
    • ϨϯμϦϯά࣌ʹඞཁͳෳ਺ͷΦϒδΣΫ
    τ΍ঢ়ଶΛɺ

    ·ͱΊΔΦϒδΣΫτ
    • ड͚౉͠Λ୲౰͢Δ͕ɺݸผͷ৘ใ͚ͩΛ
    ౉͢Data Transfer Objectͱ͸ҟͳΔ

    View Slide

  46. class Payload
    {
    private $status;
    private $output;
    public function setOutput($output)
    {
    $this->output = $output;
    }
    public function setStatus(int $status)
    {
    $this->status = $status;
    }
    public function getOutput()
    {
    return $this->output;
    }
    public function getStatus()
    {
    return $this->status;
    }
    }

    View Slide

  47. public function read($id)
    {
    $payload = new Payload;
    try {
    $result = $this->repository->findOne($id);
    if (!$result) {
    return $payload->setStatus(404);
    }
    return $payload->setStatus(200)->setOutput($result);
    } catch (Exception $e) {
    return $payload->setStatus(500)->setOutput("error");
    }
    }

    View Slide

  48. Domain Payload + Responder
    • υϝΠϯʹґଘͤͣɺԠ౴ͷΈʹ஫ྗ͢Δ
    • ςϯϓϨʔτΛඳը͢Δ͔ɺJSONͰฦ٫
    ͢Δ͔
    • εςʔλείʔυ͸Կ͔
    • υϝΠϯͱԠ౴Λૄ݁߹ʹ

    View Slide

  49. Application Refactoring

    View Slide

  50. From Controller To Action

    View Slide

  51. ADRͷΤοηϯεΛऔΓࠐΉ
    • ৽نͰ։ൃ͢Δ΋ͷҎ֎΁ͷಋೖ
    • ෳࡶԽͨ͠ίϯτϩʔϥΛ1ϝιουʹ
    • ໨తʹ͋ͬͨΞΫγϣϯΛ୲อ͢Δ༷ʹ
    • ControllerͰͷ௚઀తͳඳըදݱΛ΍ΊΔ
    • ResponderɾDomain PayloadΛಋೖ
    • ίʔυΛ࡟আ͢Δ༐ؾͱϢχοτςετ

    View Slide

  52. final class UserController extends Controller
    {
    protected $userRegister;
    public function __construct(UserRegister $userRegister)
    {
    $this->userRegister = $userRegister;
    }
    public function show($id)
    {
    return view('user.profile', ['user' => $this->userRegister->find($id)]);
    }
    public function create(Request $request)
    {
    Validate::make(…);
    $this->fluent->append($request->all());
    return view('user.create');
    }
    }

    View Slide

  53. final class UserReadController extends Controller
    {
    protected $userRegister;
    public function __construct(UserRegister $userRegister)
    {
    $this->userRegister = $userRegister;
    }
    public function __invoke($id)
    {
    return view('user.profile', ['user' => $this->userRegister->find($id)]);
    }
    }

    View Slide

  54. From View To Responder

    View Slide

  55. ADRͷΤοηϯεΛऔΓࠐΉ2
    • Domain͔Βͷ஋ʹԠͯ͡ॲཧΛมߋ͍ͯ͠Δ΋
    ͷ͕͋Ε͹(ۭͷ৔߹͸εςʔλείʔυΛมߋ
    ͢ΔͳͲ)
    • ςϯϓϨʔτࢦఆΛResponder΁
    • Jsonग़ྗͳͲͰಛผͳॲཧ͕͋Ε͹Ҡ২͢Δ
    • Ԡ౴͚ͩΛड͚༷࣋ͭʹ஫ҙ͢Δ
    • Domain Payloadͷಋೖ

    View Slide

  56. Serialize
    • EntityͳͲͷυϝΠϯΦϒδΣΫτΛͲ͏΍ͬ
    ͯγϦΞϥΠζ͢Δ͔
    • Responderͷ੹຿͔ɺDomain Payload͔ʁ
    • େ఍Domain૚ͷ੹຿Ͱ͸ͳ͍
    • Json͔JsonAPI͔ɺHal+Json͔ʁ
    • jms/serializer ͳͲͷΞϊςʔγϣϯΛར༻ͯ͠
    ෼཭

    View Slide

  57. ߦಈ
    Ԡ౴
    ໨త

    View Slide

  58. Positive
    • ϑϨʔϜϫʔΫґଘͷύλʔϯͰ͸ͳ͍
    • ීஈ։ൃͰ࢖͍ͬͯΔϑϨʔϜϫʔΫͰ΋ྲྀ
    ༻Մೳ
    • ෳࡶԽ͢ΔΞϓϦέʔγϣϯʹ
    • খͨ͘͞͠ίϯϙʔωϯτΛ૊Έ߹Θͤͯߏ

    • ݁Ռతʹґଘ͕গͳ͘ͳΔɺΫϥε͕খ͘͞
    ͳΔͨΊɺϢχοτςετ͸͠΍͍͢

    View Slide

  59. Negative
    • খ͞ͳΫϥε͕ͨ͘͞ΜͰ͖ΔͨΊɺ

    νʔϜ։ൃͰಋೖ͢Δ৔߹͸ཧղ͕ඞཁ
    • ൚༻తͳΫϥεΛ࡞Δ͔ɺখ͞ͳΫϥεΛ
    ࡞Δ͔
    • ීஈ࢖͍ͬͯΔϑϨʔϜϫʔΫʹΑͬͯ͸ɺ
    ಠࣗͰ࡞Βͳ͚Ε͹ͳΒͳ͍΋ͷ͕૿͑Δ
    ͔΋͠·ͤΜ

    View Slide

  60. try
    • Framework
    • zendframework/zend-expressive
    • radarphp/Radar.Project

    • Component
    • zendframework/zend-diactoros
    • symfony/http-foundation
    • nikic/fast-route
    • etc…

    View Slide

  61. ADRͷΤοηϯεΛΞϓϦέʔγϣϯ΁

    View Slide