At 12:45 04/12/2003 -0700, David Forslund wrote:
>I'm not sure what you mean by an object-specific interface/API.
Ok. Please bear with me, I'm going to try to write this one down so it will
be a long post.
Example. A "service" can count sheep, reset the sheep counter, and can tell
you how many it has
counted since the last reset.
In an object world, you do something like this:
class MyService:
def RegisterASheep(Sheep):
...
def GetTotalSheepSeen(Sheep):
...
def Reset():
...
The three method signatures above constitute an object-specific
API/interface. This interface is *exposed* to those who wish to use the
object. Either by means of a published API or some sort of run-time
inspection mechanism.
This works ok in a system where you have control over all endpoints. It
works especially well within a single memory space, in a single process.
The problems start when you step outside that, and start distributing the
application. Two main things happen:
1) You do have control over all endpoints. You do not control all the
interfaces - you have to work with those created by other people. Now it
becomes harder to evolve your own systems as you do not have a complete
sphere of influence over the object space.
2) The presence of an unreliable, high latency medium (the network) between
objects is the source of problems. When a router or something goes down,
the entire state of the system is contained in a distributed
intertwinglement of object references and is it is hard to recover without
loss. You can in theory make it robust, with for example two phase commit
but that has real problems over anything other than tighlty controlled
intranets.
In a services world (my bizarre, minority vision version), this API
disappears. The Service is now an opaque blob of code. We know nothing
about it except that it produces/consumes XML documents conforming to some
published schema or schemas.
(a)
In RESTian terms, the service is a resource that counts sheep. We interact
with it using standardised, non-object specific methods. In HTTP, these are
primarily, GET, POST and PUT. We "expose" a URI for the service - that puts
in on the Web.
We, use GET to find out how many sheep there are.
We use POST to announce the arrival of another sheep.
We use POST to reset the sheep counter.
In the POST cases, we have payload to send. Both of these are changing the
state of the resource. We use XML documents to describe the actions we require:
In RelaxNG Compact Syntax form, something like this:
MyService = element SheepService ( ResetSheepCounter | IncrementSheepCounter).
(b)
My version of an SOA builds on top of (a). Under the hood, the GET and POST
methods do not directly interact with the Sheep service. Both GET and POST
methods are transformed into XML messages that are enqueued to be handled
by the core service. The core service knows absolutely nothing about the
outside world except that it has a source of inward bound
messages/documents and a mechanism for sending documents to the outside
world on an outward bound message/document queue.
Furthermore, in my version of SOA, the loosecoupling of (b) is a design
time abstraction that is not necessarily present at run-time. By this I
mean that any half decent SOA architecture will be able to leverage
knowledge of the location of (a) and (b) so that at run-time, the loose
coupling layers are compiled away.
However, the fact that *the loose coupling is right there in the heart of
the design* means that the SOA can be deployed in a distributed fashion if
required. In that scenario, the asynchronous, message centric, reliable,
once and only once delivery nature of the data flow gives the system a real
edge in terms of reliability and ability to gracefully recover from failures.
Of equal if not greater importance is the fact that the loose coupling
between service logic and the outside world means that it is trivial to
*intermediate* between a service and the outside world. This ability to
splice in arbitrary cleverness on front of a service is known as proxying.
The ability to proxy saves your bacon in a distributed environment where
you do not control all the services.
- It allows you to modify your own services without breaking your contract
with other services
- It allows you to handle changes in third party services gracefully
without breaking your own code
This stuff really, really helps when it comes to the main part of the
lifecycle of an realworld IT system. The phase that features 80% of the
time and money. They phase laughably referred to as "maintenace".
> If
>you have a service and invoke a function
>on that interface, that is an API. You can look at it as an object, if
>you wish, but you don't have to. You have to
>locate the service before you can invoke a function on it. If you only
>have one method on any service, then you have
>done nothing to provide interoperability.
Au contraire, document centric, bits on the wire are a wonderful form
of interop because it is not predicated on consensus amongst communicating
parties! The real boon of loose coupling is that you can *intermediate*.
You can transform data at service boundaries to meet the needs of other
producers/consumers.
I've been designing systems for 20 years and this is where I've ended up.
On-the-wire transformation is the only way to go.
The great lie underlying objects is that it is a simple matter of getting
consensus on the interface. Fact is, consensus on the interface is a pipe
dream outside of a very limited domain of utility objects. How far away are
we from a standard API for a nominal ledger? That thing has not changed
since the days of Charles Dickens and yet we have as many interfaces to it
as we have stars in the galaxy.
Interesting, the same lie occured in the SGML world, which is now the XML
world. THe idea that it is a simple matter of time and effort before we
nail the perfect model/interface for an invoice, a blog post, whatever. Its
the same pipe dream.
*interoperability* results from self-describing bits on the wire that are
loosely coupled between producer and consumer. The loose coupling means you
can *intermediate* to perform bilateral transformations to match products
to expectations.
Interoperability does not come from publishing or discovering N customer
interfaces where N is the number of object types in the universe. Firstly,
you will never get consensus - outside of a dictatorship - on what the
interface should be. Secondly, the interfaces will keep changing and thus
breaking other interfaces (welcome to the real world of system evolution
known as "maintenenance"!). Thirdly, the sheer weight of bilateral object
plumbing creates the software equivalent of the N-Body problem in physics.
regards,
Sean