Flow Orchestrations

Posted by - raja 2008/01/16 18:19:27

Creating use case specific session facades from domain specific services.

BACKGROUND

Most enterprise applications follow a layered architecture. The best practice is to layer an application as a UI layer and the Middleware layer which in turn interacts with an EIS (Enterprise Information Services) layer . When implementing Middleware services, it is common practice to use the Session Facade pattern. The simplistic version of the facade pattern is to provide a request specific facade that in turn accesses underlying domain specific services. The facade abstracts the underlying model and also provides additional horizontal services.  The underlying domain layer itself is designed to be utilized by multiple facades. Hence the domain layer must refrain from providing most horizontal services by itself but should integrate with what is initiated by the facade. Example: Transactions. The domain layer should not introduce new transactions but should instead integrate with the transactions that are initiated by the facade. The facade as a consequence, becomes quintessential to the implementation of a robust middleware services layer.  This introduces the following problems:

  • The consuming client (UI Layer in this case) is tightly coupled with the facade's interface which sometimes may not be desirable.
  • The consuming client has to be aware of the fact that a new facade was introduced and accordingly know how to call it. (with a proxy or by using a service locator)
  • Facades get bloated since they have to take care of all the horizontal services. This is usually solved by using AOP.
  • Facades can get fine grained by providing a lot of methods each one adding to the facade interface thereby bloating the interface also.
  • The facades cannot be configured or instrumented during runtime. This is because all the logic is in java code and it is cumbersome to change the way the code functions without further bloating the facade. (adding if statements and so on) 
  • In a typical enterprise, there can be a lot of services that would be needed to be exposed. These services may in turn need to be combined to form composite services. A facade calling other facades gets to become cumbersome to implement.Attempts have been made to correct this problem by writing utility classes that provide the horizontal services and calling these utility classes from the facade. This does not fully solve the problem since the facade still needs to be coded to call these utility classes.In short, using the facade to provide all the services tends to lead to unmaintainable code that cannot be instrumented. We use an old idea to solve this problem.

Command

The first step in solving the bloated facade problem is to design fine grained components that provide one service at a time. This is in line with the Single Responsibility Principle(SRP) (which states that a component should have one single responsibility so that it should be changed because of one reason and not multiple reasons). Our bloated facade would not follow SRP since the facade implements both the request specific functionality and also the horizontal services. Hence it has to change if the request specific functionality changes(which is acceptable) or if one of the horizontal services change(not acceptable). So we introduce the notion of a command. We define a command as a stateless component or class with a single responsibility that provides a reusable service. (This harks back to the question. Can commands be stateful? The short answer is YES but the functionally scalable answer is NO.  This is a whole different discussion that I would get into in another post)This can be a horizontal service or a request specific service. Hence by this definition the facade itself becomes a command that provides a request specific service(and nothing else). The commands should be stateless since they can then be easily deployed in multi-threaded environments. Now to service a request would involve a collaboration between these commands. The commands are chained and wired to the incoming request using the chain of responsibility pattern.

Chain of Responsibility

An architectural idiom that pervades through Data Flow Systems is that of "Pipes and Filters". The pattern itself forms the backbone of UNIX shell scripting. There is also an allied design pattern called the Chain of Responsibility pattern. The classic version of the Chain of responsibility pattern constructs a chain of commands. Every request is passed through this chain till one or more of the commands respond to the request. The commands have the chance to return a response and stop the further processing of the chain. We adopt the chain of responsibility pattern but retain more of a flavor of the pipes and filters approach i.e. we set up a chain of responsibility of commands, but allow multiple commands to mutate the input.

Context

In UNIX systems, the filters read standard input and write to standard output. Hence a byte stream is exchanged between communicating processes. The processes themselves are not aware of the exact flow that has been set up using pipes. But they communicate with each other by reading the input byte stream and generating an output byte stream. We need a java equivalent to facilitate communication between our commands. This is the Context object. It would contain certain attributes that facilitate the flow. It also should contain some typical request and response attributes. (for instance pagination context, sorting context etc) Specific request - response flows should inherit from this base Context. This context should also contain a map so that it can be used for storing request and response domain objects without the need for extension.

Chain, Router & Orchestrator

A chain of responsibility implementation requires various components that share the responsibility of setting up the chain and co-ordinating the way individual commands function within it. An orchestrator is a command that initiates the chain of responsibility. The simplest implementation of an orchestrator is as a simple command that stores the first command in the chain within itself. The orchestrator delegates all execute() requests to the first command in the chain. Hence in code:

public class Orchestrator implements Command {
// --
private Command firstCommand;
public Context execute(Context in) throws Exception{
return firstCommand.execute(in);
}
}

