This is part of a series of blog post about Applied SOA. The past blog entries are:
- SOA Basics
- Service Discovery Process
- Service Taxonomy
- Service Composition
In this article, I’ll cover system consistency. In every distributed solution, a major concern should be consistency. How to you ensure that different moving parts in your solution create a consistent state?
Many patterns and solutions exist to address that problem. We’ll look at a few and see how it impacts other attributes of a SOA solution.
Distributed ACID Transaction
First solution that pops to mind is ACID transactions. ACID transaction is a very elegant and very mature technology allowing distributed parts of a system to remain consistent. I won’t go into details here and just assume you know the basics of ACID transactions.
ACID transactions can actually solve the consistency problem in a SOA solution as well. We would need to use distributed transactions (through WS-Atomic protocol for instance) where transaction would span through the calling tree of a service. This way, everything in the calling tree would be part of the same transaction and would either commit or rollback as a whole. It would conflict with solution attributes of SOA though.
The main attribute it would conflict with is service autonomy / isolation. A distributed transaction implies that a calling service holds lock in a composed service for a certain duration. This means the composed service can’t guarantee to other callers it can process their requests. That control has been partially externalized by the transaction. It breaks the trust boundaries between services: a service now needs to trust services invoking those services will hold locks on its resources. Locks also limit scalability of services.
ACID transactions are excellent at ensuring data consistency but they come at a high cost. This cost is deemed unacceptable in Cloud Computing because of scalability. The same goes for services invoked across trust boundaries (e.g. B2B services).
The typical fall-back pattern when transactions aren’t appropriate is Workflow + Compensation. With this pattern, each service expose a rollback, or compensation, logic so that a calling service can call an operation and later on call another operation in order to rollback the effect of the previous transaction. This typically assume that some sort of workflow engine is at the root of all those calls, orchestrating the service call chain.
This patterns also work and solve the consistency problem. It introduces different compromises. For instance, services must trust the workflow engine to take care of the consistency. Mostly, it is a more complicated solution. Instead of relying on a system (the transaction coordinator) to take care of compensation, you basically must implement it yourself.
Above the technical difficulty, it might be difficult to compensate after a certain lapse of time. This would be a business problem. A typical example is A puts $100 in B’s account, B pays a $90 bill, A wants to rollback its original transaction, but the money is already spent, what can we do? This is where the orchestrating workflow will get more and more complicated.
We’ve seen the two basic patterns for consistency. Typically we mix them together. For instance, a service implementation will use ACID transaction internally to ensure consistency internally. For read-only operations, composition without distributed transaction can be ok in many system where changes aren’t happening too quickly. This typically leaves you with only a small set of services requiring state-altering composition. Those often are business processes and are best handled in a Workflow engine.