Document Message
Use a Document Message to convey information to a receiver, but without indicating how the data should be used (see Figure 6.2). This is different from a Command Message (202) in that while the command likely passes data parameters, it also specifies the intended use. A Document Message also differs from an Event Message (207) in that while the event conveys information without specifying its intended use, an event associates the data it carries with a past occurrence in the business domain. Although a Document Message communicates information about the domain, it does so without indicating that the concept is a fact that expresses a specific past occurrence in the business domain. See also Domain Events, as discussed in Implementing Domain-Driven Design [IDDD].
Figure 6.2 The Sender, by means of a Document Message, provides the Receiver with information but without indicating how it should be used.
Oftentimes a Document Message serves as the reply in the Request-Reply (209) pattern. In the implementation diagram the Receiver may have previously sent a Command Message (202) to the Sender to request to data, as in Request-Reply (209), or the Sender may send the Document Message to the Receiver without the Receiver previously requesting the information of the Sender.
The following is a Document Message that conveys data about a quotation fulfillment:
case class QuotationFulfillment( rfqId: String, quotesRequested: Int, priceQuotes: Seq[PriceQuote], requester: ActorRef)
The information provided by the QuoteFulfillment document includes the unique identity of the request for quotation, the number of quotations that were requested, the number of PriceQuote instances that were actually obtained, and a reference to the actor that originally requested the quotations. The PriceQuote itself could be considered a full Document Message but in this example is just part of the composed QuoteFulfillment Document Message:
case class PriceQuote( quoterId: String, rfqId: String, itemId: String, retailPrice: Double, discountPrice: Double)
As the PriceQuote structure indicates, it is not the size or complexity of the message type that determines whether it is a Document Message. Rather, it is the fact that information is conveyed without indicating intended usage (command) or that it is conveyed as part of an application outcome (event). To reinforce this, consider the following Command Message (202) and Event Message (207), respectively, that are used in conjunction with obtaining the QuoteFulfillment Document Message:
case class RequestPriceQuote( rfqId: String, itemId: String, retailPrice: Money, orderTotalRetailPrice: Money) case class PriceQuoteFulfilled(priceQuote: PriceQuote)
The first case class, which is a Command Message (202), is used to request a set of quotations for a specific item. The second case class, that being an Event Message (207), is published when a price quotation has been fulfilled. These are both quite different from the QuoteFulfillment Document Message, which merely carries information about the results of the previously requested price quotation.
Managing Flow and Process
You can use a Document Message to assist in managing workflow or long-running processes [IDDD]. Send a Document Message on a Point-to-Point Channel (151) to one actor at a time, each implementing a step in the process. As each step completes, it appends to the document that it received, applying the changes for the processing step as it is completed. The actor for the current step then dispatches the appended Document Message on to the actor of the next processing step. This dispatch-receive-append-dispatch recurrence continues until the process has completed.
It is the final step that determines what to do with the now fully composed Document Message. Since long-running processes may be composed from a few to many different smaller processes, it’s possible that the final step in a given process merely completes one branch of a larger process. In all of this, no Document Message is actually mutated to an altered state. Instead, each step composes a new Document Message as a combination of the current document and any new information to be appended. The merging of the current Document Message with new data might be performed as a simple concatenation. Assuming a linear process where each step is responsible for gathering a PriceQuote from a given vendor, this example shows how a QuotationFulfillment can be appended:
case class QuotationFulfillment( rfqId: String, quotesRequested: Int, priceQuotes: Seq[PriceQuote], requester: ActorRef) { def appendWith( fulfilledPriceQuote: PriceQuote): QuotationFulfillment { QuotationFulfillment( rfqId, quotesRequested, priceQuotes :+ fulfilledPriceQuote, requester) } }
The Document Message itself may contain some data describing how each processing step actor is to dispatch to the next step. This might be handled by placing the actor address+name of each step inside the original document in the order in which the steps should occur. As each step completes, it simply looks up the next actor and dispatches, sending it the appended document.
val quotationFulfillment = quotationFulfillment.appendWith(newPriceQuote) quotationFulfillment.stepFollowing(name) ! quotationFulfillment
As an alternative to this document-based lookup approach, you may instead choose to use the Akka DistributedPubSubMediator, as discussed in Publish-Subscribe Channel (154), to dispatch to a single actor in the cluster without the need to actually look up the actor. This approach uses the DistributedPubSubMediator.Send router message. If using Send, you would simply place the name of each processing step actor in the document, leaving off the address. The contract of the DistributedPubSubMediator ensures that a matching actor somewhere in the cluster will receive the next Document Message per a specified routing policy.
When a long-running process has a complex routing specification, it would be best to use a Process Manager (292) to coordinate dispatching to each step. Generally, you would need such a Process Manager (292) when the dispatching rules include conditional branching based on values appended to the Document Message by one or more steps.