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 resultService 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 APIService 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
The OAuth example can be executed with the unit test https://github.com/paweld2/Service-Architecture-Model/blob/master/samproto/srcTest/pmsoft/sam/module/definition/test/OAuthPrintingPhotoTest.java
Comments
Post a Comment