The Problem With Wrapped Notifications
If you are familiar with how the paradigms of pub/sub and asynchronous-notification map to web services you probably know that there are two competing specifications in this space, WS-BaseNotification and WS-Eventing. As a member of the WS-Notification "family", the former is an OASIS Standard while the later is, as of this writing, just a W3C Member Submission (now being developed by the W3C's Web Services Resource Access Working Group). The two specifications are very similar. They both define a set of operations whereby one party (the Subscriber) can subscribe to a series of asynchronous notification messages (sent in the form of SOAP messages) from another party (the Event Source) and thereafter manage that subscription. Both specifications leverage WS-Addressing for things like specifying where the notifications should be sent, creating unique references for managing multiple subscriptions, etc. However, there are places where the two specifications differ. One of these is WS-BN's use of "wrappers"; a generic XML element that acts as an envelope for the actual event information in the notification. Although WS-BaseNotification supports the use of "raw notifications", most of the specification deals with wrapped notifications. As I will show in the rest of this article, wrapped notifications are one of those ideas that, at first, seem worthwhile but which ultimately cause more problems than they solve.
Terminology
Another place where WS-BN and WS-Eventing differ is in their terminology. We need to pick one, so I'll flip a coin an go with WS-Eventing's. From Section 2.3 of WS-Eventing:
- Event Source - A Web service that sends Notifications and accepts requests to create subscriptions.
- Event Sink - A Web service that receives Notifications.
- Notification - A one-way message sent to indicate that an event has occurred.
- Subscriber - A Web service that sends requests to create, renew, and/or delete subscriptions.
- Subscription Manager - A Web service that accepts requests to manage get the status of, renew, and/or delete subscriptions on behalf of an event source.
The Case for Wrapping
The case for using wrapped Notifications rests on one or more of the following points.
- Wrapped Notifications support generic Event Sink listeners that can accept Notifications regardless of their type (i.e. XML structure). This allows a single listener to act as the Notification Endpoint for multiple subscriptions.
- Wrapped Notifications make it easier to implement things like brokers and store-and-forward queues that deal with Notifications in a generic way (i.e. where the structure and contents of the Notification are irrelevant).
- Wrapped Notifications are necessary if you want to work with dynamic Notification types who's structure may not be known at build-time.
Sample Wrapped Message
The following is an example of what a wrapped Notification might look like on the wire. I've invented a wrapper for WS-Eventing because we want to examine the differences between wrapped and unwrapped Notifications, not compare and contrast WS-BaseNotification and WS-Eventing.
Example Message 101 <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
02 xmlns:sc009="http://www.wstf.org/docs/scenarios/sc009"
03 xmlns:wsa="http://www.w3.org/2005/08/addressing"
04 xmlns:wse="http://www.w3.org/2009/02/eventing">
05 <soap:Header>
06 <wsa:MessageID>uuid:c58980ddc0a9010321162116d316bf43</wsa:MessageID>
07 <wsa:To>http://webservice.bea.com/POClient/notify12port</wsa:To>
08 <wsa:Action>http://www.w3.org/2009/02/eventing/NotifyEnv</wsa:Action>
09 </soap:Header>
10 <soap:Body>
11 <wse:NotifyEnv>
12 <sc009:OrderInfo xmlns:sc009="http://www.wstf.org/docs/scenarios/sc009">
13 <sc009:OrderID>sc009-order-5</sc009:OrderID>
14 <sc009:OrderDate>2008-11-11T21:32:36.718-05:00</sc009:OrderDate>
15 <sc0:OrderPrice>100</sc009:OrderPrice>
16 <sc009:OrderStatus>Approved</sc009:OrderStatus>
17 <sc009:LastUpdate>2008-11-11T21:33:01.765-05:00</sc009:LastUpdate>
18 </sc009:OrderInfo>
19 </wse:NotifyEnv>
20 </soap:Body>
21 </soap:Envelope>
The wrapper in the above example is the (fictional) wse:NotifyEnv element in lines 11-19. It contains the Notification information (the sc009:OrderInfo) 12-18). It should be readily apparent that what we are doing here is tunneling through SOAP; treating SOAP as transport mechanism and using it to carry another, higher-level envelope (the wse:NotifyEnv element).
Wrapper Schema and Generated Types
Now that we've had a look at what a wrapped message looks like on the wire, let's take a look at what the schema and WSDL for our wrapper might look like:
Example WSDL 1
01 <wsdl:definitions . . .>
02 <wsdl:types>
03 <xs:schema targetNamespace="http://www.w3.org/2009/01/eventing">
04 <xs:element name="NotifyEnv">
05 <xs:complexType mixed="true">
06 <xs:sequence>
07 <xs:any namespace="##any"
08 processContents="lax"
09 minOccurs="0"
10 maxOccurs="unbounded"/>
11 </xs:sequence>
12 </xs:complexType>
13 </xs:element>
14 </xs:schema>
15 </wsdl:types>
16 <wsdl:message name="NotifyEvent">
18 <wsdl:part name="body" element="wse:NotifyEnv"/>
19 </wsdl:message>
20 <wsdl:portType name="GenericSinkPortType">
21 <wsdl:operation name="NotifyEvent">
22 <wsdl:input message="wse:NotifyEvent"/>
23 </wsdl:operation>
24 </wsdl:portType>
25 . . .
26 </wsdl:definitions>
This seems straightforward enough, but let's look at the code that get's generated when we run this through a JAX-WS, WSDL-to-Java processor (I've elided the JAXB annotations for clarity):
Example Code 1
01 public class NotifyEnv {
02 protected List<Object> content;
03 public List<Object> getContent() {
04 if (content == null) {
05 content = new ArrayList<Object>();
06 }
07 return content;
08 }
09 }
Considering that this class derives from an XML element wrapped around a sequence of xs:anys, it shouldn't be surprising that all we have to work with is a collection of references to the java.lang.Object class. On the other hand, what the heck are we supposed to do with a list of Objects? It seems like we've lost something. Compare this with the code that is generated if we build our Event Sink from a WSDL that describes a raw Notification Interface like this one:
Example WSDL 2
01 <wsdl:definitions . . .>
02 <wsdl:types>
03 <xs:schema xmlns:sc009="http://www.wstf.org/docs/scenarios/sc009"
04 targetNamespace=http://www.wstf.org/docs/scenarios/sc009>
05 <xs:include schemaLocation="http://www.wstf.org/docs/scenarios/sc009/sc009.xsd"/>
06 </xs:schema>
07 </wsdl:types>
08 <wsdl:message name="NotifyPOStatus">
09 <wsdl:part name="part1" element="tns:OrderInfo"/>
10 </wsdl:message>
11 <wsdl:portType name="PONotifyPortType">
12 <wsdl:operation name="PONotify">
13 <wsdl:input message="tns:NotifyPOStatus"/>
14 </wsdl:operation>
15 </wsdl:portType>
16 . . .
17 </wsdl:definitions>
The above WSDL describes the interface that an Event Sink must implement if it subscribes to an Event Source that emits "NotifyPOStatus" Notifications. Note that the XML schema definition of the sc009:OrderInfo element used in line 9 can be found by de-referencing the schemaLocation attribute value on line 5. Here's the code that is generated:
Example Code 201 public class OrderInfoType {
02 protected String orderID;
03 protected XMLGregorianCalendar orderDate;
04 protected BigDecimal orderPrice;
05 protected String orderStatus;
06 protected XMLGregorianCalendar lastUpdate;
07 protected String orderComments;
08 . . .
09 public String getOrderID() {
10 return orderID;
11 }
12 public void setOrderID(String value) {
13 this.orderID = value;
14 }
15 public XMLGregorianCalendar getOrderDate() {
16 return orderDate;
17 }
18 . . .
19 }
Ok so, big deal; WSDL works. But that's just the point! In the wrapped case, there isn't anything for WSDL to work with. Because our NotifyEnv type must be able to wrap arbitrary XML ("xs:any"), there isn't any type information available to generate "fully featured" classes with getters and setters, etc. nor the marshalling code that converts to/from these classes and XML. From a WSDL perspective, wrapped notifications are weakly typed. Obviously we could write code that marshals and unmarshals to/from our notification data and XML. The "list of Objects" in Example Code 1 isn't really just a list of Objects. If we dug into it we might find that the objects were instances of some DOM class like ElementNSImpl, and we could certainly write code that parsed this DOM tree and built a useful class. But writing marshalling code is a time consuming and error prone task with little business value. To increase our efficiency, we might try something like XMLBeans to generate our marshalling and unmarshalling code. If we added some form of type metadata to our wrapped Notifications, the code that services the NotifyEvent operation could use that information to invoke the correct XMLBeans-generated parser. However, we still have to figure out some way to advertise what the schema types are for the set of possible Notifications that might result from a Subscription. It's not clear how long we would continue down this path before it occurred to us that what we were doing was inventing a "WSDL inside of WSDL" to go along with our "SOAP inside of SOAP". The long and short of it is that, by tunneling over SOAP, the use of wrapped Notifications has forced us to abandon our WSDL-based tools and invent another level of tools that do effectively the same thing.
The Case Against Wrapping
Earlier we laid out some of the arguments for why wrapped Notifications were necessary or at least a good idea. Let's go through them again, this time from an opposing view:
- Generic Listeners: It is unclear why anyone would want a generic Notification listener. Ultimately you need to dispatch the Notification message to some application code that will do something interesting with it. Most SOAP/HTTP stacks already have a generic listener that accepts HTTP requests and dispatches them to the appropriate message handling chain. Raw Notifications leverage this when the Event Sink creates an endpoint for accepting incoming Notifications. Duplicating this functionality at a higher layer is redundant.
- Brokers and Queues: While it is true that infrastructure components need to deal with Notifications in a generic way, it doesn't necessarily follow that the wrapper/envelope needs to be described in WSDL. The generic wrapper should be the SOAP envelope itself. It is possible to build a service that accepts arbitrary SOAP messages and re-publishes them, or persists them, etc. Building a broker this way is harder than building one from a WSDL-defined envelope but (a) there will be far more source and sink applications developed than there will be brokers and (b) vendors that should build brokers and queues, etc., not customers.
- Dynamic Notification Types: There are situations in which the structure and content of the Notification cannot be completely known at build-time, but it is seldom the case that the content of the Notification is completely arbitrary. Often there will be a known base with possible extensions. This situation can be modeled using traditional XML extension mechanisms. In cases where the content of the Notification is completely arbitrary it is difficult to imagine how you would write application logic that could effectively deal with this situation, regardless of how the data was transported.
Summary
The core of the case against wrapped Notifications lies in the answer to the following question "Why would I use SOAP-based technologies to implement a notification mechanism?" I assert that the answer has to be either (a) because I am invested in web services programming paradigms, tools, and runtimes and I want to continue to use these, and/or (b) because I am interested in interoperability between Event Sources and Event Sinks across different middleware implementations. In either case the value of a SOAP-based approach is diminished if we try to build your notification mechanism "on top of" SOAP rather than "within" SOAP. As I have shown, defining a wrapper message/operation in WSDL creates a situation in which we can't use WSDL to describe the application specific, notification types that are emitted by the Event Source. This means that we can't use our WSDL tools to generate language-specific binding classes and the code that marshals to and from the XML representations of those Notifications. This impacts both the efficiency and the interoperability of our notification infrastructure, which where the reasons we decided to use web services in the first place.