Skip to main content

Canonical Protocol

Introduction

I will like to show some ideas about the canonical protocol I have defined. In the final example it is shown that such protocol provide features given by OAuth at the protocol level, it means that You can pass external resources without any change on services implementations or additional inter-service integration.

RPC integration paradigm

When using webservices to integrate remote systems, the development process can be summarized to:

Client side
-- Prepare request data from context
-- make call
-- Parse response and interpret result
Service provider side
-- Parse request
-- interpret data to make internal API calls
-- Create response

Well, not so difficult. But the problem is that all the code used to serialize/parse context information and interpret request/response does not add any value to the system.
Note that change from technologies like CORBA to web-services is just a standardization of the binary format used to create/parse the messages. The RPC "Remote Procedure Call" paradigm is still the same.

Proposition: Canonical Protocol

Working on systems integration (to many systems, to many webservices) I had an idea to make a new kind of integration. Before going to the details, lets see how the previous integration schema may change:

Client side
-- Inject a service API interface
-- make calls to service API
Service provider side
-- Provide a service API implementation

But where is the integration??. The integration information can be extracted from "business code" to a external layer. From the code point of view, You get a implementation of an interface and don't care about where is the implementation. To get a real difference, the injected implementation of the interface must be realized with a different mechanism than RPC. This new mechanism is the Caonnical Protocol.

A simple example

Assume that a service provide a implementation of interface CoreServiceExample on some external machine
public interface CoreServiceExample {

 void resetProcess();
 
 void putData(int value);
 
 boolean isProcessStatusOk();

}
Then the client code may look like this:
public class ClientSimple {

 @Inject
 private CoreServiceExample coreService;

 public boolean simpleMethod(int internalCounter) {
  coreService.resetProcess();
  for (int i = 0; i < internalCounter; i++) {
   coreService.putData(i);
  }
  return coreService.isProcessStatusOk();
 }

}
In the case that interface CoreServiceExample would be available as a webservice, then every call to a method should be executed as a RPC. To change method simpleMethod to make only one external execution of a webservice it is necessary to create a new method on interface CoreServiceExample with some complex data model. This means change client and provider implementation.

Canonical Protocol

