Reactive Messaging Patterns with the Actor Model: Message Construction
In Chapter 4, “Messaging with Actors,” I discussed messaging with actors, but I didn’t cover much about the kinds of messages that should be created and sent. Each message must convey the intent of the sender’s reason to communicate with the receiver. As Enterprise Integration Patterns [EIP] shows, there may be any number of motivations based on the following:
- Message intent: Why are you sending a message? Are you requesting another actor to perform an operation? If so, use a Command Message (202). Are you informing one or more other actors that you have performed some operation? In that case, use an Event Message (207). Have you been asked for some large body of information that you must convey to the requester via a Message (130)? The request can be fulfilled using a Document Message (204).
- Returning a response: When there is a contract between two actors that follows Request-Reply (209), the actor receiving the request needs to provide a reply, or response. The request is a Command Message (202) and the reply is generally a Document Message (204). Since you are using the Actor model, the actor receiving the request knows the Return Address (211) of the sender and can easily reply. If there are multiple incoming requests that are related to one another or multiple outgoing replies that are logically bundled, use a Correlation Identifier (215) to associate separate messages into one logical package.
- Huge amounts of data: Sometimes you need more than a Correlation Identifier (215) to bundle related messages. What happens if you can correlate a set of messages but you also need to ensure that they are ordered according to some application-specific sequence? That’s the job of a Message Sequence (217).
- Slow messages: As I have taken the opportunity to repeat in several places through the text, the network is unreliable. When a Message (130) of whatever type must travel over the network, there is always a change that network latency will affect its delivery. Even so, there are also latencies in actors when, for example, they have a lot of work to do before they can handle your requests. Although this can point to the need to redesign some portion of the application to deal with workload in a more acceptable time frame, you also need to do something about it when encountered. You can use a Message Expiration (218) and perhaps the Dead Letter Channel (172) to signal to the system that something needs to be done about the latency situation, if in fact it is deemed unacceptable.
- Message version: Oftentimes a Document Message (204), or actually an Event Message (207) or even a Command Message (202), can have a number of versions throughout its lifetime. You can identify the version of the message using a Format Indicator (222).
In much the same way that you must think about the kind of Message Channel (128) you will use for various application and integration circumstances, you must also think about and design your messages specifically to deal with the reaction and concurrency scenarios at hand.
Command Message
When a message-sending actor needs to cause an action to be performed on the receiving actor, the sender uses a Command Message.
If you are familiar with the Command-Query Separation principle [CQS], you probably think of a Command Message as one that, when handled by the receiver, will cause a side effect on the receiver (see Figure 6.1). After all, that’s what the C in CQS stands for: a request that causes a state transition. Yet, a Command Message as described by Enterprise Integration Patterns [EIP] may also be used to represent the request for a query—the Q in CQS. Because of the overlap in intended uses by Enterprise Integration Patterns [EIP], when designing with the CQS principle in mind and discussing a message that causes a query to be performed, it is best to instead use the explicit term query message. Even so, this is not to say that a message-based actor system must be designed with CQS in mind. Depending on the system, it may work best for a given Command Message to both alter state and elicit a response message, as discussed in Request-Reply (209).
Figure 6.1 The Sender, by means of a Command Message, tells the Receiver to do something.
Each Command Message, although sent by a requestor, is defined by the receiver actor as part of its public contract. Should the sent Command Message not match one defined as part of the receiver’s contract, it could be redirected to the Invalid Message Channel (170).
Command Messages are designed as imperative exhortations of actions to be performed; that is, the exhortation for an actor to perform some behavior. The Command Message will contain any data parameters and collaborating actor parameters necessary to perform the action. For example, besides passing any data that is required to perform the command, a Command Message may also contain a Return Address (211) to indicate which actor should be informed about possible side effects or outcomes.
In essence you can think of a Command Message as a representation of an operation invocation. In other words, a Command Message captures the intention to invoke an operation, but in a way that allows the operation to be performed at a time following the message declaration.
The following case classes implement Command Messages for an equities trading domain:
case class ExecuteBuyOrder( portfolioId: String, symbol: String, quantity: Int, price: Money) case class ExecuteSellOrder( portfolioId: String, symbol: String, quantity: Int, price: Money)
Here a StockTrader receives the two Command Messages but rejects any other message type by sending them to the Dead Letter Channel (172), which doubles as an Invalid Message Channel (170):
class StockTrader(tradingBus: ActorRef) extends Actor { ... def receive = { case buy: ExecuteBuyOrder => ... case sell: ExecuteSellOrder => ... case message: Any => context.system.deadLetters ! message } }
Normally a Command Message is sent over a Point-to-Point Channel (151) because the command is intended to be performed once by a specific receiving actor. To send a broadcast type of message, likely you will want to use an Event Message (207) along with Publish-Subscribe Channel (154).