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

PHPでアクターモデルを活用したSagaパターンの実践法 / php-saga-pattern...

PHPでアクターモデルを活用したSagaパターンの実践法 / php-saga-pattern-with-actor-model

phperkaigi2025

yuuki takezawa

March 21, 2025
Tweet

More Decks by yuuki takezawa

Other Decks in Technology

Transcript

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

    CTO גࣜձࣾACESɺגࣜձࣾωοτϓϩςΫγϣϯζٕज़ސ໰ • Go / Scala / Kotlin
  2. X

  3. X

  4. ੒ޭ • Account1 ͷ࢒ߴ͸ 0 ԁɺAccount2 ͷ࢒ߴ͸ 20 ԁ •

    ظ଴௨Γʹ͓ޓ͍ͷॲཧ͕ਖ਼ৗʹऴΘΔ
  5. ߏ੒ • Runner - సૹϓϩηεΛ։࢝͠ɺ݁ՌΛऩू͢ΔओཁͳΞΫλʔ • Account - ࢒ߴΛ࣋ͪɺೖۚ/ग़ۚૢ࡞Λॲཧ͢ΔΞΫλʔ •

    TransferProcess - ΞΧ΢ϯτؒͷૹۚΛ੍ޚ͢ΔΞΫλʔ • AccountProxy - TransferProcessͱAccountΞΫλʔؒͷ௨৴Λ஥հ
  6. class TransferProcess implements ActorInterface, PersistentInterface { use Mixin; private bool

    $processCompleted = false; private bool $restarting = false; private bool $stopping = false; public function __construct( private readonly Ref $from, private readonly Ref $to, private readonly float $amount, private readonly float $availability, private readonly Behavior $behavior = new Behavior() ) { $this->behavior->become( new ReceiveFunction( fn($context) => $this->starting($context) ) ); }
  7. class TransferProcess implements ActorInterface, PersistentInterface { use Mixin; private bool

    $processCompleted = false; private bool $restarting = false; private bool $stopping = false; public function __construct( private readonly Ref $from, private readonly Ref $to, private readonly float $amount, private readonly float $availability, private readonly Behavior $behavior = new Behavior() ) { $this->behavior->become( new ReceiveFunction( fn($context) => $this->starting($context) ) ); } 1FSTJTUFODFΛ࢖͏͜ͱͰɺ ΞΫλʔࣗ਎͕Ͳ͏ͳ͔ͬͨΛอ؅ ͭ·Γ&WFOU4PVSDJOH
  8. class TransferProcess implements ActorInterface, PersistentInterface { use Mixin; private bool

    $processCompleted = false; private bool $restarting = false; private bool $stopping = false; public function __construct( private readonly Ref $from, private readonly Ref $to, private readonly float $amount, private readonly float $availability, private readonly Behavior $behavior = new Behavior() ) { $this->behavior->become( new ReceiveFunction( fn($context) => $this->starting($context) ) ); } 1IMVYPSͰ͸.JYJO5SBJUʹجຊ࣮૷͋Γ ར༻͢Δͱঢ়ଶ෮ݩͳͲΛࣗಈͰߦ͏
  9. class TransferProcess implements ActorInterface, PersistentInterface { use Mixin; private bool

    $processCompleted = false; private bool $restarting = false; private bool $stopping = false; public function __construct( private readonly Ref $from, private readonly Ref $to, private readonly float $amount, private readonly float $availability, private readonly Behavior $behavior = new Behavior() ) { $this->behavior->become( new ReceiveFunction( fn($context) => $this->starting($context) ) ); } ΞΫλʔͷৼΔ෣͍Λมߋ͢Δ #FIBJWPS
  10. class TransferProcess implements ActorInterface, PersistentInterface { use Mixin; private bool

    $processCompleted = false; private bool $restarting = false; private bool $stopping = false; public function __construct( private readonly Ref $from, private readonly Ref $to, private readonly float $amount, private readonly float $availability, private readonly Behavior $behavior = new Behavior() ) { $this->behavior->become( new ReceiveFunction( fn($context) => $this->starting($context) ) ); } ͜ͷΞΫλʔͷॳظঢ়ଶͱͯ͠ TUBSUJOHϝιου͕ಈ͘
  11. private function starting(ContextInterface $context): void { if ($context->message() instanceof Started)

    { $context->spawnNamed($this->tryDebit($this->from, -$this->amount), 'DebitAttempt'); $this->persistEvent(new ProtoBuf\TransferStarted()); } } private function tryDebit(Ref $targetActor, float $amount): Props { return Props::fromProducer( fn() => new AccountProxy( $targetActor, fn($sender) => new Debit($amount, $sender) ) ); }
  12. private function starting(ContextInterface $context): void { if ($context->message() instanceof Started)

    { $context->spawnNamed($this->tryDebit($this->from, -$this->amount), 'DebitAttempt'); $this->persistEvent(new ProtoBuf\TransferStarted()); } } private function tryDebit(Ref $targetActor, float $amount): Props { return Props::fromProducer( fn() => new AccountProxy( $targetActor, fn($sender) => new Debit($amount, $sender) ) ); } %FCJUΞΫλʔΛੜ੒
  13. private function starting(ContextInterface $context): void { if ($context->message() instanceof Started)

    { $context->spawnNamed($this->tryDebit($this->from, -$this->amount), 'DebitAttempt'); $this->persistEvent(new ProtoBuf\TransferStarted()); } } private function tryDebit(Ref $targetActor, float $amount): Props { return Props::fromProducer( fn() => new AccountProxy( $targetActor, fn($sender) => new Debit($amount, $sender) ) ); } సૹ։࢝ΠϕϯτΛอ؅
  14. private function starting(ContextInterface $context): void { if ($context->message() instanceof Started)

    { $context->spawnNamed($this->tryDebit($this->from, -$this->amount), 'DebitAttempt'); $this->persistEvent(new ProtoBuf\TransferStarted()); } } private function tryDebit(Ref $targetActor, float $amount): Props { return Props::fromProducer( fn() => new AccountProxy( $targetActor, fn($sender) => new Debit($amount, $sender) ) ); } "DDPVOU1SPYZΛੜ੒͠ɺ ग़ۚϝοηʔδΛૹ৴
  15. private function applyEvent(Message $event): void { switch (true) { case

    $event instanceof ProtoBuf\TransferStarted: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingDebitConfirmation($context) ) ); break; case $event instanceof ProtoBuf\AccountDebited: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingCreditConfirmation($context) ) ); break; case $event instanceof ProtoBuf\CreditRefused: $this->behavior->become( new ReceiveFunction( fn($context) => $this->rollingBackDebit($context) ) ); break; case $event instanceof ProtoBuf\AccountCredited: case $event instanceof ProtoBuf\DebitRolledBack: case $event instanceof ProtoBuf\TransferFailed: $this->processCompleted = true; break; } }
  16. private function applyEvent(Message $event): void { switch (true) { case

    $event instanceof ProtoBuf\TransferStarted: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingDebitConfirmation($context) ) ); break; case $event instanceof ProtoBuf\AccountDebited: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingCreditConfirmation($context) ) ); break; case $event instanceof ProtoBuf\CreditRefused: $this->behavior->become( new ReceiveFunction( fn($context) => $this->rollingBackDebit($context) ) ); break; case $event instanceof ProtoBuf\AccountCredited: case $event instanceof ProtoBuf\DebitRolledBack: case $event instanceof ProtoBuf\TransferFailed: $this->processCompleted = true; break; } } ͍͔ͭ͘ͷΠϕϯτΛӬଓԽͨ͠ޙʹ ࣗΞΫλʔͷৼΔ෣͍Λมߋ͢Δ
  17. private function applyEvent(Message $event): void { switch (true) { case

    $event instanceof ProtoBuf\TransferStarted: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingDebitConfirmation($context) ) ); break; case $event instanceof ProtoBuf\AccountDebited: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingCreditConfirmation($context) ) ); break; case $event instanceof ProtoBuf\CreditRefused: $this->behavior->become( new ReceiveFunction( fn($context) => $this->rollingBackDebit($context) ) ); break; case $event instanceof ProtoBuf\AccountCredited: case $event instanceof ProtoBuf\DebitRolledBack: case $event instanceof ProtoBuf\TransferFailed: $this->processCompleted = true; break; } } ૹ͕ۚ։࢝͢Δͱɺ Ҿ͖མͱ͠ʹؔ͢ΔৼΔ෣͍ʹมԽ
  18. private function applyEvent(Message $event): void { switch (true) { case

    $event instanceof ProtoBuf\TransferStarted: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingDebitConfirmation($context) ) ); break; case $event instanceof ProtoBuf\AccountDebited: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingCreditConfirmation($context) ) ); break; case $event instanceof ProtoBuf\CreditRefused: $this->behavior->become( new ReceiveFunction( fn($context) => $this->rollingBackDebit($context) ) ); break; case $event instanceof ProtoBuf\AccountCredited: case $event instanceof ProtoBuf\DebitRolledBack: case $event instanceof ProtoBuf\TransferFailed: $this->processCompleted = true; break; } } ೖۚʹؔ͢ΔৼΔ෣͍ʹมԽ
  19. private function applyEvent(Message $event): void { switch (true) { case

    $event instanceof ProtoBuf\TransferStarted: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingDebitConfirmation($context) ) ); break; case $event instanceof ProtoBuf\AccountDebited: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingCreditConfirmation($context) ) ); break; case $event instanceof ProtoBuf\CreditRefused: $this->behavior->become( new ReceiveFunction( fn($context) => $this->rollingBackDebit($context) ) ); break; case $event instanceof ProtoBuf\AccountCredited: case $event instanceof ProtoBuf\DebitRolledBack: case $event instanceof ProtoBuf\TransferFailed: $this->processCompleted = true; break; } } ೖ͕ࣦۚഊ͢Δͱ ϩʔϧόοΫͷৼΔ෣͍ʹ
  20. private function applyEvent(Message $event): void { switch (true) { case

    $event instanceof ProtoBuf\TransferStarted: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingDebitConfirmation($context) ) ); break; case $event instanceof ProtoBuf\AccountDebited: $this->behavior->become( new ReceiveFunction( fn($context) => $this->awaitingCreditConfirmation($context) ) ); break; case $event instanceof ProtoBuf\CreditRefused: $this->behavior->become( new ReceiveFunction( fn($context) => $this->rollingBackDebit($context) ) ); break; case $event instanceof ProtoBuf\AccountCredited: case $event instanceof ProtoBuf\DebitRolledBack: case $event instanceof ProtoBuf\TransferFailed: $this->processCompleted = true; break; } } ਖ਼ৗʹೖग़͕ۚ׬ྃɺ ϩʔϧόοΫ͕׬ྃɺ ࢦఆճ਺Ҏ্ૹۚʹࣦഊ͢Δͱऴྃ
  21. readonly class TransferFactory { public function __construct( private ContextInterface $context,

    private float $availability, private int $retryAttempts, private ProviderInterface $provider, ) { } public function createTransfer( string $actorName, Ref $fromAccount, Ref $toAccount, float $amount, ): SpawnResult { $props = Props::fromProducer( fn() => new TransferProcess($fromAccount, $toAccount, $amount, $this->availability), Props::withReceiverMiddleware( new EventSourcedFactory($this->provider) ), Props::withSupervisor( new OneForOneStrategy( $this->retryAttempts, new DateInterval('PT10S'), new DefaultDecider(), ) ) ); return $this->context->spawnNamed($props, $actorName); } }
  22. readonly class TransferFactory { public function __construct( private ContextInterface $context,

    private float $availability, private int $retryAttempts, private ProviderInterface $provider, ) { } public function createTransfer( string $actorName, Ref $fromAccount, Ref $toAccount, float $amount, ): SpawnResult { $props = Props::fromProducer( fn() => new TransferProcess($fromAccount, $toAccount, $amount, $this->availability), Props::withReceiverMiddleware( new EventSourcedFactory($this->provider) ), Props::withSupervisor( new OneForOneStrategy( $this->retryAttempts, new DateInterval('PT10S'), new DefaultDecider(), ) ) ); return $this->context->spawnNamed($props, $actorName); } } ΞΫλʔੜ੒࣌ʹ ো֐ઓུΛࢦࣔ
  23. readonly class TransferFactory { public function __construct( private ContextInterface $context,

    private float $availability, private int $retryAttempts, private ProviderInterface $provider, ) { } public function createTransfer( string $actorName, Ref $fromAccount, Ref $toAccount, float $amount, ): SpawnResult { $props = Props::fromProducer( fn() => new TransferProcess($fromAccount, $toAccount, $amount, $this->availability), Props::withReceiverMiddleware( new EventSourcedFactory($this->provider) ), Props::withSupervisor( new OneForOneStrategy( $this->retryAttempts, new DateInterval('PT10S'), new DefaultDecider(), ) ) ); return $this->context->spawnNamed($props, $actorName); } } 0OF'PS0OF͸ ࢠΞΫλʔͷ͏ͪର৅ͷҰ͚ͭͩ
  24. readonly class TransferFactory { public function __construct( private ContextInterface $context,

    private float $availability, private int $retryAttempts, private ProviderInterface $provider, ) { } public function createTransfer( string $actorName, Ref $fromAccount, Ref $toAccount, float $amount, ): SpawnResult { $props = Props::fromProducer( fn() => new TransferProcess($fromAccount, $toAccount, $amount, $this->availability), Props::withReceiverMiddleware( new EventSourcedFactory($this->provider) ), Props::withSupervisor( new OneForOneStrategy( $this->retryAttempts, new DateInterval('PT10S'), new DefaultDecider(), ) ) ); return $this->context->spawnNamed($props, $actorName); } } ඵؒͷ͏ͪࢦఆճ਺ ճ ෼ ֘౰ͷΞΫλʔΛ࠶ىಈ͢Δ
  25. readonly class TransferFactory { public function __construct( private ContextInterface $context,

    private float $availability, private int $retryAttempts, private ProviderInterface $provider, ) { } public function createTransfer( string $actorName, Ref $fromAccount, Ref $toAccount, float $amount, ): SpawnResult { $props = Props::fromProducer( fn() => new TransferProcess($fromAccount, $toAccount, $amount, $this->availability), Props::withReceiverMiddleware( new EventSourcedFactory($this->provider) ), Props::withSupervisor( new OneForOneStrategy( $this->retryAttempts, new DateInterval('PT10S'), new DefaultDecider(), ) ) ); return $this->context->spawnNamed($props, $actorName); } } ϝοηʔδΛอ؅ͯ͠ ΞΫλʔΛ෮ݩ͢Δ
  26. readonly class TransferFactory { public function __construct( private ContextInterface $context,

    private float $availability, private int $retryAttempts, private ProviderInterface $provider, ) { } public function createTransfer( string $actorName, Ref $fromAccount, Ref $toAccount, float $amount, ): SpawnResult { $props = Props::fromProducer( fn() => new TransferProcess($fromAccount, $toAccount, $amount, $this->availability), Props::withReceiverMiddleware( new EventSourcedFactory($this->provider) ), Props::withSupervisor( new OneForOneStrategy( $this->retryAttempts, new DateInterval('PT10S'), new DefaultDecider(), ) ) ); return $this->context->spawnNamed($props, $actorName); } } ࢠΞΫλʔΛ࠶ىಈ͢Δͱɺ ϝοηʔδʹରԠͨ͠ॲཧΛ಺෦Ͱߦ͍ ࠷৽ঢ়ଶʹ෮ݩ ো֐ൃੜ࣌ͷঢ়ଶʹ໭Δʂ
  27. class Account implements ActorInterface { private float $balance = 10.0;

    private array $processedMessages = []; public function __construct( private readonly float $serviceUptime, private readonly float $refusalProbability, private readonly float $busyProbability ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Message\Credit: case $message instanceof Message\Debit: $this->handleBalanceChange($context, $message); break; case $message instanceof Message\GetBalance: $context->respond($this->balance); break; } }
  28. class Account implements ActorInterface { private float $balance = 10.0;

    private array $processedMessages = []; public function __construct( private readonly float $serviceUptime, private readonly float $refusalProbability, private readonly float $busyProbability ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Message\Credit: case $message instanceof Message\Debit: $this->handleBalanceChange($context, $message); break; case $message instanceof Message\GetBalance: $context->respond($this->balance); break; } } ΞΫλʔʹϝοηʔδ͕౸ୡ͢Δͱ ࣮ߦ͞ΕΔSFDFJWFϝιου
  29. class Account implements ActorInterface { private float $balance = 10.0;

    private array $processedMessages = []; public function __construct( private readonly float $serviceUptime, private readonly float $refusalProbability, private readonly float $busyProbability ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Message\Credit: case $message instanceof Message\Debit: $this->handleBalanceChange($context, $message); break; case $message instanceof Message\GetBalance: $context->respond($this->balance); break; } } ೖग़ۚϝοηʔδʹରԠ
  30. class Account implements ActorInterface { private float $balance = 10.0;

    private array $processedMessages = []; public function __construct( private readonly float $serviceUptime, private readonly float $refusalProbability, private readonly float $busyProbability ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Message\Credit: case $message instanceof Message\Debit: $this->handleBalanceChange($context, $message); break; case $message instanceof Message\GetBalance: $context->respond($this->balance); break; } } ࢒ߴௐ੔ʹؔ͢Δॲཧ
  31. class Account implements ActorInterface { private float $balance = 10.0;

    private array $processedMessages = []; public function __construct( private readonly float $serviceUptime, private readonly float $refusalProbability, private readonly float $busyProbability ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Message\Credit: case $message instanceof Message\Debit: $this->handleBalanceChange($context, $message); break; case $message instanceof Message\GetBalance: $context->respond($this->balance); break; } } ࢒ߴ֬ೝͷϝοηʔδ͕དྷΔͱ ૹ৴ݩʹฦ٫
  32. private function handleBalanceChange( ContextInterface $context, Message\ChangeBalance $message ): void {

    if ($this->alreadyProcessed($message->replyTo)) { $context->send($message->replyTo, $this->processedMessages[(string) $message->replyTo]); return; } if ($message instanceof Message\Debit && ($message->amount + $this->balance) < 0) { $context->send($message->replyTo, new Message\InsufficientFunds()); return; } if ($this->refusePermanently()) { $this->processedMessages[(string) $message->replyTo] = new Message\Refused(); $context->send($message->replyTo, new Message\Refused()); return; } if ($this->isBusy()) { $context->send($message->replyTo, new Message\ServiceUnavailable()); return; } if ($this->shouldFailBeforeProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } usleep(random_int(0, 150) * 1000); $this->balance += $message->amount; $this->processedMessages[(string) $message->replyTo] = new Message\Ok(); if ($this->shouldFailAfterProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } $context->send($message->replyTo, new Message\Ok()); }
  33. private function handleBalanceChange( ContextInterface $context, Message\ChangeBalance $message ): void {

    if ($this->alreadyProcessed($message->replyTo)) { $context->send($message->replyTo, $this->processedMessages[(string) $message->replyTo]); return; } if ($message instanceof Message\Debit && ($message->amount + $this->balance) < 0) { $context->send($message->replyTo, new Message\InsufficientFunds()); return; } if ($this->refusePermanently()) { $this->processedMessages[(string) $message->replyTo] = new Message\Refused(); $context->send($message->replyTo, new Message\Refused()); return; } if ($this->isBusy()) { $context->send($message->replyTo, new Message\ServiceUnavailable()); return; } if ($this->shouldFailBeforeProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } usleep(random_int(0, 150) * 1000); $this->balance += $message->amount; $this->processedMessages[(string) $message->replyTo] = new Message\Ok(); if ($this->shouldFailAfterProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } $context->send($message->replyTo, new Message\Ok()); } ࢒ߴௐ੔ʹؔ͢Δॲཧ
  34. private function handleBalanceChange( ContextInterface $context, Message\ChangeBalance $message ): void {

    if ($this->alreadyProcessed($message->replyTo)) { $context->send($message->replyTo, $this->processedMessages[(string) $message->replyTo]); return; } if ($message instanceof Message\Debit && ($message->amount + $this->balance) < 0) { $context->send($message->replyTo, new Message\InsufficientFunds()); return; } if ($this->refusePermanently()) { $this->processedMessages[(string) $message->replyTo] = new Message\Refused(); $context->send($message->replyTo, new Message\Refused()); return; } if ($this->isBusy()) { $context->send($message->replyTo, new Message\ServiceUnavailable()); return; } if ($this->shouldFailBeforeProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } usleep(random_int(0, 150) * 1000); $this->balance += $message->amount; $this->processedMessages[(string) $message->replyTo] = new Message\Ok(); if ($this->shouldFailAfterProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } $context->send($message->replyTo, new Message\Ok()); } Ҿ਺ͰͦΕͧΕͷঢ়ଶΛ γϛϡϨʔτ
  35. private function handleBalanceChange( ContextInterface $context, Message\ChangeBalance $message ): void {

    if ($this->alreadyProcessed($message->replyTo)) { $context->send($message->replyTo, $this->processedMessages[(string) $message->replyTo]); return; } if ($message instanceof Message\Debit && ($message->amount + $this->balance) < 0) { $context->send($message->replyTo, new Message\InsufficientFunds()); return; } if ($this->refusePermanently()) { $this->processedMessages[(string) $message->replyTo] = new Message\Refused(); $context->send($message->replyTo, new Message\Refused()); return; } if ($this->isBusy()) { $context->send($message->replyTo, new Message\ServiceUnavailable()); return; } if ($this->shouldFailBeforeProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } usleep(random_int(0, 150) * 1000); $this->balance += $message->amount; $this->processedMessages[(string) $message->replyTo] = new Message\Ok(); if ($this->shouldFailAfterProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } $context->send($message->replyTo, new Message\Ok()); } ϝοηʔδʹؚ·ΕΔૹ৴ઌʹ ฦ৴
  36. private function handleBalanceChange( ContextInterface $context, Message\ChangeBalance $message ): void {

    if ($this->alreadyProcessed($message->replyTo)) { $context->send($message->replyTo, $this->processedMessages[(string) $message->replyTo]); return; } if ($message instanceof Message\Debit && ($message->amount + $this->balance) < 0) { $context->send($message->replyTo, new Message\InsufficientFunds()); return; } if ($this->refusePermanently()) { $this->processedMessages[(string) $message->replyTo] = new Message\Refused(); $context->send($message->replyTo, new Message\Refused()); return; } if ($this->isBusy()) { $context->send($message->replyTo, new Message\ServiceUnavailable()); return; } if ($this->shouldFailBeforeProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } usleep(random_int(0, 150) * 1000); $this->balance += $message->amount; $this->processedMessages[(string) $message->replyTo] = new Message\Ok(); if ($this->shouldFailAfterProcessing()) { $context->send($message->replyTo, new Message\InternalServerError()); return; } $context->send($message->replyTo, new Message\Ok()); } ΞΫλʔࣗ਎͕ঢ়ଶ؅ཧ
  37. readonly class AccountProxy implements ActorInterface { public function __construct( private

    Ref $target, private Closure $createMessage ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Started: $context->send($this->target, ($this->createMessage)($context->self())); $context->setReceiveTimeout(new DateInterval('PT2S')); break; case $message instanceof Refused: case $message instanceof Ok: $context->cancelReceiveTimeout(); $context->send($context->parent(), $message); break; case $message instanceof InsufficientFunds: case $message instanceof InternalServerError: case $message instanceof ReceiveTimeout: case $message instanceof ServiceUnavailable: throw new RuntimeException('Unexpected message'); } } }
  38. readonly class AccountProxy implements ActorInterface { public function __construct( private

    Ref $target, private Closure $createMessage ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Started: $context->send($this->target, ($this->createMessage)($context->self())); $context->setReceiveTimeout(new DateInterval('PT2S')); break; case $message instanceof Refused: case $message instanceof Ok: $context->cancelReceiveTimeout(); $context->send($context->parent(), $message); break; case $message instanceof InsufficientFunds: case $message instanceof InternalServerError: case $message instanceof ReceiveTimeout: case $message instanceof ServiceUnavailable: throw new RuntimeException('Unexpected message'); } } } "DUPS͕ىಈ͢ΔͱྲྀΕͯ͘Δ ಺෦ϝοηʔδ 4UBSUFE
  39. readonly class AccountProxy implements ActorInterface { public function __construct( private

    Ref $target, private Closure $createMessage ) { } public function receive(ContextInterface $context): void { $message = $context->message(); switch (true) { case $message instanceof Started: $context->send($this->target, ($this->createMessage)($context->self())); $context->setReceiveTimeout(new DateInterval('PT2S')); break; case $message instanceof Refused: case $message instanceof Ok: $context->cancelReceiveTimeout(); $context->send($context->parent(), $message); break; case $message instanceof InsufficientFunds: case $message instanceof InternalServerError: case $message instanceof ReceiveTimeout: case $message instanceof ServiceUnavailable: throw new RuntimeException('Unexpected message'); } } } -FU*U$SBTI ͜Ε·Ͱͷ1)1ͱҟͳΔߟ͑ํ
  40. $inMemoryProvider = $this->inMemoryProvider(); (new ForWithProgress( $this->numberOfIterations, $this->intervalBetweenConsoleUpdates, true, false ))->everyNth(

    fn($i) => print("Started {$i}/{$this->numberOfIterations} processes\n"), function ($i, $nth) use ($context, $inMemoryProvider) { $fromAccount = $this->createAccount($context, "FromAccount{$i}"); $toAccount = $this->createAccount($context, "ToAccount{$i}"); $actorName = "Transfer Process {$i}"; $factory = new TransferFactory( $context, $this->uptime, $this->retryAttempts, $inMemoryProvider ); $transfer = $factory->createTransfer($actorName, $fromAccount, $toAccount, 10); $this->transfers[] = (string) $transfer->getRef(); if ($i === $this->numberOfIterations && !$nth) { print("Started {$i}/{$this->numberOfIterations} processes\n"); } } );
  41. $inMemoryProvider = $this->inMemoryProvider(); (new ForWithProgress( $this->numberOfIterations, $this->intervalBetweenConsoleUpdates, true, false ))->everyNth(

    fn($i) => print("Started {$i}/{$this->numberOfIterations} processes\n"), function ($i, $nth) use ($context, $inMemoryProvider) { $fromAccount = $this->createAccount($context, "FromAccount{$i}"); $toAccount = $this->createAccount($context, "ToAccount{$i}"); $actorName = "Transfer Process {$i}"; $factory = new TransferFactory( $context, $this->uptime, $this->retryAttempts, $inMemoryProvider ); $transfer = $factory->createTransfer($actorName, $fromAccount, $toAccount, 10); $this->transfers[] = (string) $transfer->getRef(); if ($i === $this->numberOfIterations && !$nth) { print("Started {$i}/{$this->numberOfIterations} processes\n"); } } ); 'SPN"DDPVOU9ͱ5P"DDPVOU9ͱ͍͏໊લͰ "DDPVOUΞΫλʔΛੜ੒
  42. $inMemoryProvider = $this->inMemoryProvider(); (new ForWithProgress( $this->numberOfIterations, $this->intervalBetweenConsoleUpdates, true, false ))->everyNth(

    fn($i) => print("Started {$i}/{$this->numberOfIterations} processes\n"), function ($i, $nth) use ($context, $inMemoryProvider) { $fromAccount = $this->createAccount($context, "FromAccount{$i}"); $toAccount = $this->createAccount($context, "ToAccount{$i}"); $actorName = "Transfer Process {$i}"; $factory = new TransferFactory( $context, $this->uptime, $this->retryAttempts, $inMemoryProvider ); $transfer = $factory->createTransfer($actorName, $fromAccount, $toAccount, 10); $this->transfers[] = (string) $transfer->getRef(); if ($i === $this->numberOfIterations && !$nth) { print("Started {$i}/{$this->numberOfIterations} processes\n"); } } ); 5SBOTGFS1SPDFTTΞΫλʔʹ ̎ͭͷ"DDPVOUΞΫλʔϦϑΝϨϯε "DDPVOUΞΫλʔؒͰ૬ޓॲཧ
  43. go(function () { $system = ActorSystem::create(); $system->getLogger()->info('Starting'); $numberOfTransfers = 10;

    $intervalBetweenConsoleUpdates = 1; $uptime = 99.99; $retryAttempts = 3; $refusalProbability = 0.01; $busyProbability = 0.01; $props = ActorSystem\Props::fromProducer( fn() => new Runner( $numberOfTransfers, $intervalBetweenConsoleUpdates, $uptime, $refusalProbability, $busyProbability, $retryAttempts ), ActorSystem\Props::withSupervisor( new ActorSystem\Strategy\OneForOneStrategy( $retryAttempts, new DateInterval('PT10S'), new ActorSystem\Supervision\DefaultDecider(), ) ), ); $system->root()->spawnNamed($props, 'runner'); });
  44. go(function () { $system = ActorSystem::create(); $system->getLogger()->info('Starting'); $numberOfTransfers = 10;

    $intervalBetweenConsoleUpdates = 1; $uptime = 99.99; $retryAttempts = 3; $refusalProbability = 0.01; $busyProbability = 0.01; $props = ActorSystem\Props::fromProducer( fn() => new Runner( $numberOfTransfers, $intervalBetweenConsoleUpdates, $uptime, $refusalProbability, $busyProbability, $retryAttempts ), ActorSystem\Props::withSupervisor( new ActorSystem\Strategy\OneForOneStrategy( $retryAttempts, new DateInterval('PT10S'), new ActorSystem\Supervision\DefaultDecider(), ) ), ); $system->root()->spawnNamed($props, 'runner'); }); 3VOOFSΞΫλʔੜ੒
  45. go(function () { $system = ActorSystem::create(); $system->getLogger()->info('Starting'); $numberOfTransfers = 10;

    $intervalBetweenConsoleUpdates = 1; $uptime = 99.99; $retryAttempts = 3; $refusalProbability = 0.01; $busyProbability = 0.01; $props = ActorSystem\Props::fromProducer( fn() => new Runner( $numberOfTransfers, $intervalBetweenConsoleUpdates, $uptime, $refusalProbability, $busyProbability, $retryAttempts ), ActorSystem\Props::withSupervisor( new ActorSystem\Strategy\OneForOneStrategy( $retryAttempts, new DateInterval('PT10S'), new ActorSystem\Supervision\DefaultDecider(), ) ), ); $system->root()->spawnNamed($props, 'runner'); }); 3VOOFSΞΫλʔʹର͢Δઓུ
  46. go(function () { $system = ActorSystem::create(); $system->getLogger()->info('Starting'); $numberOfTransfers = 10;

    $intervalBetweenConsoleUpdates = 1; $uptime = 99.99; $retryAttempts = 3; $refusalProbability = 0.01; $busyProbability = 0.01; $props = ActorSystem\Props::fromProducer( fn() => new Runner( $numberOfTransfers, $intervalBetweenConsoleUpdates, $uptime, $refusalProbability, $busyProbability, $retryAttempts ), ActorSystem\Props::withSupervisor( new ActorSystem\Strategy\OneForOneStrategy( $retryAttempts, new DateInterval('PT10S'), new ActorSystem\Supervision\DefaultDecider(), ) ), ); $system->root()->spawnNamed($props, 'runner'); }); نఆ৚݅Ҏ্ࣦഊ͢Δͱ ଞͷγεςϜʹΤεΧϨʔτ͢ΔͳͲ͕Մೳ