Building a reusable subflow
February 1, 2011 2 Comments
A lot has been written already about performing common logic to messages using a variety of approaches, but they typically do logging using a message logger or trace primitive. They shy away from using a Service Invoke within a subflow, and for good reason, as the SI primitive’s output terminal returns the response from its invocation and it’s tricky to propagate the original request to the parent flow. Particularly when you don’t know what this original request looks like!
This post focuses on how to model you data and build a reusable subflow to perform message enrichment without knowing the specific message type you’re operating on.
And the reason we are using a subflow, as opposed to a service gateway pattern is because, when a subflow lives in a shared library, it can be selectively reused as if it were a mediation primitive. So we can create a new module, or a new component in an existing assembly, or add it as a primitive to an existing mediation flow. It is a very flexible approach.
The linchpin that enables us to build generic subflows lies in the design and modeling of interfaces and data objects. We would like the subflow to be message type agnostic, we want it to operate within the context of various interactions. In other words, we make no assumptions on the shape and size of the parent flow we might find ourselves within.
But we must have some certainties. Like, if we use the subflow (as in this example) for message enrichment, at some point we will need to navigate to elements in the message body.
In order to guarantee those certainties we will enforce a set of best practices at design time.
- Every interface operation has a single input, a single output, and a common set of faults.
- Every message type inherits from a base type. This base type contains attributes common to all messages. These are our certainties.
The picture above shows the input parameter type for the retrieveCustomerDetails operation. We create the RetrieveCustomerDetailsRequest business object by inheriting from BaseRequest. The BaseRequest has a attribute that we choose to name ‘header’ of type RequestHeader, which itself inherits from CommonHeader.
Any attribute we model into the CommonHeader BusinessObject is guaranteed to be present in the message. We just need to figure out how to navigate to them (later).
The same is true for faults: create faults that inherit from a BaseFault, add a BaseFault attribute named consistletly (this example uses ‘header’), and make this attribute a superset of CommonHeader. This will ensure your certainties will be also present in faults.
We can start building the subflow now. Create the subflow in a shared library so you can reuse it from any project depending on it.
The purpose of the subflow in this particular example will be to populate a regionId attribute, which is present in all messages (because is a CommonHeader attribute).
We can call this subflow EnrichRegionId. Below is a screen capture of the complete subflow in the mediation flow editor.
At the heart of the subflow there is a Service Invoke primitive, the purpose of which is to retrieve a region code from an external system.
The sublow has an input and three output nodes, the input represents the request message as passed in by the parent flow, the first output will propagate the enriched request message to the parent flow, and the following two output nodes are used for propagating faults.
For those three nodes, their terminal message types are set to anyType.
What this means is that, at development time, the tooling is told that any WSDL type can be fired in or out of these terminals (at runtime this is always true, terminal type information exists for the primary benefit of the tooling).
This is one of the V7 improvements I like the most. I can force V6 into something similar but it is nowhere near as elegant.
Now, lets take a look at what’s going on in the subflow.
I have no idea of which message type I will receive, the service invoke primitive half way down the flow loses my request (its output terminals all relate to its own operation) and I have to find a way to propagate it to the parent flow.
The solution is to keep the /body part of the message in context throughout the flow, using a message element setter.
I used the correlation context out of habit. I don’t have a response flow to worry about here. The context type is xsdAny, so any body type can be safely stored here. One thing to watch out for is developers modeling context data at the parent flow level. This can break things and has to be worked around (by asking developers to include an xsdAny attribute in their context objects, that the subflow can use to store the body), but lets keep it simple.
So that’s my request body stored away. Now we have to operate on /body attributes on a /body type which is mostly unknown.
All we know are the certainties we designed into our data model.
We also know that, for as long as we use the default binding style on our interfaces (document literal wrapped) the SMO path into the body payload will be predictable.
Heres what the SMO representation of an updateCustomerDetails request looks like:
We can get hold of /body, the ‘header’ attribute is our designed in certainty, but we know nothing about what’s between them. All we know is that we are going to find an attribute called ‘header’ two levels down from /body.
The good news is that we can use custom XPath to navigate to it:
In my XML map, the one that provides the Service Invoke with its input parameters, I use a custom XPath transform to retrieve a field from the body in the correlation context:
You can see how if hop over the uncertainties to get to what I need.
After the service invoke, I use a message element setter to enrich the body and propagate it to the parent flow:
1 enriches the message body currently in context and 2 copies it to the body propagated to the parent flow.
Here’s a closer look at 1:
The external system used to retrieve the region id is mediated, so the service invoke uses an internal interface which can return the same faults used throughout the solution. This means I can simply wire those faults to the corresponding output nodes. I’m treating service invoke timeouts and failures as serviceFaults so I can transform and wire those too.
At parent flow level, adding the subflow is very simple. Just drop it in the request flow editor, wire it up and accept the automatic casting proposed by WID (those Set Message Type primitives are automatically generated):
The response flow is inconsequential. Simple wires for message pass-through: