So what exactly is a service? A service is essentially a well-defined interface to an autonomous chunk of functionality, which usually corresponds to a specific business process. That might sound a lot like a regular old object-oriented component to you. While both services and components have commonality in that they expose discrete interfaces of functionality, a service is more focused on the capabilities offered than the packaging. Services are meant to be higher-level, business-oriented offerings that provide technology abstraction and interoperability within a multipurpose “services” tier of your architecture.
What makes up a service? Typically you’ll find:
- Contract: Explains what operations the service exposes, types of messages, and exchange patterns supported by this service, and any policies that explain how this service is used.
- Messages: The data payload exchanged between the service consumer and provider.
- Implementation: The portion of the service which actually processes the requests, executes the expected business functionality, and optionally returns a response.
- Service provider: The host of the service which publishes the interface and manages the lifetime of the service.
- Service consumer: Ideally, a service has someone using it. The service consumer is aware of the available service operations and knows how to discover the provider and determine what type of messages to transmit.
- Facade: Optionally, a targeted facade may be offered to particularly service consumers. This sort of interface may offer a more simplified perspective on the service, or provide a coarse-grained avenue for service invocation.
What is the point of building a service? I’d say it’s to construct an asset capable of being reused which means that it’s a discrete, discoverable, self-describing entity that can be accessed regardless of platform or technology.
Service-oriented architecture is defined as an architectural discipline based on loosely-coupled, autonomous chunks of business functionality which can be used to construct composite applications. Through the rest of this article we get a chance to flesh out many of the concepts that underlie that statement. Let’s go ahead and take a look at a few of the principles and characteristics that I consider most important to a successful service-oriented BizTalk solution. As part of each one, I’ll explain the thinking behind the principle and then call out how it can be applied to BizTalk Server solutions.
Many of the fundamental SOA principles actually stem from this particular one. In virtually all cases, some form of coupling between components is inevitable. The only way we can effectively build software is to have interrelations between the various components that make up the delivered product. However, when architecting solutions, we have distinct design decisions to make regarding the extent to which application components are coupled. Loose coupling is all about establishing relationships with minimal dependencies.
What would a tightly-coupled application look like? In such an application, we’d find components that maintained intimate knowledge of each others’ working parts and engaged in frequent, chatty synchronous calls amongst themselves. Many components in the application would retain state and allow consumers to manipulate that state data. Transactions that take place in a tightly coupled application probably adhere to a two-phase commit strategy where all components must succeed together in order for each data interaction to be finalized. The complete solution has its ensemble of components compiled together and singularly deployed to one technology platform. In order to run properly, these tightly-coupled components rely on the full availability of each component to fulfill the requests made of them.
On the other hand, a loosely-coupled application employs a wildly different set of characteristics. Components in this sort of application share only a contract and keep their implementation details hidden. Rarely preserving state data, these components rely on less frequent communication where chunky input containing all the data the component needs to satisfy its requestors is shared. Any transactions in these types of applications often follow a compensation strategy where we don’t assume that all components can or will commit their changes at the same time. This class of solution can be incrementally deployed to a mix of host technologies. Asynchronous communication between components, often through a broker, enables a less stringent operational dependency between the components that comprise the solution.
What makes a solution loosely coupled then? Notably, the primary information shared by a component is its interface. The consuming component possesses no knowledge of the internal implementation details. The contract relationship suffices as a means of explaining how the target component is used. Another trait of loosely coupled solutions is coarse-grained interfaces that encourage the transmission of full data entities as opposed to fine-grained interfaces, which accept small subsets of data. Because loosely-coupled components do not share state information, a thicker input message containing a complete impression of the entity is best. Loosely-coupled applications also welcome the addition of a broker which proxies the (often asynchronous) communication between components. This mediator permits a rich decoupling where runtime binding between components can be dynamic and components can forgo an operational dependency on each other.
Let’s take a look at an example of loose coupling that sits utterly outside the realm of technology.
Completely non-technical loose coupling example
When I go to a restaurant and place an order with my waiter, he captures the request on his pad and sends that request to the kitchen. The order pad (the contract) contains all the data needed by the kitchen chef to create my meal. The restaurant owner can bring in a new waiter or rotate his chefs and the restaurant shouldn’t skip a beat as both roles (services) serve distinct functions where the written order is the intersection point and highlight of their relationship.
Why does loose coupling matter? By designing a loosely-coupled solution, you provide a level of protection against the changes that the application will inevitably require over its life span. We have to reduce the impact of such changes while making it possible to deploy necessary updates in an efficient manner.
How does this apply to BizTalk Server solutions?
A good portion of the BizTalk Server architecture was built with loose coupling in mind. Think about the BizTalk MessageBox which acts as a broker facilitating communication between ports and orchestrations while limiting any tight coupling. Receive ports and send ports are very loosely coupled and in many cases, have absolutely no awareness of each other. The publish-and-subscribe bus thrives on the asynchronous transfer of self-describing messages between stateless endpoints. Let’s look at a few recommendations of how to build loosely-coupled BizTalk applications.
Orchestrations are a prime place where you can either go with a tightly-coupled or loosely-coupled design route. For instance, when sketching out your orchestration process, it’s sure tempting to use that Transform shape to convert from one message type to another. However, a version change to that map will require a modification of the calling orchestration. When mapping to or from data structures associated with external systems, it’s wiser to push those maps to the edges (receive/send ports) and not embed a direct link to the map within the orchestration.
BizTalk easily generates schemas for line-of-business (LOB) systems and consumed services. To interact with these schemas in a very loosely coupled fashion, consider defining stable entity schemas (i.e. “canonical schemas”) that are used within an orchestration, and only map to the format of the LOB system in the send port. For example, if you need to send a piece of data into an Oracle database table, you can certainly include a map within an orchestration which instantiates the Oracle message. However, this will create a tight coupling between the orchestration and the database structure. To better insulate against future changes to the database schema, consider using a generic intermediate data format in the orchestration and only transforming to the Oracle-specific format in the send port.
How about those logical ports that we add to orchestrations to facilitate the transfer of messages in and out of the workflow process? When configuring those ports, the Port Configuration Wizard asks you if you want to associate the port to a physical endpoint via the Specify Now option. Once again, pretty tempting. If you know that the message will arrive at an orchestration via a FILE adapter, why not just go ahead and configure that now and let Visual Studio.NET create the corresponding physical ports during deployment? While you can independently control the auto-generated physical ports later on, it’s a bad idea to embed transport details inside the orchestration file.
On each subsequent deployment from Visual Studio.NET, the generated receive port will have any out-of-band changes overwritten by the deployment action.
Chaining orchestration together is a tricky endeavor and one that can leave you in a messy state if you are too quick with a design decision. By “chaining orchestrations”, I mean exploiting multiple orchestrations to implement a business process. There are a few options at your disposal listed here and ordered from most coupled to least coupled.
- Call Orchestration or Start Orchestration shape: An orchestration uses these shapes in order to kick off an additional workflow process. The Call Orchestration is used for synchronous connection with the new orchestration while the Start Orchestration is a fire-and-forget action. This is a useful tactic for sharing state data (for example variables, messages, ports) from the source orchestration to the target. However, both options require a tight coupling of the source orchestration to the target. Version changes to the target orchestration would likely require a redeployment of the source orchestration.
- Partner direct bound ports: These provide you the capability to communicate between orchestrations using ports. In the forward partner direct binding scenario, the sender has a strong coupling to the receiver, while the receiver knows nothing about the sender. This works well in situations where there are numerous senders and only one receiver. Inverse partner direct binding means that there is a tight coupling between the receiver and the sender. The sender doesn’t know who will receive the command, so this scenario is intended for cases where there are many receivers for a single sender. In both cases, you have tight coupling on one end, with loose-coupling on the other.
- MessageBox direct binding: This is the most loosely-coupled way to share data between orchestrations. When you send a message out of an orchestration through a port marked for MessageBox direct binding, you are simply placing a message onto the bus for anyone to consume. The source orchestration has no idea where the data is going, and the recipients have no idea where it’s been.
MessageBox direct binding provides a very loosely-coupled way to send messages between different orchestrations and endpoints.
While MessageBox direct binding is great, you do lose the ability to send the additional state data that a Call Orchestration shape will provide you. So, as with all architectural decisions, you need to decide if the sacrifice (loose coupling, higher latency) is worth the additional capabilities.
Decisions can be made during BizTalk messaging configuration that promote a loosely-coupled BizTalk landscape. For example, both receive ports and send ports allow for the application of maps to messages flying past. In each case, multiple maps can be added. This does NOT mean that all the maps will be applied to the message, but rather, it allows for sending multiple different message types in, and emitting a single type (or even multiple types) out the other side. By applying transformation at the earliest and latest moments of bus processing, you loosely couple external formats and systems from internal canonical formats. We should simply assume that all upstream and downstream systems will change over time, and configure our application accordingly.
Another means for loosely coupling BizTalk solutions involves the exploitation of the publish-subscribe architecture that makes up the BizTalk message bus. Instead of building solely point-to-point solutions and figuring that a SOAP interface makes you service oriented, you should also consider loosely coupling the relationship between the service input and where the data actually ends up. We can craft a series of routing decision that take into account message content or context and direct the message to one or more relevant processes/endpoints. While point-to-point solutions may be appropriate for many cases, don’t neglect a more distributed pattern where the data publisher does not need to explicitly know exactly how their data will be processed and routed by the message bus.
When identifying subscriptions for our send ports, we should avoid tight coupling to metadata attributes that might limit the reuse of the port. For instance, you should try to create subscriptions on either the message type or message content instead of context attributes such as the inbound receive port name. Ports should be tightly coupled to the MessageBox and messages it stores, not to attributes of its publisher. That said, there are clearly cases where a subscriber is specifically looking for data that corresponds to a targeted piece of metadata such as the subject line of the email received by BizTalk. As always, design your solution in a way that solves your business problem in an efficient manner.