Loose-coupling through the relaxation of endpoint assumptions

In a meeting yesterday here in Newcastle, few of us (Santosh Shrivastava, Paul Watson, Mark Little, Stuart Wheater, Simon Woodman, and I) got together to discuss/understand the differences (if any) and similarities between service-orientation and object-orientation. You may notice that some of the people involved are the same as the ones behind Arjuna and have a long history in the area of distributed computing. That was the reason I was so keen to discuss architectural matters with them even though their work has been mainly with distributed-object technologies 🙂 This is not a report on what was discussed since the discussions will continue and we hope to write about any outcomes soon. Instead, I want to post few thoughts as a result of this discussion. I am not going to suggest that what follows is something new. Just my current thoughts.

About assumptions

The number and nature of the assumptions we make when we build distributed systems contributes to the level of coupling between the components of that system. For example, in RPC-based systems, there is a coupling between the client and the server because of the assumption that the server supports the signature of the particular procedure which is called. Since the procedure-invocation is the communication abstraction, the semantics of calling a procedure are followed (i.e. even a 'void' procedure will block until its execution is finished). (Please note that I am talking about design here and not particular implementations). In traditional object-based systems, every entity is modelled as an object. Every object is associated with an endpoint for which we have references (or pointers). We make the assumption that every endpoint supports an interface and also that there is some associated state.

It is my thesis that when we are architecting distributed systems with loose-coupling in mind, we have to reduce the number of assumptions about what is supported by endpoints. This introduces additional complexity at the actors "listening" at those endpoints but that's something I am prepared to accept since my aim is loose-coupling and scalability and not implementation complexity.

Here are two categories of assumptions that we can remove/relax:
  • Actor-specific interfaces
  • Addressing/identity
  • State

Interfaces

In traditional object-oriented distributed programming, every object is associated with an interface. An endpoint (the point on the network on which an object is "attached") is supposed to support that interface. We communicate with the actor (the object) only through this interface and our communication abstraction is the method. The interface is closely coupled with the endpoint.

In service-orientation, and in particular in MEST, we remove interfaces from the endpoints of our actors. Our services don't support interfaces but, instead, they receive/send messages. Contracts are put in place to describe/govern the exchange of information between services. A particular endpoint may support different contracts.

A service only supports one logical operation which has uniform semantics across all services: "process the message received".

We note that REST has done something very similar. REST moves from the arbitrary interfaces of traditional object-oriented computing to a restricted small set of operations with uniform semantics.

Addressing/identity

The distributed-computing community has been discussing the issue of addressing/naming of objects for ever. In traditional object-orientation, the "address" of an object is usually coupled with the mechanisms for accessing it (e.g. a CORBA IOR). An object may have an identity which is not associated with the mechanisms of accessing it. However, it is still the case that each object gets its own unique address since objects are the logical recipients of any communication activity (i.e. method invocations). As a result we have to build additional infrastructure to cope with dead addresses (references).

In service-orientation, only services can send/receive messages. Hence, there is the issue of how to address/identify specific resources and how to communicate with them. Let me start from the latter. We are not allowed to explicitly communicate with resources/objects. Resources or objects don't exist in the architecture and, as a result, are not explicitly addressed. Instead, resources are named/identified using a transport/transfer-neutral mechanism (e.g. URNs). Please note that naming is different from identifying. Services talk to other services about resources and not to resources directly. Please note that I am using the definition of a "resource" as an entity with state (the WS-RF view) and not just an entity (stateful or stateless) that has a URI (the W3C view).

This issue relates to the recent discussion in the W3CWS-Addressing mailing list and to our arguments against WS-RF.

