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

PHPでアクターモデルを理解・体験しよう / Understand and experienc...

yuuki takezawa
September 24, 2024

PHPでアクターモデルを理解・体験しよう / Understand and experience the actor model in PHP

This presentation was given at PHP Conference Okinawa 2024.
Welcome to 
the World of the Actor Model.


yuuki takezawa

September 24, 2024
Tweet

More Decks by yuuki takezawa

Other Decks in Technology

Transcript

  1. Pro fi le • ஛ᖒ ༗و a.k.a ytake • ઍגࣜձࣾ

    CTO / ΄͔ٕज़ސ໰ʢωοτϓϩςΫγϣϯζͳͲʣ • Go / Scala / Kotlin / PHP • ΞΫλʔϞσϧେ޷͖
  2. Phluxor • Phluxor / Actor System • Phluxor Persistence /

    Event Sourcing etc.. • Phluxor Remote / Network (Experimental / WIP) • Phluxor Cluster / Virtual Actors (WIP)
  3. What is The Actor Model? • 1973೥ʹൃද͞Εͨฒߦܭࢉͷ਺ֶతϞσϧͷҰछ A type of

    mathematical model for concurrent computation introduced in 1973. • Erlang΍ScalaͳͲͰ͓ͳ͡Έ Well-known in languages like Erlang and Scala.
  4. What is The Actor Model? • ϝοηʔδΛ༻͍ͯ΍ΓऔΓΛߦ͏ Communication is performed

    using messages. • ΞΫλʔ Ϟσϧ͸ɺ͢΂͕ͯΞΫλʔ The actor model adopts the philosophy that everything is an actor.
  5. Internal State • No Share Memory • State Change ->

    Event, and Persistence • Immutable Message
  6. Actor Reference • ͢΂ͯͷΞΫλʔ͸ΞυϨεΛ࣋ͭ All actors have an address. •

    ͜ͷΞυϨε͸ϢχʔΫͰࣝผ͞ΕΔ This address is unique and identi fi able. • ૹ৴ݩ΍ૹ৴ઌΛܾΊΔ͜ͱ͕Ͱ͖Δ You can specify the sender and recipient.
  7. Actor Lifecycle • ͢΂ͯͷΞΫλʔ͸ϥΠϑαΠΫϧΛ࣋ͭ All actors have a lifecycle •

    ΞΫλʔͷఀࢭɺ࠶ىಈͳͲ including stopping, restarting, etc. • Կ΋ཁٻ͞Εͳ͍৔߹͸Կ΋͠ͳ͍ If nothing is requested of them, they do nothing.
  8. Hierarchy • ΞΫλʔ͸ΞΫλʔΛੜ੒Ͱ͖Δ Actors can create actors. • ਌ࢠؔ܎͕ܗ੒͞ΕΔ Parent-child

    relationships are formed. • ࢠΞΫλʔʹ࢓ࣄ͕ґཔͰ͖Δ You can assign tasks to child actors.
  9. Fault Tolerance • ਌ΞΫλʔ͕ࢠΞΫλʔΛ؂ಜɾ؅ཧ͢Δ੹຿Λ࣋ͭ Parent actors have the responsibility to

    supervise and manage child actors. • ਌ࢠؔ܎ʹͳ͍ΞΫλʔ΋؂ࢹ͢Δ͜ͱ͕Ͱ͖Δ Actors that are not in a parent-child relationship can also be monitored.
  10. Fault Tolerance • ࣦഊ࣌ʹͲͷΑ͏ʹ෮چͤ͞Δ͔ΛࢦࣔͰ͖Δ You can instruct how to recover

    in case of failure. • ΞΫλʔͷো֐͸ଞʹ͸఻೻͠ͳ͍ Actor failures do not propagate to others.
  11. <?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;

    use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } }
  12. <?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;

    use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } } 1BSFOU"DUPS
  13. <?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;

    use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } } $SFBUF$IJME "DUPS
  14. <?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;

    use Phluxor\ActorSystem\Message\Restarting; class ChildActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Restarting: $context->logger()->info('restarting...'); break; case $message instanceof Hello: $context->logger()->info('Hello ' . $message->name); $context->stop($context->self()); break; } } } $IJME"DUPS
  15. <?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;

    use Phluxor\ActorSystem\Message\Restarting; class ChildActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Restarting: $context->logger()->info('restarting...'); break; case $message instanceof Hello: $context->logger()->info('Hello ' . $message->name); $context->stop($context->self()); break; } } } 4UPQ4FMG
  16. <?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;

    use Phluxor\ActorSystem\Props; use Phluxor\ActorSystem\ProtoBuf\Terminated; class ParentActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Hello: $ref = $context->spawn(Props::fromProducer(fn() => new ChildActor())); $context->send($ref, $message); break; case $message instanceof Terminated: $context->logger()->info('terminated', [ 'who' => $message->getWho()->getId(), 'why' => $message->getWhy(), ]); break; } } } /PUJ fi DBUJPO GSPN$IJME
  17. <?php declare(strict_types=1); namespace Example; use Example\Message\Hello; use Phluxor\ActorSystem\Context\ContextInterface; use Phluxor\ActorSystem\Message\ActorInterface;

    use Phluxor\ActorSystem\Message\Restarting; class ChildActor implements ActorInterface { public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Restarting: $context->logger()->info('restarting...'); break; case $message instanceof Hello: $context->logger()->info('Hello ' . $message->name); throw new \Exception('hi, I am an exception'); break; } } } UISPX&YDFQUJPO -FUJUDSBTI
  18. ActorInterface • No Return / Only sending and receiving messages.

    • All actors have a receive method that receives a context. • All actors are asynchronous and independent.
  19. <?php declare(strict_types=1); namespace Phluxor\ActorSystem\Message; use Phluxor\ActorSystem\Context\ContextInterface; interface ActorInterface { /**

    * Receives a context. * * @param ContextInterface $context The context to receive. * @return void */ public function receive(ContextInterface $context): void; }
  20. Spawning Actors • spawn method is used to create a

    child actor or root actor. • spawnNamed method is used to create a named actor. • spawnPre fi x method is used to create an actor with a pre fi x. • When an actor is spawned, Phluxor\ActorSystem\Ref is returned.
  21. use Phluxor\ActorSystem; use Phluxor\ActorSystem\Props; $system = ActorSystem::create(); $spawned = $system->root()->spawn(

    Props::fromProducer( fn() => new YourActor() ) ); 8IFODSFBUJOHDIJMEBDUPST  UIFZBSFHFOFSBUFEGSPNUIFBDUPSDPOUFYU 5IFJOUFSGBDFJTUIFTBNFBTUIFSPPU
  22. $context->spawnNamed( Props::fromProducer( fn() => new YourChildActor() ), 'unique-name' ); $context->spawnPrefix(

    Props::fromProducer( fn() => new YourChildActor() ), 'prefix-' ); "TTJHOBOZOBNFUPUIFBDUPS *GZPVBSFVTJOHQFSTJTUFODF  BTTJHOBVOJRVFTQFDJ fi DOBNFSBUIFSUIBOBSBOEPNPOF DSFBUFBOBDUPSXJUIBQSF fi Y
  23. Props • The Props are a con fi guration object

    used to create an actor. • Phluxor\ActorSystem\Props is a class that creates a Props object. • and more!!!!
  24. Props::fromProducer(fn() => new YourActor()) Props::fromFunction( new ActorSystem\Message\ReceiveFunction( fn( ActorSystem\Context\ContextInterface $context

    ) => $context->message() ) ); :PVDBOTQFDJGZBDMBTTUIBUJNQMFNFOUTUIF"DUPS*OUFSGBDF  PSVTFBGVODUJPOTUZMF
  25. Props::fromProducer( fn() => new VoidActor(), ActorSystem\Props::withSupervisor( new ActorSystem\Strategy\OneForOneStrategy( 10, new

    \DateInterval('PT10S'), fn() => ActorSystem\Directive::Restart ) ) ); Props::fromProducer( fn() => new YourActor(), Props::withMailboxProducer(new ActorSystem\Mailbox\Unbounded()) ); :PVDBONPEJGZUIF.BJMCPY 5IFSFBSFPQUJPOTGPS#BUDI #PVOEFE BOE6OCPVOEFENBJMCPYFT :PVDBONPEJGZUIFTVQFSWJTPSTUSBUFHZ  TJNJMBSUP"LLB 1FLLP PS1SPUP"DUPS
  26. Send Message • send is a non-blocking, fi re-and-forget •

    request is very similar to send, Only use it when request/reply communication is required between two actors. • Futures and promises
  27. $context->send($ref, new PrepareTest(['subject' => $msg->getSubject()]) ); $context->request($ref, new PrepareTest(['subject' =>

    $msg->getSubject()]) ); $future = $context->requestFuture( $ref, new PrepareTest(['subject' => $msg->getSubject()]), 1 ); 5IFSFTVMUJTFYQFDUFEUPCFPCUBJOFEXJUIJOUIFTQFDJ fi FEUJNF *GOPU BOFSSPSXJMMCFSFUVSOFEBTUIFSFTVMU
  28. example (the answer sheet • Class time begins • The

    teacher comes to the classroom • Test begins • Write your answers on the answer sheet. • Submit test answer sheets
  29. readonly class ClassroomActor implements ActorInterface { // omit public function

    receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } }
  30. readonly class ClassroomActor implements ActorInterface { // omit public function

    receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 'SPNSPPU
  31. readonly class ClassroomActor implements ActorInterface { // omit public function

    receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 4QBXO5FBDIFS
  32. readonly class ClassroomActor implements ActorInterface { // omit public function

    receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 4FOEBNFTTBHFUPZPVSUFBDIFS
  33. readonly class ClassroomActor implements ActorInterface { // omit public function

    receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 3FQMZUPUIFTQFDJ fi FEBDUPS 0ODFBMMTUVEFOUTIBWFDPNQMFUFEUIFUFTU
  34. readonly class ClassroomActor implements ActorInterface { // omit public function

    receive(ContextInterface $context): void { $msg = $context->message(); switch (true) { case $msg instanceof StartsClass: $ref = $context->spawn( Props::fromProducer( fn() => new TeacherActor( $this->subject, $this->students, $context->self() ) ) ); $context->send($ref, new PrepareTest($msg->subject)); break; case $msg instanceof FinishTest: $context->send( $this->stream, new ClassFinished($msg->subject) ); \Swoole\Coroutine::sleep(0.1); $context->stop($context->self()); break; } } } 4UPQBOESFMFBTFSFTPVSDFT
  35. class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface

    $context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } }
  36. class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface

    $context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } } 'SPN $MBTTSPPN
  37. class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface

    $context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } } 4QBXO4UVEFOUT 6OJRVF
  38. class TeacherActor implements ActorInterface { // omit public function receive(ContextInterface

    $context): void { $msg = $context->message(); switch (true) { case $msg instanceof PrepareTest: $context->logger()->info( sprintf("Teacher has issued a %s test", $msg->subject) ); foreach ($this->students as $student) { $ref = $context->spawnNamed( Props::fromProducer(fn() => new StudentActor()), sprintf('student-%d', $student) ); $context->send($ref->getRef(), new StartTest($msg->subject)); } break; case $msg instanceof SubmitTest: $this->endOfTests[] = $msg; if (count($this->endOfTests) === count($this->students)) { $context->send($this->replyTo, new FinishTest($this->subject)); $context->poison($context->self()); } break; } } } *OUFSOBM4UBUF$IBOHF 0ODFBMMTUVEFOUTIBWFDPNQMFUFEUIFUFTU 4UPQBOESFMFBTFSFTPVSDFT
  39. class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void

    { $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } }
  40. class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void

    { $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } 'SPN5FBDIFS
  41. class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void

    { $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } 3BOEPNMZTFMFDUUIFUJNFUPBOTXFSUIFUFTU TT
  42. class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void

    { $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } QBSFOU BSF 5FBDIFS"DUPS %POUGPSHFUUIBUJUTBIJFSBSDIJDBM
  43. class StudentActor implements ActorInterface { public function receive(ContextInterface $context): void

    { $msg = $context->message(); if ($msg instanceof StartTest) { sleep(random_int(1, 9)); $context->logger()->info( sprintf( '%s is submitting the answer to the %s test', $context->self(), $msg->subject ) ); $context->send( $context->parent(), new SubmitTest( $msg->subject, (string)$context->self(), ) ); $context->poison($context->self()); } } } 4UVEFOU/BNF 'PSFYBNQMF TUVEFOU TQBXO/BNFE 4UPQBOESFMFBTFSFTPVSDFT
  44. Example • Teacher -> Actor / Aggregate • Students ->

    Actor • Classroom -> Actor / Aggregate
  45. In closing • It's a somewhat different programming paradigm. •

    It's quite dif fi cult to understand just by listening, so please try it out yourself. • Once you understand it, you can apply it to your regular programming and modeling!