Orchestrators can also provide a "catalog"ing functionality. A catalog is a list of named commands that participate in the flow. Orchestrators also typically store a catalog of CommandDescriptors. A CommandDescriptor contains meta-data about the command. This catalog of descriptors allows for management of the flow from a user interface (such as an Instrumentation Panel or JMX). An OrchestratorConfig is a component that undertakes the task of assembling the various commands into the flow. An orchestration config may have the ability to parse a flow definition language(FDL). It can also rely on other IOC(Inversion of Control) frameworks such as Spring to set up the flow. A chain is a command that supports a list of linked commands. These commands are executed in sequence by the chain. The commands have the ability to stop further processing of the chain if desired. Alternately, the commands can choose to be unaware of the fact that they are participating in a chain. A router is a command that has several commands attached to it. The router passes control to one of the attached commands based on the context that is being passed.

Integration with DI

Dependency Injection(DI) frameworks act like giant command factories. They assemble individual commands and inject dependencies on them. They also surround command with decorators that allow the command logic to be tweaked with various horizontal concerns. For instance, there can be a LoggingDecorator that allows commands to be logged before and after invocation.

Aspect Oriented Programming techniques provide more fine grained and declarative ways to implement horizontal concerns. A sophisticated dependency injection framework can construct a command and wrap it using either decorators or aspects. Since these definitions are required during the time the command is created (as opposed to the time when the command is being used), it is a good feature if the OrchestratorConfig can integrate with a DI framework for the creation of the command. Otherwise, the OrchestratorConfig would end up either implementing most of the functionality of a DI framework or would become too simplistic for use.

The advantages of Dependency Injection in the implementation of Orchestration framework are:

  • Testable commands.
  • Clean separation of horizontal concerns.
  • Implementing custom behavior before and after command invocation.

Integration with Business Rules

Business Rules engines are increasingly becoming popular for externalizing changeable business logic from code. These rules can interact with the Orchestration framework by setting up alternate flows depending on certain conditions becoming applicable. For instance, a negative balance in a certain account can trigger a flow different from the normal flow. This rule would typically be set up as a router in the flow. The router would link to other commands that would be executed if the conditions are satisfied.

Typically, business rule commands act as condition nodes in the flow. The condition nodes get attached to alternate flows. The rules would govern which of the alternate flows to be executed during runtime depending on the context that is actually passed to the execute() method.

Framework Objectives

Micro-flow vs. Macro-flow – The framework provides a micro-flow which is a flow between processes typically but not restricted to the same Virtual Machine. It does not attempt to provide a solution for macro flows or interruptible flows. It also does not attempt to provide compensatory flows. We would need a work flow product for such things.

  • The framework provides an implementation of the Chain of responsibility pattern. It provides some default commands which can be extended.
  • It should provide for a flexible orchestrator whose configuration can be specified in different formats. (either in code or in xml or in any other convenient way)
  • The framework should provide for a standard implementation of a router and a chain.
  • (Optional) It should provide for a convenient way of implementing a chain that allows multi threading.

Benefits

The following benefits occur because of externalizing orchestration from code:

  • Individual commands adhere to the Single Responsibility Principle. Hence they can be coded and maintained easily. An entire knowledge of the system is not required to program individual commands.
  • Horizontal services are externalized from business services. This achieves separation between core and reusable horizontal services.
  • The orchestration can be instrumented relatively easily. This feature is important if configuration changes need to be performed on the fly.
  • Individual commands are testable since the dependencies are clearly separated. This integrates very well with dependency injection frameworks.
  • The commands are more stable due to the adherence to the SRP(Single Responsibility Principle). One concern should typically affect only one command (or a group of related commands). It should not affect multiple unrelated commands.
  • This architecture promotes a plug-in kind of behavior. The commands can be routed differently based on various criteria. * * This allows us to specify different routes and implementations. These routes can be registered from the modules to the core framework. Hence customized behavior can be added by merely adding a module with the correct registrations in the class path of the end artifact (such as a WAR file). Hence a modular behavior is promoted by this framework.
Blog Comments (3)

Gaurav Malhotra, 2009-03-07 08:39:43

"Having shared white board session on the above topic and using your brain child in my current project/other project(s)... It was pleasure reading this article...and reliving white board session with you.. fab article"

Gaurav Malhotra, 2009-03-07 08:42:22

"Also this framework ... is blessing if used with your Service Identity concept (subject/operation design pattern) Please write article on that also"

Raja Shankar Kolluru, 2009-03-10 03:57:50

"Thanks Gaurav. This article does require augmentation from white board discussions :-) I have been meaning to break it down into multiple articles but haven't had too much time to do that. I also agree that I should cover the subject/operation pattern that has been a core design tenet of mine in a lot of architectural overhauls. more work to do. But I am sure glad that it is benefitting somebody."