$30 off During Our Annual Pro Sale. View Details »

実践 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<aka ytake> PHP Conference Fukuoka

    2017
  2. Action Domain Responder?

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

  5. ΞϓϦέʔγϣϯϑϩʔ • RequestΛ͏͚ͱΓɺ
 Routeʹඥ͍ͮͨॲཧ(Action)ΛDispatch • Action͸ϏδωεϩδοΫ(Domain)ͱ΍ΓͱΓΛߦ͏ • ϏδωεϩδοΫͰಘΒΕͨσʔλΛResponder΁ • Responder͸ɺಘΒΕͨσʔλΛར༻ͯ͠ɺ


    ResponceΛ࡞੒ • ResponseΛΫϥΠΞϯτʹฦ٫
  6. Controller • ControllerͰෳ਺ͷEndpointΛ࣋ͪɺ
 CRUDͳͲɺͦΕͧΕͷΞΫγϣϯΛController ͕ղܾ • ͦΕͧΕͷΞΫγϣϯ͕ར༻͢ΔΫϥεͳͲ͸
 ίϯτϩʔϥʹ • ϨεϙϯεΛฦ٫͢Δ੹೚Λड͚࣋ͭ


    (ςϯϓϨʔτ΍JsonͳͲͷAPIϨεϙϯεͳͲ)
  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'); } }
  8. Action • 1ͭͷAction͸1ͭͷΫϥε͕୲౰ • ϨεϙϯεΛฦ٫͢Δ੹೚͸ड͚࣋ͨͳ͍
 (ςϯϓϨʔτ΍JsonͳͲͷAPIϨεϙϯεͳͲ) • υϝΠϯͱϨεϙϯμΛ઀ଓ • Closure΍__invokeͳͲͰ࣮ߦ͞ΕΔϝιου

    Λهड़͢Δ͜ͱ͕ଟ͍(psr-15४ڌͷ΋ͷͳͲ)
  9. final class UserReadAction { public function __construct(Responder $responder) { $this->responder

    = $responder; } public function __invoke($id) { return $this->responder->emit( ['user' => User::findOrFail($id)] ); } }
  10. class Task { public function process(callable $callable) { return call_user_func($callable);

    } }
  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; } });
  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); } }
  13. Model, Domain • ϏδωεϩδοΫΛղܾ͢Δ૚ • υϝΠϯۦಈͷύλʔϯͰ࣮૷͢Δɺ
 ͱ͍͏ҙຯͰ͸ͳ͍ • ඞཁʹԠͯ͡ঢ়ଶ΍ӬଓԽΛ •

    ΤϯςΟςΟͳͲ͸ResponderͰར༻͢Δ͜ͱ͕͋ Δ
 (renderͳͲ) • ORMͰදݱ͢Δ΋ͷ͕υϝΠϯͰ͋Ε͹ͦΕͰ΋Մ
  14. View • MVCͷView͸Controller͕ίϯςϯπͷੜ੒Λ ߦ͍ɺϔομͳͲΛૢ࡞ͯ͠ϨεϙϯεΛฦ٫ • ίϯτϩʔϥ͕ड͚֤࣋ͭΞΫγϣϯͰϨεϙ ϯεฦ٫ʹ͍ͭͯͦΕͧΕҟͳΔ৔߹͕͋Δ • ͋ΔϝιουͰ͸Presenter͕ඞཁͰ͋ͬͨΓ ͱ༷ʑ

    • ςϯϓϨʔτͷΈΛࢦ͢Θ͚Ͱ͸ͳ͍
  15. Responder • ΞΫγϣϯʹରԠ͢ΔResponderΛݸผʹ༻ ҙ • υϝΠϯͷσʔλΛResponderʹ౉͢ • Responder͸ϔομͷઃఆ΍ɺςϯϓϨʔτ ΍ඳըํ๏ͳͲΛ୲౰ •

    ҰൠతͳςϯϓϨʔτϏϡʔΛ૊ΈࠐΉ͜ͱ ΋Մೳ
  16. ෺ࣄΛখ͘͞ɺ࠷খ୯Ґʹ

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

  18. None
  19. Domain Layer

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

  21. αʔϏεཁ݅ • Ϣʔβʔͷొ࿥৘ใ͕औಘͰ͖Δ • Ϣʔβʔͷߪಡ৘ใΛऔಘ͢Δ • ߪಡ͍ͯ͠ΔϢʔβʔʹϝʔϧΛૹ৴͢Δ • Ϣʔβʔ͕ୀձͰ͖Δ •

    Ϣʔβʔ͸ͭͿ΍͘͜ͱ͕Ͱ͖Δ • Ϣʔβʔ͸ΠΠωΛԡ͢͜ͱ͕Ͱ͖Δ • etc
  22. γεςϜཁ݅ • RDBMSΛར༻͢Δ • NoSQLΛར༻͢Δ • ϑϨʔϜϫʔΫ͸ԿʑΛར༻͢Δ • طଘͷίʔυʹ֦ுͱ࣮ͯ͠૷͢Δ •

    ৽نʹ࣮૷͢Δ • etc
  23. None
  24. None
  25. $repository = new Repository(new Database()); $entity = new Entity(1, ‘testing’);

    $repository->add($entity); $repository->save(); // return Entity[] $repository->findAll();
  26. ґଘੑٯసͷݪଇ

  27. None
  28. ࡉ෼Խ • ϏδωεϩδοΫʹ͓͚Δσʔλͷอ؅ઌ͸
 ີ݁߹Ͱ͋Δ΂͖͔ʁ • Ϣʔβʔ৘ใऔಘͱ͍͏ࣄฑɺಈ࡞ʹ͓͍ͯɺ 
 σʔλϕʔε͔Ͳ͏͔ɺ
 ͳʹΛ࢖͏͔͸γεςϜཁ݅ʹ෼ྨ͞ΕΔ

  29. Business Logic Database DataMapper / ORM

  30. ϏδωεϩδοΫ࠶ߟ • ࣮૷ύλʔϯ͔Βߟ͑ΔͰ͸ͳ͘ɺ
 ΞϓϦέʔγϣϯΛטΈࡅ͘͜ͱ • ϏδωεϩδοΫͷొ৔ਓ෺͸Կ͔ • ར༻ऀ͸୭͔ʁ • ϏδωεϩδοΫͷओ໾͸୭͔

    • ϏδωεϩδοΫ͕࡞༻͢Δ৚݅͸ʁ • σʔλϕʔε΍ϥΠϒϥϦʹनΘΕͣʹɺ
 ࠷খ୯ҐͰϏδωεϩδοΫʹؔ࿈͢Δ΋ͷΛ
 ෼ղ͢Δ
  31. ొ৔ਓ෺ͷ੔ཧ

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

    • ΞϓϦέʔγϣϯʹΑͬͯҟͳΔͨΊɺ
 ॻ੶Λݟͯࣸܦͯ͠ࡁΉྖҬͰ͸͋Γ·ͤΜ • σʔλϕʔεͷΧϥϜ ΠίʔϧͰ͸͋Γ·ͤΜ
  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; } }
  34. Value Object • ෆมͰ͋Δ͜ͱ • ೥ྸ΍Կ͔͕ҟͳ͍ͬͯΔ͕ݸ͸มΘΒͳ͍
 -> ೥ྸ΍Կ͔ Value Object

    • Entityͷ෇Ճ৘ใͰ͋Γɺຊ࣭తͳͱ͜ΖͰ ͸ͳ͍͔΋͠Εͳ͍ • ը໘্ʹදࣔ͢Δ΋ͷɺͰ͸͋Γ·ͤΜ
  35. Repository • ӬଓԽΛߦ͏(อଘ) • ࣮ࡍʹ͸͜͜Λ࣮૷͢Δͷ͕Ұ൪ख͕͔͔Δ • DAOؾຯʹͳͬͯ͠·͏͜ͱ΋
 (QueryͳͲσʔλϕʔεͷ৘ใ͕هड़͞Ε Δ) •

    EntityͷCollection͔Β࢝ΊͯΈ·͠ΐ͏
  36. Specification • ࢓༷ύλʔϯͱΑ͹ΕΔ΋ͷ • Կ͔ͷ৚݅Λ࢓༷ͱͯ͠੾Γग़͢ • ཁٻΛຬͨ͢΋ͷ͔Ͳ͏͔ • Repositoryͱ૬ޓ࡞༻͢Δ΋ͷ •

    ϦϙδτϦͰඞཁͰ͋Ε͹ɺ
 ΫΤϦΛར༻ͳͲΛSpecificationͰ࣮ߦ
  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; } }
  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(); } }
  39. Domain Service • Entity΍Value ObjectͰදݱͰ͖ͳ͍ɺ
 ϏδωεϩδοΫʹج͍ͮͯॲཧΛߦ͏ • ൚༻తͳΫϥεͰ͸ͳ͍ͨΊɺxxServiceͱ͍͏໊લ͸ආ͚ ͨํ͕ྑ͍ •

    Application Service͔Βར༻͞ΕΔ͜ͱ͕ଟ͍ • υϝΠϯͷ஌͕ࣝ֎ʹᷓΕͳ͍༷ʹ஫ҙ͢Δ
 (Repository͕࿐ग़͍ͯ͠ΔͳͲ) • υϝΠϯʹ͸ؔ܎ͳ͍ϑϨʔϜϫʔΫͷػೳͳͲ΋ར༻͠ ͳ͍༷ʹ஫ҙ͢Δ
  40. ҰͭͷΞΫγϣϯʹ ҰͭͷυϝΠϯ

  41. Responder Layer

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

    • υϝΠϯϨΠϠʔͱಉ༷ʹ࠷খ୯Ґ΁
  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); } }
  44. Domain Payload

  45. Domain Payload • https://vaughnvernon.co/?page_id=40 • ϨϯμϦϯά࣌ʹඞཁͳෳ਺ͷΦϒδΣΫ τ΍ঢ়ଶΛɺ
 ·ͱΊΔΦϒδΣΫτ • ड͚౉͠Λ୲౰͢Δ͕ɺݸผͷ৘ใ͚ͩΛ

    ౉͢Data Transfer Objectͱ͸ҟͳΔ
  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; } }
  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"); } }
  48. Domain Payload + Responder • υϝΠϯʹґଘͤͣɺԠ౴ͷΈʹ஫ྗ͢Δ • ςϯϓϨʔτΛඳը͢Δ͔ɺJSONͰฦ٫ ͢Δ͔ •

    εςʔλείʔυ͸Կ͔ • υϝΠϯͱԠ౴Λૄ݁߹ʹ
  49. Application Refactoring

  50. From Controller To Action

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

    ResponderɾDomain PayloadΛಋೖ • ίʔυΛ࡟আ͢Δ༐ؾͱϢχοτςετ
  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'); } }
  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)]); } }
  54. From View To Responder

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

    Ԡ౴͚ͩΛड͚༷࣋ͭʹ஫ҙ͢Δ • Domain Payloadͷಋೖ
  56. Serialize • EntityͳͲͷυϝΠϯΦϒδΣΫτΛͲ͏΍ͬ ͯγϦΞϥΠζ͢Δ͔ • Responderͷ੹຿͔ɺDomain Payload͔ʁ • େ఍Domain૚ͷ੹຿Ͱ͸ͳ͍ •

    Json͔JsonAPI͔ɺHal+Json͔ʁ • jms/serializer ͳͲͷΞϊςʔγϣϯΛར༻ͯ͠ ෼཭
  57. ߦಈ Ԡ౴ ໨త

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

    ங • ݁Ռతʹґଘ͕গͳ͘ͳΔɺΫϥε͕খ͘͞ ͳΔͨΊɺϢχοτςετ͸͠΍͍͢
  59. Negative • খ͞ͳΫϥε͕ͨ͘͞ΜͰ͖ΔͨΊɺ
 νʔϜ։ൃͰಋೖ͢Δ৔߹͸ཧղ͕ඞཁ • ൚༻తͳΫϥεΛ࡞Δ͔ɺখ͞ͳΫϥεΛ ࡞Δ͔ • ීஈ࢖͍ͬͯΔϑϨʔϜϫʔΫʹΑͬͯ͸ɺ ಠࣗͰ࡞Βͳ͚Ε͹ͳΒͳ͍΋ͷ͕૿͑Δ

    ͔΋͠·ͤΜ
  60. try • Framework • zendframework/zend-expressive • radarphp/Radar.Project
 • Component •

    zendframework/zend-diactoros • symfony/http-foundation • nikic/fast-route • etc…
  61. ADRͷΤοηϯεΛΞϓϦέʔγϣϯ΁