What does REST do in this? Well, REST promotes the addressing of individual resources through the use of transport- (or "application protocol-" to some 🙂 specific URIs. Although the last letter of "URI" stands for "Identifier", the Web hasn't really been built in this way. We don't know whether http://example.org/resource1 and ftp://example.org/resource1 really point to the same resource. In other words, URIs are used as addresses/names rather than as identifiers that uniquely and unambiguously identify a resource independently of any particular transport/transfer technology.

The Web and REST suffer from the same problem as traditional object-oriented systems; that of dead references (e.g. the page 404 problem). Since there is not global infrastructure in place to cope with reference/address counting or management, the Web scales and it just lets humans deal with the error pages. Some Web Server implementations, however, are doing their best to deal with such situations at the cost of additional complexity at the implementation.

We assume something similar in MEST; the application-specific protocols that are put in place between services are going to deal, in an application-specific manner, with those situations where a named/identified resource is unknown to the service. Management of addresses/names/identifiers is not part or a property of the architecture.

State

Here we go again!

In traditional object-oriented computing every object is assumed to have state. That's part of the definition of an "object". If one suggests that objects do not have state (at the architecture level), then I would say that they are not talking about objects but about something else. Anyway...

Objects are associated with some piece of state that can be referenced across the network. That state is associated with the endpoint from which the object is made accessible. Hence, we need to come up with additional mechanisms to guarantee properties that relate to state management (e.g. locking). More importantly, the exposure of state makes it easier for others to bind directly to it and make their part of their application depended on it. Of course, it all falls over when that state suddenly disappears.

In MEST we are advocating for stateless services. What do we mean by that? Of course we are not suggesting that services cannot maintain state for their own purposes. We are just saying that there is no directly exposed state to which other services can bind. Also, we allow for conversational state between services to exist but we require that all of it exists on the network and not at a particular endpoint. There is no association between an endpoint and state.

How does REST do here? Well, REST promotes the idea of stateless interactions and putting the state on the wire (I hope I am not wrong on this) but, as I discussed above, it doesn't preclude the association of state with a particular endpoint (a URI).

Example

Let me try to illustrate the above discussion using an example.

First, in pseudo-real-life (I know that this is not how it's actually done)...

I go to a bank. I want to pay my credit card bill. I give them my bank account number, my credit card number, and the amount I want to pay from my bank account. I am not giving them a file cabinet number that is specific to my bank account number or a pointer to a safe where my money is kept. I am giving them a name that they understand (e.g. bank:barclays:111-222:333-444-555-666). Then, I give them the credit card number. I am not telling them how to interact with my credit card company. The number I give them does not contain any communication-specific information (e.g. it's not a CORBA IOR). Instead, I am giving them a logical name with some semantics attached (e.g. creditcard:mastercard:777-888-999-000). They know how to reach my credit card company or they will find out. Also, I did not give them a reference to my actual credit card account. Just a name to it. Note that I talked to my banking service about my two resources (the bank account and the credit card account) and not to those resources.

In a traditional object-oriented manner, I would probably do that...

// Reference to Mastercard's object representation of my credit card (network wide)
CreditCard card = savas.CreditCard(); // Reference to Barclay's object representation of my bank account (network wide)
BankAccount account = savas.BankAccount(); // Reference to the object representing Barclay's bank
Barclays bank = Barclays.GetInstance(); bank.PayMoney(account, card, 1000);

What the above means is that I get network-wide references to objects and I pass them around. It's also very likely (but not always true) that I assume a particular underlying communication infrastructure on which my distributed object-based system is built.

Of course this is not the way distributed systems are built, even object-based ones. Instead, some indirections are introduced. A bank account may be represented by a pseudo-object which is passed by balue or the PayMoney() method may be designed to accept abstract names rather than references to objects. But as we start designing in this way, we are effectively starting to move away from the principles of pure object-orientation.

In REST, we'd probably do something like this...

POST http://barclays.com/account/111-222/333-444-555-666
CreditCard=http://mastercard.com/777-888-999-000
Ammount=1000

Here we assume that all communication is taking place over HTTP (in fact, we are telling our bank how to communicate with our credit card company). One could write the same like this...

POST http://barclays.com/account/111-222/333-444-555-666
CreditCard=creditcard:mastercard:777-888-999-000
Ammount=1000

but then I'd argue that we are not adhering to the principles of REST since we are naming a resource without using a URI.

Finally, here's how the same would look like using Web Services and the principles of MEST. A message like this

<soap:Envelope>
  <soap:Header />
  <soap:Body>
    <bank:MoneyTransfer>
      <bank:account>bank:barclays:111-222:333-444-555-666</bank:account>
      <bank:creditcard>creditcard:mastercard:777-888-999-000</bank:creditcard>
      <bank:ammount>1000</bank:account>
   </bank:MoneyTransfer>
</soap:Envelope>

is transferred over an arbitrary transport to Barclays'. The receipt of the message is the equivalent of a request to process the message. The bank determines how to deal with the resources named in the message. This is closer to how things work in real life.

Conclusion

What do you think? Is relaxing assumptions about endpoints a way to enable loose-coupling? Of course I am not suggesting that we get loose-coupling for free even if we relax the assumptions about the endpoints since we can mess up at a higher-level.

I think that REST is an example of how relaxing the assumptions can enable loose-coupling. Can we go further with MEST?

UPDATE: Formatting fixed.

7 responses to “Loose-coupling through the relaxation of endpoint assumptions”

  1. Mike
    Hi, I was wondering what is the difference between consumers binding to a specific method in an interface (e.g distributed objects) as opposed to binding or commiting themselves to the structure of a message that the service expects (MEST). If the method signature changes then all consumers who rely on the old signature break. What happens then if the structure of the message changes? Will all clients who send the old message structure also break because they are no longer sending valid messages?
  2. Hey Mike, this is a good point. However, please note that 1) a single endpoint may support different contracts/policies (unlike in traditional object-orientation) and 2) describing structure allows for more flexibility than describing types at particular endpoints. Also, in MEST behaviour is associated with protocols and message exchanges rather than methods.
  3. Mike
    Hi Savas, Thanks for the swift reply. I guess my main question then is where does the loose coupling actually occur? Doesn’t the coupling shift from method signatures to message structure?
  4. Loose coupling is possible due to the fewer assumptions we make about the endpoint. We still have to agree on a contract defining structure of the messages, whichcan be more flexible than a type-safe interface at a cost of having to do more at the implementation behind the service boundary. Also, an endpoint is not tightly-coupled with an interface because a single endpoint may support multiple message contracts (depending on policy for example).
  5. It could also be said that the loose coupling derives from the genericity of the implicit “process message” semantic. So, any client written to use that semantic (or an equally generic – aka uniform – one) can reuse the same one for all other services without any code changes at either end. With specific interfaces, and without code changes, a client bound to a specific interface can only *ever* interact with services which use that interface. There’s a quantitative difference here, not just qualitative.
  6. MikeD
    With the SOAP example, is the receiver supposed to cancel the money transfer that matches the supplied data?
  7. No, why “cancel”? When we fill in a “MoneyTransfer” form in a bank, can a bank take that to mean something else? Well, the bank could do what it wanted but then it wouldn’t follow the semantics of the protocol defined in the “bank” namespace.