a service oriented design pattern finite state machine behaviour
TRANSCRIPT
A Service Oriented Design Pattern - Finite State Machine Behaviour Model
Some Context
Services tend to pass data around, as in Data is transmitted and Behaviour is
deployed. We are getting to a stage where the contract on a service can be
described well enough as a schema (say an XSD) in that the structure of the
elements is understood and that facets on those elements can tie it down enough for
a decent representation on 'both ends'. Putting to one side the service description
language bindings, the use of XML and a canonical schema has, all in all, greatly
aided integration between systems. If you make some concessions to the technology
in your Domain Model then this can actually work pretty well. Indigo is building on
this by concentrating on a 'Data Contract' part of the Service description, and with a
nod towards versioning and the like.
But Behaviour descriptions are a lot less flushed out, in that walking up to a Service
and figuring out what is does is a completely different level of problem than
describing a Data Contract using an open set of standards. A lot of this is to do with
how Data tends to evolve at a lot slower rate of change than the rules and processes
that make up the behaviour; data does tend to stick around a long time and
databases tend to like it that way. As Behaviour code can change much more often
and be 'unclosed' in it's effects, then it breaks often compatability and the like; some
people describe this as 'coupling' and it's been around in terms of how to design
class hierarchies long before SOD(*3) came along.
The reason something like document/literal is so sensible is that it steps back from
enforcing a set message transfer mindset of transporting an object instant from one
side of the world to the other and goes more into the client being able to reinterpret
or chicken-peck bits of what they receive and understand. This 'View' on the message
data and how it can be interpreted may keep the contract loose enough between the
parties to allow it to survive longer unbroken.
So what of Behaviour description? I tend to think of categorizing the ways we've
attempted to model behaviour in Services in a few ways:
(1) Bind It. Our Service bindings have placeholders for operations that translate to a
Behaviour 'Action', whether it's the explicit SOAP Action or a combination of a few of
the service description bits. It is then left up to the toolkit on the platform of choice to
hook back up to a method call or invocation. If you see a method name called
'DoPurchaseOrderChecks()' you get the top-level understand of what the semantic
intent of the service action is. If the single parameter is a PurchaseOrder XML
message then it doesn't take Sherlock Holmes's Smarter Brother to deduct what
should go in and out. It's a lot like using a library that you didn't write, in that
documentation, specificity and experience all make up the rest of the 'contract'.
(2) Resource It. So much has been written about REST I'm almost loathed to add
that much. I think it's suffice to say that if you take a 'Resource' view of the Purchase
Order then you can reuse much of the webs infrastructure of a few simple HTTP
verbs, i.e. GET/PUT. You add context to what you want to do and which resource you
want to do it to but moving the description up into the endpoint, i.e. a
http://localhost/Acme/PurchaseOrders/ProcessQueue PUT on an XML body
containing a Purchase Order doesn't take Dr Watson to figure out either.
(3) Complicate It. Managing the cost of data and behaviour change between
systems is an entire IT industry sector called EAI. It is natural that the tools and
product vendors in this space want to commoditize this work and bring down the cost
of change. WebMethods, Biztalk etc all work around this concept. WS-BPEL is a Janus
like draft spec in that it covers a pretty wide remit in its 2 sections. You can see it as
either a 'coordination and sequencer' or 'process execution' depending on your
choice of lifestyle. It does represent the best attempt at modeling interactions
between services without 'just code', and the most charitable thing I can think of to
say is that it's not baked yet and might yet get somewhere good.
(4) Deploy It. The term Service Agent burnt magnesium bright for a while, similar to
MC Hammer's music career, although I see it used a lot less today. The idea of a
small chunk of Mediator pattern that is 'away from the Service' seems sensible and
can be generated to be a little more that '(1) Bind It' in terms that some limited
behaviour can live in the client behind the agent. Of course, the Achilles heel is that if
you want Services to be about interoperability and decentralized change
management then you've just shot yourself in the foot. A bit of a generalization, but
designs that tend to love deployed Service Agents tend to view SOD like 'The Son of
CORBA/DCOM' and have a fairly monoculture view of IT.
(5) Split It. I have now seen a couple of SOA descriptions that include 'Orchestration'
like Services, i.e. Services whose responsibility it is to make the Entity/Data Services
dance. You split your Domain Model by having Data-centric services and Behaviour-
centric services. Office Information Bridge loosely takes this approach of a
sequencing 'Actions' and I do feel this direction has some merit. It is a little similar to
'(3) Complicate It' in that you need someone else's run-time, but I can picture a lot of
the design concepts finding their way into PAG and the Enterprise Templates too. I
haven't seen much public feedback from Razorbill's as yet, and it might be the case
of 'So much to argue about, So little time' that stops us getting to 3rd Base of a
taxonomy together of different types of Service even?
Pattern Description
The following simple pattern is intended to offer another way to model limited
behaviour using Services.
The behaviour of a Service can be described by providing a 'finite state machine'
driven sequence of actions as part of the contract. The Data Contract of the Service
provides a number of 'anchor point' known states that can be considered part of the
structure of the document message. The client passes the XML message to the
Service and includes the element of the desired next state, say as part of the body.
The Service then processes this message and runs behaviour as part of the asked for
'state transition'. The Service can then accept the transition, hold it for further
processing or reject it.
Key concepts of the pattern:
- The Data Contract and a limited intent of the Behaviour are 'kept together' in a
single Service description. What a Purchase Order looks like and what it can do are
exposed together.
- The client not only knows the preferred structure of the data but also understands
the current available paths to get the 'domain object' to the next state in the FSM.
- The behaviour that can be described is limited to what you can model in a FSM for
that domain object, but it is (a) better than nothing, i.e. it is machine understandable
in terms of contract and (b) not exclusive, i.e. I am not proposing that this is the only
way you model your behaviour.
- It's a design pattern, in that it presumes you are designing and writing code rather
than relying on the middleware of some other existing infrastructure. The pattern
should be simple enough to code yourself as part of the existing Services
infrastructure you get from the technology platform vendors.
An analogy to this pattern is a 'Model View Controller' concept in presentation
architectures. Most user interfaces are driven by a finite state machine, in that the
sequence of steps can be codified into a Controller to (a) offer some flexibility for
change and (b) define enough separation as to make this change less intrusive: The
problem of coupling behaviour between Services is a similar one.
Worked Example
We have a Purchase Order (of course). A schema describes it as simply as possible.
The domain object behaviour can be modeled in the following way in terms of the
'lifecycle' of the data:
The Purchase Order schema codifies this state machine as a series of enumerations
of allowable states within the schema.
The 'Purchase Order' Service has the following set of operations:
msgPO Create()
msgPO Read( id )
msgPO Update( msgPO )
bool Delete( id )
msgPO Import( msgPO )
The CRUD operation are self explanatory, but we now have an extra 'I' for the Import,
i.e. CRUDI. The Import method takes a single document of message type Purchase
Order. The 'Import' is less 'definite' than a 'Save' as it indicates the clients desire to
'do something' rather than an atomic operation guaranteed to go through.
The client indicates in the 'State' element of the Purchase Order that they want to
'Approve' this Purchase Order instance.
Presuming for this example that a 'Reviewed' Purchase Order already exists and can
be uniquely keyed upon, then the Service will run a series of 'pipeline' checks on the
requested state transition. Business rules will run based on what data is currently in
the PO, what PO has been passed and who has requested it.
What Works Well
The pattern works well for:
- Where you need to describe allowable behaviour to a 3rd party. As the behaviour is
often intrinsically linked with data structure, you get a versioning story that keeps
them consistent.
- You can Gateway a 'facade' very easily with this pattern, i.e. provide all your
business services through a common 'top level' Service interface that has a small
surface area, meaning small as in to protect, document and manage.
- Extra metadata included with the Purchase Order can help prepare the client for
what it needs to do, i.e. what fields are required and the like. This helps provide a
'coarse grained' interface very important for anything to do with XML Services.
- The pattern lends itself to Request/Response as well as Async Processing, as in the
limiting nature of the Import is what makes it self-contained.
What Doesn't Work As Well
- Some things don't lend themselves to being modeled with a FSM. That's ok, as the
pattern tends to get used like this:
PurchaseOrderService
CRUDI
PurchaseOrderInteralService
DoThis() // Used by Import for A->B transition
DoThat() // Used by Import for B->C transition
DoSomethingWeird() // Exposed
PurchaseOrderDataService
CRUD // Implements the top Service
...as in sometimes it is necessary to expose certain operations on the InternalService
too. The pattern lends itself to passing data messages around, rather than RPC
context-like instructions to go reformat harddisks, fiddle with configuration and the
like.
- You have to model upfront what the FSM will look like and sometimes that changes.
This is a fact of life and I tend to think of just like any other Data-centric schema
change. It is an important point that the FSM sequences are seen as 'anchor points'
from the Services perspective, i.e. if a 3rd party system receives a PO in 'Reviewed'
state and wants to do run all sorts of extra logic and states against it then that is fine
- as long as I receive it back in a state I understand. This is similar to the concept of
preserving data you don't understand, but from a behaviour point of view.
Anyway, this is probably far too long for a blog post so I should finish up. I just
wanted to throw the description out there sooner rather than later and I hope it is of
interest to some people.
It's also worth pointing out that this isn't a 'pattern first' exercise, in that I've been
working with a team of people doing this for about 3 years now. That isn't to say this
is a panacea or 'The Answer You Are Looking For' but I did want get across this isn't
all theory. Lot's of Services base software we have built is based around this design
pattern.
I hope this encourages more people to share their patterns and I welcome any
feedback: Unless it's negative, of course.