It is often the case where Enterprise Application require one or more Vendor based products to be integrated into the home grown system.
While sometimes useful, there are many issues that arise from simply embedding a product into your code.
In my own past experience, I have integrated everything from Workflow Engines to Unstructured Data Search Indexes.
Some of the common issues that come up when integrating a product or service (it can be commercial or open source or even another home grown framework used within the organization) are:
- Deployment of new versions of the product.
- A high level architectural or firm wide product support or vendor change.
- Trying to integrate multiple products of the same type supplied by multiple vendors seamlessly in your system.
- Having to transition to a new product or version over an extended period of time or more than one release version of your application.
Many years ago when I was faced with firm wide IT political issues around Workflow Engine products; at the time I was using a home-grown patented engine, and the firm’s architecture group decided that all workflow based applications must use Tibco’s Staffware product, I came up with a strategy of being able to support both own in-house engine and Staffware simultaneously using two patterns from the Gang of Four (GoF) playbook.
Before we get into the details on how it actually works, I want to share with everyone the diagram…
High Level Design Diagram:
How does it works?
If we take my workflow engine example, I think it will be pretty simple to explain.
Note: Most modern workflow engine’s are large software products which usually include entire UI builders, and even their own application servers in some instances. In my experience I only leverage workflow engine packages for their workflow processing; so basically I use their APIs to interact programmatically with their engines to move requests around a workflow process.
Each Workflow Engine exposes it’s own set of APIs, in the case of Java it’s usually a set of JARs and the APIs can be rather complex including admin functions, and various other things that we might not be interested in.
The first step in the process of creating an Adapter-Factory is to declare a new Interface for which every Adapter will implement. This Interface declares certain methods, that are required by your application and are somewhat common across the multiple vendors or products you need to integrate with.
In the case of the workflow engine example, these methods are things like GetQueues(), TransitionWorkflow(), etc.
One of the fundamental ideas is that you do NOT want anyone outside of the Adapter layer to deal with the native objects used by the vendor or product specific APIs. So the second step is creating what I refer to as “Proxy” objects which may have a mirror image of fields as the Vendor specific objects, but they can NEVER reference any vendor data types.
Part of the job of the Adapter is to translate to and from these proxy objects and the native vendor objects.
The next step is to implement one Adapter per Vendor/Product, or even one Adapter per Vendor/Product-Version combination (in the case of where you need to support multiple versions of the same Product).
The ability to add a new Adapter at any time mitigates the risk that a Vendor may produce a new version of a product which you for one reason or another (such as support contracts) need to migrate to in the future. You simply add a new Adapter for the new version of the Product, and keep the old version active in your code base as a fall back strategy or during a staggered rollout.
Because we “proxy” or mirror every native vendor/product object and never expose those native objects above the Adapter level, this besides making it possible to support multiple vendors or versions at the same time, minimizes the changes to the rest of the system if a new vendor or version comes on board.
Once we have implemented one or more Adapters for the Vendors or Products we need to support in this Adapter-Factory implementation, the next step is to create the Factory itself.
Normally, I make it pretty simple, I have a “Default” Adapter type that will be returned if the caller of the factory does not pass the Name or Flag representing a specific Adapter Type to return. This default Adapter is usually configured via a property so I can change the default version without having to change and recompile the factory itself. I usually make Factory objects like these, Singletons. Other than these two specifications, the Factory just follows the normal Factory Design Pattern.
Finally, I always wrap the Factory and the calls to the Adapter implementations in a Facade. This simplifies the client code’s interaction with the Adapter-Factory itself and makes it very easy to use this design pattern without putting a burden of understanding the pattern itself on the side of the client code developers.
This pretty much sums my Adapter-Factory Design Pattern. I have used it in Production systems very heavily and my experience with having to work with multiple vendor products that provides the same type of service within a single application has become a lot easier to deal with because of this design. I hope this pattern becomes a useful tool in your toolbox when designing and developing your own systems.
Final Note: If you think about it JDBC itself is an Adapter-Factory!Just Another Stream of Random Bits… – Robert C. Ilardi