Now that you have seen the entities that can interact with a message, taken a close look at message anatomy, and seen the different message encoders that ship with WCF, let’s examine how we can express where we want a message to be sent. After all, messages aren’t terribly useful unless we can send them to a receiver. Just as the postal service requires a well-defined addressing structure, service-oriented messages also require a well-defined addressing structure. In this section, we will build our own addressing scheme, see whether it is broadly applicable to messaging applications, and then relate it to the addressing scheme that is typically used with WCF messages.
Service-oriented messages specify the ultimate receiver directly in the message. This is a subtle but important point. If the target of the message is specified in the message itself, a whole set of messaging patterns becomes possible. You will learn more about messaging patterns in Chapter 3, “Message Exchange Patterns, Topologies, and Choreographies”.
When we insert an address directly into a message, we pave the way for more efficient message processing. Efficiency can mean many things, and in this sense, I am talking about the ease of implementing more advanced messaging behaviors, as opposed to the speed with which a message can be created. Just as writing an address on an envelope takes time, serializing an address into a message takes time. However, just as writing an address on an envelope improves postal efficiency, serializing an address into a message improves processing efficiency, especially when more advanced messaging behaviors are implemented (like message routers and intermediaries).
So what sorts of items should we place in an address? For starters, an address should identify the ultimate receiver we want to send a message to. Since the ultimate receiver might be host-ing multiple services, we should also have a way to uniquely identify the specific service on the ultimate receiver. It’s possible that one address element might be able to describe both the ultimate receiver hosting the service and the service itself. Take the following example:
In the age of the Internet, we have come to understand that this address includes both the location of the ultimate receiver and a protocol that we can use to access it. Since most SO messages are SOAP messages, we need some SOAP construct that will convey the same information.
We have learned already that SOAP messages can contain three types of elements: an envelope, one header with multiple header blocks, and a body. The envelope isn’t a good choice, since the envelope can occur only once. That leaves the header blocks and the body as the only two remaining candidates. So what about the body? From our earlier discussion, we know that the body is intended for use only by the ultimate receiver. By process of elimination, we see that the only logical place for us to put an address is in the header of a message. So what should this header block look like? How about:
<Envelope> <Header> <To>http://wintellect.com/OrderService</To> </Header> <Body> …</Body> </Envelope>
At a high level, this simple XML structure accomplishes our goal of identifying the ultimate receiver and service we would like to send a message to.
It might also be useful to add sender information to the message, sort of like a return address on a letter. Adding sender information to the message serves two purposes: to indicate the sender to the ultimate receiver, and to indicate the sender to any intermediaries. We have already seen that a URL can be used to identify the target of a message. So maybe we can in fact use the same construct to identify the sender. Take the following example:
<Envelope> <Header> <To>http://wintellect.com/ReceiveService</To> <From>http://wintellect.com/SendService</From> </Header> <Body> …</Body> </Envelope>
Adding this simple element to the SOAP message indicates where the message came from, and it can be used either by an intermediary or by the ultimate receiver.
What if there’s a problem processing the message? Every modern computing platform has some way to indicate errors or exceptions. These error handling mechanisms make our applications more robust, predictable, and easier to debug. It is natural to want the same mechanism in our messaging applications. Given that we already have a <To> and a <From> in our message, we could send all of our error notifications to the address specified in the <From> element. What if we want error notifications to go to a location specifically reserved for handling errors? In this case, we have to create yet another element:
<Envelope> <Header> <To>http://wintellect.com/OrderService</To> <From>http://wintellect.com/SendService</From> <Error>http://wintellect.com/ErrorService</Error> </Header> <Body> …</Body> </Envelope>
Adding the <Error> element to the header clearly indicates where the sender would like error messages to be sent. Because this URL is in the header, it can be used by either the ultimate recipient or an intermediary.
Our simple addressing scheme requires the sender to add our To, From, and Error information as header blocks in the message and then send the message to the ultimate receiver. As processing occurs at an intermediary or the ultimate receiver, an error might occur. Given that we now have the error element in our message, the intermediary or ultimate receiver should be able to send us an error message. This error message will be an entirely different message from the one originally sent. From the initial sender’s perspective, receiving error messages is troubling in and of itself, but it is especially troubling if we don’t know the message send that caused the error. It would be great for debugging, troubleshooting, and auditing if there were a way for us to correlate the original message with the error message. To do this, we need two separate elements in our message: a message identifier element, and a message correlation element. Let’s look at the message identifier first:
<Envelope> <Header> <MessageID></MessageID> <To>http://wintellect.com/OrderService</To> <From>http://wintellect.com/SendService</From> <Error>http://wintellect.com/ErrorService</Error> </Header> <Body>...</Body> </Envelope>
In this example, we have called our message identifier element <MessageID>. For now, we can think of MessageID’s value as a globally unique number. Upon generation, this number does not mean anything to other participants. If the initial sender generates a message as described earlier, all intermediaries and the ultimate receiver know where to send an error message, but they can also use MessageID to reference the particular message that caused the error. If the ultimate receiver for error messages and the sender are different, these processes must exchange information between themselves to fully understand the message send that caused the error.
If we assume that either an intermediary or the ultimate receiver has encountered a problem processing a message, it follows that a new message should be sent to the address specified in the error element. If an intermediary or an ultimate receiver sends an entirely new message, the intermediary or the ultimate receiver becomes the sender of the new message. Likewise, the address specified in the original Error header block now becomes the ultimate receiver of the new message. We just established that the initial message that caused the error will contain a MessageID element. Somehow, the error message needs to contain a reference to this MessageID element. The correlation between the original message and the error message can be described by using a RelatesTo element:
<Envelope> <Header> <MessageID></MessageID> <RelatesTo></RelatesTo> <To>http://www.wintelelct.com/ErrorService</To> <From>http://wintellect.com/OrderService</From> <Error>http://wintellect.com/ErrorService</Error> </Header> <Body> …</Body> </Envelope>
The error service at http://wintellect.com/ErrorService is the ultimate recipient of this message. When this error service reads the message, information about the message that caused the error is available in the RelatesTo element. Although the error service might not do anything with the RelatesTo information, it can be used for debugging, troubleshooting, and auditing. Notice also in this example that the To, From, and Error elements have all changed to reflect the new context of the message.
Let’s step away from error messages for a bit and go back to the initial message. As you’ve seen, we have a way to specify the ultimate receiver, the address of the initial sender, a unique identifier for the message, and where error notifications should be sent. It is possible that we want a way to specify a reply address while still specifying the address of the initial sender. Examples of this behavior abound in the real world. For example, invoices commonly have a “Send further correspondence here” address that is different from the initial sending address. Our SO messages need a similar construct. We can once again use the notion of an address combined with a new element to describe this information. We will call this new element ReplyTo in the following example:
<Envelope> <Header> <MessageID></MessageID> <To>http://wintellect.com/OrderService</To> <ReplyTo>http://wintellect.com/OrderReplyService</ReplyTo> <From>http://wintellect.com/SendService</From> <Error>http://wintellect.com/ErrorService</Error> </Header> <Body> …</Body> </Envelope>
It might seem repetitive to have both a From and a ReplyTo element in the same message. It’s important to remember, however, that From and ReplyTo might be describing exactly the same service, but they can also describe two different services. Adding a ReplyTo element simply adds more flexibility and functionality to the set of header blocks we are creating.
This next header block will require a little context, especially if you don’t have much experience dealing with Web services. Once again, I would like to step into a real-world example first. We all know that postal addresses can contain an ATTN line. Typically, this line is used to route the parcel to a particular person, department, or operation. Take a look at the following postal address:
Contoso Boomerang Corporation ATTN: New Customer Subscriptions 2611 Boomerang Way Atlanta, GA 30309
From experience, we know that this address refers to Contoso Boomerang Corporation. More precisely, we know that the address specifically refers to the New Customer Subscriptions group within Contoso Boomerang Corporation.
If you expect to send mail to a large company, you may not have to specifically address a particular department. You could send mail to Contoso Boomerang Corporation and expect someone to ultimately open the mail, make a decision about who should receive the mail, and route the mail to the inferred recipient. Clearly this process will take longer than if we specifically addressed the message to the correct department or group.
Contoso Boomerang Corporation might have several groups that can receive mail. Each group might have its own set of actions to perform. For example, Contoso might have one group responsible for signing up new customers, another group responsible for customer support, and yet another group for new product development. At an abstract level, addresses can specify different levels of granularity for the destination, and each destination might have its own set of tasks or actions to perform.
So far, we have created elements that define the ultimate receiver, a reply-to receiver, an error notification receiver, a message identifier, a message correlation mechanism, and the initial sender. We have not, however, defined a way to indicate an action or operation for the message. Let’s assume, for now, that we can use another header element containing a URL as a way to identify an action or operation. The following example illustrates this assumption with the addition of a new header:
<Envelope> <Header> <MessageID></MessageID> <To>http://wintellect.com/OrderService</To> <Action>urn:ProcessOrder/Action> <ReplyTo>http://wintellect.com/OrderReplyService</ReplyTo> <From>http://wintellect.com/SendService</From> <Error>http://wintellect.com/ErrorService</Error> </Header> <Body> …</Body> </Envelope>
In this example, the Action element states that the ProcessMsg operation should be performed on this message. It is possible that OrderService defines additional operations. For example, we can send another message to the archive message operation by using the following Action element:
<Envelope> <Header> <MessageID></MessageID> <To>http://wintellect.com/OrderService</To> <Action>urn:ArchiveMessage</Action> <ReplyTo>http://someotherurl.com/OrderReplyService</ReplyTo> <From>http://wintellect.com/SendService</From> <Error>http://wintellect.com/ErrorService</Error> </Header> <Body> ... </Body> </Envelope>
We have just arbitrarily defined seven elements that help us address messages. By no means can we assume that our element names will be universally adopted. We could, however, build our own infrastructure that understands these elements and use this infrastructure in each of our messaging participants. In other words, we can’t send these messages to an application that does not understand what our seven message headers mean. Likewise, our application could not receive messages that contained different addressing headers. For example, another application vendor could have defined message headers like the following:
<Envelope> <Header> <MessageIdentifier>1</MessageIdentifier> <SendTo>http://wintellect.com/OrderService</SendTo> <Op>http://wintellect.com/OrderService/ArchiveMessage</Op> <Reply>http://someotherurl.com/OrderReplyService</Reply> <SentFrom>http://wintellect.com/SendService</SentFrom> <OnError>http://wintellect.com/ErrorService</OnError> </Header> <Body>...</Body> </Envelope>
Applications that contain our infrastructure cannot process this message.
If we were to take a survey of most enterprise applications, we would see that software vendors have followed this exact model in defining their own messages. For several years, SOAP has been the agreed-upon message format, but there was no agreement on the header blocks that could appear in a message, and as a result, applications could not easily interoper-ate. True SOAP message interoperability requires a set of header blocks that are common across all software vendors. As mentioned in Chapter 1, the WS-* specifications go a long way toward solving this problem by defining a common set of messaging headers.