RSS Articles RSS Notes de lectures

Introduction

Microsoft BizTalk Server orchestrations essentially allow running a sequence of steps in a process, which can involve several systems. Orchestrations can quickly get hard to understand and maintain because of various sources of complexity, as shown below.

This article describes a few practices, in addition to ones that are already well documented[1], that can be used to reduce the complexity of orchestrations and make them easier to understand.

Variables and messages scopes

In C# code, a good practice is to declare variables as close as possible to where they are used, so that the variable's role becomes more obvious. 


Example C# code where scope is wider than necessary


Improved C# code, where each variable's scope is restricted to where it is being used

An analogous practice in a BizTalk orchestration is to declare variables or messages in the scope where they will be used, instead of declaring all variables and messages at the top level of the orchestrations. It's even usually a good idea to introduce scopes in orchestrations whose role is simply to group a sequence of related steps, including the variables and messages used by these steps.  This way, the variables/messages will be encapsulated in the context where they are relevant. It also improves performance by reducing the number of variables that have to be serialized at each persistence point.


Example orchestration with scope wider than necessary

 


Refactored example: all messages, variables and correlation sets moved to the scope where they are used

This practice can make a complex orchestration significantly easier to understand, which is important for maintainability. When looking at an orchestration's variables/messages, a developer will want to know:

  • What’s the role of the variable/message?
  • Where's the variable/message used?
  • What are the impacts if a change is made to the way the variable/message is used (for example for a fix, requirements change or refactoring)?

If an orchestration has 30 variables or messages declared in "global" scope (top level), a developer would have to look at each shape to find the answer to these questions. This can make understanding an orchestration very hard and even harder to modify it.

To avoid this problem, it is better to move, as much as possible, variables and message declarations to the smaller scope where they will be used. This way, to analyze the role of a variable/message or the impacts of a change, a developer will only have to look at a few shapes instead of the whole orchestration.

Obviously, there will still be messages at the "global" scope (at least the initial message an orchestration receives), but these should be kept to a minimum.

Split orchestrations into smaller orchestrations

The "Call Orchestration" shape can be used to call orchestrations that contain common code that is reused across several orchestrations. However, its usefulness is not limited to enabling code reuse. An even more beneficial use of this shape is to break down a complex orchestration into smaller ones, even if each smaller orchestration is not reused anywhere else.

Each smaller orchestration can be given a descriptive name saying what it does. This allows having a high level orchestration that's mostly composed of a sequence of calls to other orchestrations. By reading the name of each called orchestration, a developer can understand what the higher level orchestration is doing, without looking at the technical details.This is similar to breaking down, in C#, a complex method into smaller methods.

Note that grouping variables into scopes can be a good first step before splitting some parts of an orchestration into smaller orchestrations: start by dividing the orchestration into scopes, and then move the more complex scopes to a separate orchestration.

The example below shows how the example in the previous section could be refactored into smaller orchestrations:


Refactored orchestration: delegates most of its work to other called orchestrations

Information hiding by using object-oriented classes in orchestrations

Putting messages/variables in orchestration scopes is one basic way to hide information in the context where it is relevant. Splitting complex code in several smaller methods (or smaller orchestrations) is another way. To go further, it's also possible to uses .Net classes to encapsulate (hide information) by moving code and its associated data to a class.

This is a widely used practice in C# applications, but it can also be used with BizTalk orchestrations. To use it, we take advantage of the fact that an orchestration variable can be any .Net class, as long as it's Serializable (marked with the [Serializable]attribute). (non-Serializable classes could be used in atomic scopes, but this is usually not the preferred solution)

A serializable class used as an orchestration variable can encapsulate (by using private members) some state about the process being performed by the orchestration. If the orchestration is dehydrated, all the class member's (including private ones) are serialized and persisted to BizTalk's database. When the orchestration is rehydrated, that state is restored.

This means that the orchestration can delegate some steps of a process to a class, to simplify the orchestration. Also, since the variables required by the class are kept as private fields of the class, the orchestration does not need to know about these variables. Therefore, it is usually a better solution than simply calling classes with static methods, which would require passing the values of several variables between the orchestration and the various methods it invokes throughout a process.

For example, the sequence diagram below shows the interactions between an orchestration and a class with static methods. The orchestration has to keep variables for the return values of each of the class's static methods, and then pass these values to other methods of the same static class.


Overview of procedural interactions between an orchestration and a static class

This can be simplified by keeping intermediate values as private fields or local variables in the class. Also, not all methods of the class need to be public, some can be made private, and only the public methods need to be invoked by the orchestration. Finally, the "MapInternalClientIdToOtherSystemClientId" method should be moved to another class, so that each class has a single responsibility (one class for file name generation, another for Client ID mapping). However, the orchestration needs only to interact with the "FileNameGenerator" class, and the exact type of ClientID needed for the file names (other system ClientID instead of internal ClientID) is a fact hidden to the orchestration.

Refactored object-oriented example, with reduce number of informations that must be known to the orchestration

This allows limiting the impacts of a change. Instead of having ripple effects throughout an orchestration, a change can now be done within a class. The change is also easier to test using simple C# unit tests (for example using NUnit[2] or MSTest[3]).

When designing these classes, take in consideration the SOLID principles[4]. One of these principles is the Single Responsibility Principle, which states that a class should have only one responsibility. Also note that it's possible for a BizTalk orchestration to call a class which itself calls other classes in order to better separate responsibilities across classes, as long as all the classes are Serializable. 

More complex cases could even use objects that are part of a domain model (designed using Domain-Driven Design), or design patterns such as the State pattern. However, this possibility can be taken too far by giving too many responsibilities to BizTalk (the orchestration and its associated classes). Orchestrations are useful to coordinate between different parts of a business process (for example, by calling web services that represent a business application's public interface), but they are usually not the best location for the business logic itself.

Conclusion 

BizTalk Orchestrations, like .Net code, can quickly become hard to maintain if they are too complex. This article described a few practices that are commonly used in object-oriented languages, and has shown how the same practices can be applied to BizTalk orchestrations:

  • Limit a variable's scope to the smallest possible scope where the variable is used;

  • Breaking down complex components into a set of smaller ones;

  • Information hiding using private fields in .Net classes, and public methods that perform work on the object's private state, which prevents the calling code from having to know how the object's state is managed.

Applies to

 All Microsoft BizTalk Server versions from 2004 to 2010.

References