The canonical protocol provide a new solution to this integration problem. Note that only the last method isProcessStatusOk() returns a type different that void. Client execution flow do not depends on the intermediates calls to interface CoreServiceExample. This means that execution may be recorded locally during program execution and one external call to service provider may be done with a single message similar to:
CALLS
MethodCall[#0.resetProcess()#-1]
MethodCall[#0.putData(#1)#-1]
MethodCall[#0.putData(#2)#-1]
MethodCall[#0.putData(#3)#-1]
....
MethodCall[#0.putData(#n)#-1]
MethodCall[#0.isProcessStatusOk()#(n+1)]
BINDING
#0->BindingKeyInstanceReference [key=Key[type=CoreServiceExample, annotation=[none]], instanceNr=0]
#1->DataObjectInstanceReference [ instanceNr=1]objectReference="INTEGER_SERIALIZATION"
#2->DataObjectInstanceReference [ instanceNr=2]objectReference="INTEGER_SERIALIZATION"
....
#n->DataObjectInstanceReference [ instanceNr=n]objectReference="INTEGER_SERIALIZATION"
#(n+1)->PendingDataInstanceReference [dataType=boolean, instanceNr=5]]
The message information is enough to recreate executions to interface CoreServiceExample on the provider side. Logical execution with Guice injection API may look as:
  Injector providerInjector = getProviderInjector();
  Key serviceApiKey = context.getBindingKey(0);
  // previous call return the key: Key.get(CoreServiceExample.class);
  CoreServiceExample realService = providerInjector.getInstance(serviceApiKey);
  realService.resetProcess();
  realService.putData(request.getData(1));
...
  realService.putData(request.getData(n));
  boolean realResult = realService.isProcessStatusOk();
  context.savePendingResult(n+1,realResult);
The real service provider code is untouched!!!. It just have to implement interface CoreServiceExample.class, regardless of how the client uses the API!. The response message is then:
#(n+1)->FilledDataInstanceReference [instanceNr=(n+1), getObjectReference()="values of realResult"]]
Not only method calls returning void can be delayed. If a method returns a interface, then further nested execution of method can be recorded.

A new quality on integration

The service interaction flow presented below is taken from OAuth documentation http://hueniverse.com/oauth/guide/workflow/. The big difference is that interaction between PhotoSharingAPI(faji service) and PrintingPhotoAPI(beppa service) is realized at the canonical protocol level, and that services don't have any information about each other: No direct integration between PhotoSharingAPI-PrintingPhotoAPI is necessary.
The real power of the canonical protocol is the possibility to pass external references between services. To show this, assume the following client code realizing Jane interaction from OAuth workflow example:
 public class JaneGuiInteraction implements JaneGuiService {

  @Inject
  private PhotoSharingAPI sharingApi;

  @Inject
  private PrintingPhotoAPI printingApi;

  public boolean runPrintingPhotoTest() {
   String credentials = sharingApi.register("JANE");
   PhotoSharingAlbum myVacationAlbum = sharingApi.getAccessToAlbum(credentials);

   PrintingPhotoOrder order = printingApi.createPrintingOrder();
   order.addAdressInformation("grandmother direction");

   PhotoInfo[] vacationPhotos = myVacationAlbum.getPhotoList();
   PhotoResource[] photos = new PhotoResource[vacationPhotos.length];
   for (int i = 0; i < photos.length; i++) {
    order.addPhotoResourceReference(myVacationAlbum.getPhotoSharingResource(vacationPhotos[i].photoId));
   }
   return order.submitOrder();
  }
 }
Note the call
order.addPhotoResourceReference(myVacationAlbum.getPhotoSharingResource(vacationPhotos[i].photoId));
The method getPhotoSharingResource returns a interface PhotoResource from service bind to PhotoSharingAPI. This reference is passed to a order created on service printingApi. In such interaction, the canonical protocol records the references to PhotoResource in the context of each service slightly different:

For PhotoSharingAPI service
#11->BindingKeyInstanceReference [key=Key[type=pmsoft.sam.module.definition.test.oauth.service.PhotoResource, annotation=[none]], instanceNr=11]
MethodCall[#4.getPhotoSharingResource(#10)#11]
The canonical protocol now know that a instance of PhotoResource is created from service PhotoSharingAPI by a call to method getPhotoSharingResource.

For PrintingPhotoAPI service
#3->ExternalSlotInstanceReference [key=Key[type=pmsoft.sam.module.definition.test.oauth.service.PhotoResource, annotation=[none]], instanceNr=3]
MethodCall[#1.addPhotoResourceReference(#3)#-1]
When client code pass this reference to service PrintingPhotoAPI, the canonical protocol implementation creates a key with type ExternalSlotInstanceReferece and remembers internally the mapping (#3,PrintingPhotoAPI)->(#11,PhotoSharingAPI).
When recorded methods execution is realized in PrintingPhotoAPI real implementation, the execution of method addPhotoResourceReference is realized with a Proxy instance that records methods calls. As a return information, additional calls to service PhotoSharingAPI may be created. In this case it is:
#8->ServerPendingDataInstanceReference [instanceNr=8]]
MethodCall[#3.getPhotoData()#8]]
On base of mapping (#3,PrintingPhotoAPI)->(#11,PhotoSharingAPI), this call is translated to a request to PhotoSharingAPI service:
#16->PendingDataInstanceReference [dataType=class [B, instanceNr=16]]
MethodCall[#11.getPhotoData()#16]]
This call returns a final data object without any further method call:
#16->FilledDataInstanceReference [instanceNr=16, getObjectReference()=[B@7f4d1d41]]
and then result can be retrieved to PrintingPhotoAPI service. Note that the key #8 is the original reference number of a ServerPendingDataInstanceReference above.
#8->FilledDataInstanceReference [instanceNr=8, getObjectReference()=[B@7f4d1d41]]

Prototype implementation

This post don't define strictly the canonical protocol, but just shows some ideas. To see the prototype implementation go to https://github.com/paweld2/Service-Architecture-Model

Comments

Popular posts from this blog

Inverse of Control is not Dependency Injection

How to understand the difference between "Dependency Injection" and "Inverse of Control"? Problem lies in lack of examples to "Inverse of Control" different than "Dependency Injection". So, below You can find a simple example to see the difference. On car traffic, each car is "controlled" by a driver. On front of stoplights, cars line up waiting for green light. Drivers accelerate at the sight of green lights, but they do it with delay. This leads to a situation as shown: Applying the Inverse of Control principle, stoplights could take "control" of car's acceleration and execute a synchronized and fast start on green light. So inverse of control for car-semaphore scenario looks like this: How is Dependency Injection related to Inverse of Control?? Well, on Dependency Injection you take the "control" over the new statement from developers to architect. Control on developer looks like this: public clas

Id generation from prime numbers factorization

How to generate unique ID numbers on a multithread application?? Assumptions: Generated ID are unique Minimal synchronization between thread No synchronization between thread during generation No order assumed, just different values Ids generated from primes The idea is to generate the ids as product of powers of prime numbers id = p_1^f1 * p_2^f2 * p_2^f3 * ... * p_n^fn We use different prime numbers in each thread to generate different sets of ids in each thread. Assuming that we use primes (2,3,5), the sequence will be: 2, 2^2, 2^3, 2^4, 2^5,..., 2^64 Then, when we see that a overflow will be generated, we roll the factor to the next prime: 3, 2*3 , 2^2*3, 2^3*3, 2^4*3, 2^5*3,..., 2^62*3 Generation class Each instance of class IdFactorialGenerator will generate different sets of ids. To have a thread save generation of Ids, just use ThreadLocal to have a per-thread instance setup. package eu.pmsoft.sam.idgenerator; public class IdFactorialGenerator {