Slide 1

Slide 1 text

࣮ફ Action Domain Responder yuuki takezawa PHP Conference Fukuoka 2017

Slide 2

Slide 2 text

Action Domain Responder?

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Controller • ControllerͰෳ਺ͷEndpointΛ࣋ͪɺ
 CRUDͳͲɺͦΕͧΕͷΞΫγϣϯΛController ͕ղܾ • ͦΕͧΕͷΞΫγϣϯ͕ར༻͢ΔΫϥεͳͲ͸
 ίϯτϩʔϥʹ • ϨεϙϯεΛฦ٫͢Δ੹೚Λड͚࣋ͭ
 (ςϯϓϨʔτ΍JsonͳͲͷAPIϨεϙϯεͳͲ)

Slide 7

Slide 7 text

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'); } }

Slide 8

Slide 8 text

Action • 1ͭͷAction͸1ͭͷΫϥε͕୲౰ • ϨεϙϯεΛฦ٫͢Δ੹೚͸ड͚࣋ͨͳ͍
 (ςϯϓϨʔτ΍JsonͳͲͷAPIϨεϙϯεͳͲ) • υϝΠϯͱϨεϙϯμΛ઀ଓ • Closure΍__invokeͳͲͰ࣮ߦ͞ΕΔϝιου Λهड़͢Δ͜ͱ͕ଟ͍(psr-15४ڌͷ΋ͷͳͲ)

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

$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; } });

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Model, Domain • ϏδωεϩδοΫΛղܾ͢Δ૚ • υϝΠϯۦಈͷύλʔϯͰ࣮૷͢Δɺ
 ͱ͍͏ҙຯͰ͸ͳ͍ • ඞཁʹԠͯ͡ঢ়ଶ΍ӬଓԽΛ • ΤϯςΟςΟͳͲ͸ResponderͰར༻͢Δ͜ͱ͕͋ Δ
 (renderͳͲ) • ORMͰදݱ͢Δ΋ͷ͕υϝΠϯͰ͋Ε͹ͦΕͰ΋Մ

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

୯Ұ੹೚ͷݪଇ Single Responsibility Principle

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

Domain Layer

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

ґଘੑٯసͷݪଇ

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Business Logic Database DataMapper / ORM

Slide 30

Slide 30 text

ϏδωεϩδοΫ࠶ߟ • ࣮૷ύλʔϯ͔Βߟ͑ΔͰ͸ͳ͘ɺ
 ΞϓϦέʔγϣϯΛטΈࡅ͘͜ͱ • ϏδωεϩδοΫͷొ৔ਓ෺͸Կ͔ • ར༻ऀ͸୭͔ʁ • ϏδωεϩδοΫͷओ໾͸୭͔ • ϏδωεϩδοΫ͕࡞༻͢Δ৚݅͸ʁ • σʔλϕʔε΍ϥΠϒϥϦʹनΘΕͣʹɺ
 ࠷খ୯ҐͰϏδωεϩδοΫʹؔ࿈͢Δ΋ͷΛ
 ෼ղ͢Δ

Slide 31

Slide 31 text

ొ৔ਓ෺ͷ੔ཧ

Slide 32

Slide 32 text

Entity • ΞϓϦέʔγϣϯͷओ໾ • ݸମΛࣝผͰ͖Δ΋ͷ Θͨ͠ͱ͋ͳͨ • ͜Εͱ͋ΕͱͦΕ͸ಉ͡΋ͷ͔Ͳ͏͔ • 5ࡀͷࠒͷࢲͱࠓͷࢲ͸ಉ͔͡Ͳ͏͔ • ΞϓϦέʔγϣϯʹΑͬͯҟͳΔͨΊɺ
 ॻ੶Λݟͯࣸܦͯ͠ࡁΉྖҬͰ͸͋Γ·ͤΜ • σʔλϕʔεͷΧϥϜ ΠίʔϧͰ͸͋Γ·ͤΜ

Slide 33

Slide 33 text

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; } }

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

Repository • ӬଓԽΛߦ͏(อଘ) • ࣮ࡍʹ͸͜͜Λ࣮૷͢Δͷ͕Ұ൪ख͕͔͔Δ • DAOؾຯʹͳͬͯ͠·͏͜ͱ΋
 (QueryͳͲσʔλϕʔεͷ৘ใ͕هड़͞Ε Δ) • EntityͷCollection͔Β࢝ΊͯΈ·͠ΐ͏

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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; } }

Slide 38

Slide 38 text

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(); } }

Slide 39

Slide 39 text

Domain Service • Entity΍Value ObjectͰදݱͰ͖ͳ͍ɺ
 ϏδωεϩδοΫʹج͍ͮͯॲཧΛߦ͏ • ൚༻తͳΫϥεͰ͸ͳ͍ͨΊɺxxServiceͱ͍͏໊લ͸ආ͚ ͨํ͕ྑ͍ • Application Service͔Βར༻͞ΕΔ͜ͱ͕ଟ͍ • υϝΠϯͷ஌͕ࣝ֎ʹᷓΕͳ͍༷ʹ஫ҙ͢Δ
 (Repository͕࿐ग़͍ͯ͠ΔͳͲ) • υϝΠϯʹ͸ؔ܎ͳ͍ϑϨʔϜϫʔΫͷػೳͳͲ΋ར༻͠ ͳ͍༷ʹ஫ҙ͢Δ

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Responder Layer

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

Domain Payload

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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; } }

Slide 47

Slide 47 text

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"); } }

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Application Refactoring

Slide 50

Slide 50 text

From Controller To Action

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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'); } }

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

From View To Responder

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

ߦಈ Ԡ౴ ໨త

Slide 58

Slide 58 text

Positive • ϑϨʔϜϫʔΫґଘͷύλʔϯͰ͸ͳ͍ • ීஈ։ൃͰ࢖͍ͬͯΔϑϨʔϜϫʔΫͰ΋ྲྀ ༻Մೳ • ෳࡶԽ͢ΔΞϓϦέʔγϣϯʹ • খͨ͘͞͠ίϯϙʔωϯτΛ૊Έ߹Θͤͯߏ ங • ݁Ռతʹґଘ͕গͳ͘ͳΔɺΫϥε͕খ͘͞ ͳΔͨΊɺϢχοτςετ͸͠΍͍͢

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

try • Framework • zendframework/zend-expressive • radarphp/Radar.Project
 • Component • zendframework/zend-diactoros • symfony/http-foundation • nikic/fast-route • etc…

Slide 61

Slide 61 text

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