Request-Reply
When a message is sent from one actor to another, it is considered a request. When the receiver of the request message needs to send a message back to the request sender, the message is a reply. As shown in Figure 6.4, a common usage pattern of Request-Reply has the requestor sending a Command Message (202) and the receiver replying with a Document Message (204). In such a case, and as described in Command Message (202), the command is probably a Query Message [IDDD].
Figure 6.4 A Requestor and a Reactor collaborate with each other using Request-Reply.
While the requestor will normally send a Command Message (202), replying with a Message Document Message (204) is not a strict requirement. Still, if you consider the document payload of the reply to be any simple structure, not necessarily a complex one, then it is often appropriate to refer to the reply as a Document Message (204). The point is that the document carries data but does not indicate what the consumer should do with it.
Request-Reply is quite simple and straightforward to implement using the Actor model. In fact, Request-Reply is considered part of the basic actor semantics. Here is how it works:
package co.vaughnvernon.reactiveenterprise.requestreply import akka.actor._ import co.vaughnvernon.reactiveenterprise._ case class Request(what: String) case class Reply(what: String) case class StartWith(server: ActorRef) object RequestReply extends CompletableApp(1) { val client = system.actorOf(Props[Client], "client") val server = system.actorOf(Props[Server], "server") client ! StartWith(server) awaitCompletion println("RequestReply: is completed.") } class Client extends Actor { def receive = { case StartWith(server) => println("Client: is starting...") server ! Request("REQ-1") case Reply(what) => println("Client: received response: " + what) RequestReply.completedStep() case _ => println("Client: received unexpected message") } } class Server extends Actor { def receive = { case Request(what) => println("Server: received request value: " + what) sender ! Reply("RESP-1 for " + what) case _ => println("Server: received unexpected message") } }
The following output is produced by the Client and Server:
Client: is starting... Server: received request value: REQ-1 Client: received response: RESP-1 for REQ-1 Client: is completing...
The three classes at the top of the file are the messages that can be sent. Following the message types there is the application (App) object, and then the Client and Server actors. Note that the use of awaitCompletion() in the App bootstrap object makes the application stick around until the two actors complete.
The first message, StartWith, is sent to the Client to tell it to start the Request-Reply scenario. Although StartWith is a Command Message (202) request, note that the Client does not produce a reply to the App. The StartWith message takes one parameter, which is the instance of the Server actor (actually an ActorRef). The Client makes a request to the Server, and the Server makes a reply to the Client. The Request and Reply are the other two different message types.
Specifically, a Client knows how to StartWith and how to react to Reply messages, while a Server knows how to react to Request messages. If the Client receives anything but StartWith and Reply, it simply reports that it doesn’t understand. The Server does the same if it receives anything but a Request.
These details notwithstanding, the main point of this simple Scala/Akka example is to show how Request-Reply is accomplished using the Actor model. It’s pretty simple. Wouldn’t you agree? Request-Reply is a natural form of programming using the Actor model. As you can see, the Server doesn’t need to know it is replying to the Client actor. It only needs to know it is replying to the sender of the Request, and the sender of the Request needs to know that it will receive a Reply to its Request.
All of this happens asynchronously. The Client and the Server share nothing; that is, their states are completely encapsulated and protected. That, and the fact that each actor will handle only one message at a time, allows the asynchronous message handling to be completely lock free.