|
| 1 | +/** |
| 2 | + * Copyright (C) 2015 Roland Kuhn <http://rolandkuhn.com> |
| 3 | + */ |
| 4 | +package com.reactivedesignpatterns.chapter14 |
| 5 | + |
| 6 | +import akka.typed._ |
| 7 | +import akka.typed.ScalaDSL._ |
| 8 | +import akka.typed.AskPattern._ |
| 9 | +import java.util.UUID |
| 10 | +import akka.event.Logging |
| 11 | +import scala.concurrent.Future |
| 12 | +import akka.util.Timeout |
| 13 | +import scala.concurrent.duration._ |
| 14 | +import akka.pattern.AskTimeoutException |
| 15 | + |
| 16 | +object AskPattern { |
| 17 | + |
| 18 | + sealed trait MyCommands |
| 19 | + case class StartVerificationProcess(userEmail: String, replyTo: ActorRef[VerificationProcessResponse]) extends MyCommands |
| 20 | + private case class MyEmailResult(correlationID: UUID, status: StatusCode, explanation: String) extends MyCommands |
| 21 | + |
| 22 | + sealed trait VerificationProcessResponse |
| 23 | + case class VerificationProcessStarted(userEmail: String) extends VerificationProcessResponse |
| 24 | + case class VerificationProcessFailed(userEmail: String) extends VerificationProcessResponse |
| 25 | + |
| 26 | + case class SendEmail(sender: String, recipients: List[String], |
| 27 | + body: String, correlationID: UUID, |
| 28 | + replyTo: ActorRef[SendEmailResult]) |
| 29 | + |
| 30 | + case class SendEmailResult(correlationID: UUID, status: StatusCode, |
| 31 | + explanation: String) |
| 32 | + |
| 33 | + sealed trait StatusCode |
| 34 | + object StatusCode { |
| 35 | + case object OK extends StatusCode |
| 36 | + case object Failed extends StatusCode |
| 37 | + } |
| 38 | + |
| 39 | + def withoutAskPattern(emailGateway: ActorRef[SendEmail]): Behavior[StartVerificationProcess] = |
| 40 | + ContextAware[MyCommands] { ctx => |
| 41 | + val log = Logging(ctx.system.eventStream, "VerificationProcessManager") |
| 42 | + var statusMap = Map.empty[UUID, (String, ActorRef[VerificationProcessResponse])] |
| 43 | + val adapter = ctx.spawnAdapter((s: SendEmailResult) => MyEmailResult(s.correlationID, s.status, s.explanation)) |
| 44 | + |
| 45 | + Static { |
| 46 | + case StartVerificationProcess(userEmail, replyTo) => |
| 47 | + val corrID = UUID.randomUUID() |
| 48 | + val request = SendEmail("verification@example.com", List(userEmail), constructBody(userEmail, corrID), corrID, adapter) |
| 49 | + emailGateway ! request |
| 50 | + statusMap += corrID -> (userEmail, replyTo) |
| 51 | + ctx.schedule(5.seconds, ctx.self, MyEmailResult(corrID, StatusCode.Failed, "timeout")) |
| 52 | + case MyEmailResult(corrID, status, expl) => |
| 53 | + statusMap.get(corrID) match { |
| 54 | + case None => |
| 55 | + log.error("received SendEmailResult for unknown correlation ID {}", corrID) |
| 56 | + case Some((userEmail, replyTo)) => |
| 57 | + status match { |
| 58 | + case StatusCode.OK => |
| 59 | + log.debug("successfully started the verification process for {}", userEmail) |
| 60 | + replyTo ! VerificationProcessStarted(userEmail) |
| 61 | + case StatusCode.Failed => |
| 62 | + log.info("failed to start the verification process for {}: {}", userEmail, expl) |
| 63 | + replyTo ! VerificationProcessFailed(userEmail) |
| 64 | + } |
| 65 | + statusMap -= corrID |
| 66 | + } |
| 67 | + } |
| 68 | + }.narrow[StartVerificationProcess] |
| 69 | + |
| 70 | + def withChildActor(emailGateway: ActorRef[SendEmail]): Behavior[StartVerificationProcess] = |
| 71 | + ContextAware { ctx: ActorContext[StartVerificationProcess] => |
| 72 | + val log = Logging(ctx.system.eventStream, "VerificationProcessManager") |
| 73 | + |
| 74 | + Static { |
| 75 | + case StartVerificationProcess(userEmail, replyTo) => |
| 76 | + val corrID = UUID.randomUUID() |
| 77 | + val childActor = ctx.spawnAnonymous(Props(FullTotal[SendEmailResult] { |
| 78 | + case Sig(ctx, PreStart) => |
| 79 | + ctx.setReceiveTimeout(5.seconds) |
| 80 | + Same |
| 81 | + case Sig(_, ReceiveTimeout) => |
| 82 | + log.warning("verification process initiation timed out for {}", userEmail) |
| 83 | + replyTo ! VerificationProcessFailed(userEmail) |
| 84 | + Stopped |
| 85 | + case Msg(_, SendEmailResult(`corrID`, StatusCode.OK, _)) => |
| 86 | + log.debug("successfully started the verification process for {}", userEmail) |
| 87 | + replyTo ! VerificationProcessStarted(userEmail) |
| 88 | + Stopped |
| 89 | + case Msg(_, SendEmailResult(`corrID`, StatusCode.Failed, explanation)) => |
| 90 | + log.info("failed to start the verification process for {}: {}", userEmail, explanation) |
| 91 | + replyTo ! VerificationProcessFailed(userEmail) |
| 92 | + Stopped |
| 93 | + case Msg(_, SendEmailResult(wrongID, _, _)) => |
| 94 | + log.error("received wrong SendEmailResult for corrID {}", corrID) |
| 95 | + Same |
| 96 | + })) |
| 97 | + val request = SendEmail("verification@example.com", List(userEmail), constructBody(userEmail, corrID), corrID, childActor) |
| 98 | + emailGateway ! request |
| 99 | + } |
| 100 | + } |
| 101 | + |
| 102 | + def withAskPattern(emailGateway: ActorRef[SendEmail]): Behavior[StartVerificationProcess] = |
| 103 | + ContextAware { ctx => |
| 104 | + val log = Logging(ctx.system.eventStream, "VerificationProcessManager") |
| 105 | + implicit val timeout = Timeout(5.seconds) |
| 106 | + import ctx.executionContext |
| 107 | + |
| 108 | + Static { |
| 109 | + case StartVerificationProcess(userEmail, replyTo) => |
| 110 | + val corrID = UUID.randomUUID() |
| 111 | + val response: Future[SendEmailResult] = |
| 112 | + emailGateway ? (SendEmail("verification@example.com", List(userEmail), constructBody(userEmail, corrID), corrID, _)) |
| 113 | + response.map { |
| 114 | + case SendEmailResult(`corrID`, StatusCode.OK, _) => |
| 115 | + log.debug("successfully started the verification process for {}", userEmail) |
| 116 | + VerificationProcessStarted(userEmail) |
| 117 | + case SendEmailResult(`corrID`, StatusCode.Failed, explanation) => |
| 118 | + log.info("failed to start the verification process for {}: {}", userEmail, explanation) |
| 119 | + VerificationProcessFailed(userEmail) |
| 120 | + case SendEmailResult(wrongID, _, _) => |
| 121 | + log.error("received wrong SendEmailResult for corrID {}", corrID) |
| 122 | + VerificationProcessFailed(userEmail) |
| 123 | + }.recover { |
| 124 | + case _: AskTimeoutException => |
| 125 | + log.warning("verification process initiation timed out for {}", userEmail) |
| 126 | + VerificationProcessFailed(userEmail) |
| 127 | + }.foreach(result => replyTo ! result) |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + private def constructBody(userEmail: String, corrID: UUID): String = ??? |
| 132 | + |
| 133 | +} |
0 commit comments