[Miwi-middleware] KIARA/DFKI Internal API Design

Dmitri Rubinstein rubinstein at cs.uni-saarland.de
Tue Oct 7 15:08:16 CEST 2014


Hi,

I just finished the first version of our internal API description that I 
developed. Please note that we don't want this to be an official public 
KIARA API. Possibly it can be used as a KIARA plugin API, but I expect 
more modifications in future. All of this is work in progress and change 
from day to day when new features are added.

It is possible that my description is not fully clear, so please comment 
and ask about details.

Best,

Dmitri
-------------- next part --------------
KIARA DFKI Internal API Design
------------------------------

In this document we explain internal design and implementation of the current KIARA/DFKI Java version
available on https://github.com/dmrub/kiara-java.

1. Overall design

The basic idea behind the internal API is that given a communication channel (e.g. TCP socket) both
endpoints send and receive messages. The message can be seen as an universal data unit that can represent 
an RPC request, response, notification, etc.

2. Transport

The transport layer works on top of the communication channel and takes care of splitting input data stream(s) 
into transport messages, and also to send transport messages via output data stream(s). 
Design was based on following decisions:

* Transport layer is independent of other layers, 
  we have no dependencies on serialization and dispatching layers of the framework.

* We keeping all information provided by the implementation of the transport layer, since it can be required by
  the serialization and dispatching layers.

* API is fully asynchronous

* Convenience for the end-user (developer) is not a goal, since API is internal

Transport layer API consists of following classes:

TransportMessage    - represents message and all the information that is provided by the transport layer.
                      (source: http://goo.gl/bk0ohV)

Transport           - factory that produces TransportConnection and TransportAddress instances.
                      (source: http://goo.gl/p7SF2m)

TransportConnection - represents single communication channel between client and server (e.g. opened TCP socket),
                      is used to send and receive transport messages.
                      (source: http://goo.gl/Y0NlWK)

TransportAddress    - represents abstract address identifying TransportConnection, Transport constructs TransportAddress instances from URI.
                      (source: http://goo.gl/jcRvTR)
                      
TransportRegistry   - allow to register Transport instances with the transport name. (e.g. HttpTransport with "http" and "https").
                      (source: http://goo.gl/jFIfL7)

Transport layer can also be seen as a higher level abstraction of a (not necessarily network-based) communication library,
that allow to exchange messages containing payload (ByteBuffer) and related metadata. The current design is actually
dictated by the features of the HTTP protocol, not TCP. In HTTP a single request message contains following information:

Request line, such as GET /logo.gif HTTP/1.1 or Status line, such as HTTP/1.1 200 OK
Headers
Optional HTTP message body data

For further processing and dispatch we need to keep and make available all this information.
We map HTTP body data to the payload of the TransportMessage and represent it by the ByteBuffer.
Everything else is stored as the metadata in a String to Object mapping. The mapping has a number
of predefined keys with the fixed interpretation:

session-id   - ID of the current communication session, required for security
content-type - Mime Type of the payload data encoding
request-uri  - URI used in HTTP request, or similar transport protocol
http-method  - HTTP method used in HTTP request, or similar transport protocol

More keys can be easily defined later without changing the API.

Additionally to the abstract representation of the transport connection and message we also provide abstract representation of the
transport address. We need this for the advanced message dispatch, because in protocols like HTTP dispatch of the request is not based
only on the combination of host and port. For example assume we provide KIARA services via HTTP server. 
The same host and port combination can be shared between multiple services (e.g. static HTML pages, files, etc.). 
In order to figure out if we should handle the request we need to check additionally the request path. 
Since transport layer is not taking care of the dispatch we need to provide an abstract API that allow to match requests.
The idea is that URI's can have patterns (e.g. glob patterns with '*' and '?' letters) so we can register services not just for
a specific path but also for a pattern. For example:

        String patternUri = "http://localhost:8080/*/bar";
        String addrUri1 = "http://localhost:8080/foo/bar";
        String addrUri2 = "http://localhost:8080/moo/tar";
        Kiara.init();

        TransportAddress patternAddr = TransportRegistry.createTransportAddressFromURI(patternUri);
        TransportAddress addr = TransportRegistry.createTransportAddressFromURI(addrUri1);

        boolean result = patternAddr.acceptConnection(addr);

        System.out.println(result); // Will output true
        
        addr = TransportRegistry.createTransportAddressFromURI(addrUri2);
        result = patternAddr.acceptConnection(addr);
        
        System.out.println(result); // Will output false

The transport API is fully self-contained, so it can be used directly without the rest of the KIARA API. 
Here are examples of the client and server: http://goo.gl/1cNbo1, http://goo.gl/Z7sRcC.

It is built on top of the Netty library (http://netty.io/), but it is 100% wrapper in order to be able to change internal implementation
easily. Currently TCP and HTTP transport protocols are supported.

The only limitation of this API is that without modification it can only support a transport protocol where data stream can be
split into single messages.
For example our TCP protocol sends a 32-bit unsigned integer specifying payload length at the start of every message.

3. Protocol

Protocol layer is responsible for deserializing TransportMessage-s into request and response messages and vice versa.

Protocol layer API consists of following classes:

Message          - represents a deserialized message that can be either serialized into TransportMessage or dispatched.
                   Message contains its kind (REQUEST, RESPONSE or EXCEPTION), RPC method name, RPC call arguments or RPC call result.
                   (source: http://goo.gl/1WUeQW)

Protocol         - provides construction of Message instances for requests and responses from TransportMessage payload (ByteBuffer).
                   (source: http://goo.gl/ViC5ek)

ProtocolRegistry - factory that provides registration of Protocol classes, and construction of Protocol instances.
                   (source: http://goo.gl/YMx2rv)

Important here is a difference between TransportMessage and Message. They represent usually the same message but on different
abstraction levels. TransportMessage instance contains just data, but no interpretation (request, exception or response).
Message instance represents an RPC message and allows to get or set arguments and a result.

Additionally Message implementations (e.g. JsonRpcMessage, source: http://goo.gl/mh2fQP) store data that are specific to the protocol.
In the case of JSON-RPC we need to store message ID required for the dispatch.

Note that in our current implementation there is no serialization API, because we use external libraries 
(jackson for JSON-RPC: https://github.com/FasterXML/jackson, Java Serialization for JavaObjectSerialization format).
Also depending on the protocol serialization encoding can be too different, so even multiple APIs are possible.

Instead Message class currently require that user pass type information about Java class that should be deserialized,
and serializer implementation takes care of correct conversion.

4. Dispatching

Most of the server-side dispatch mechanism is implemented in ServerConnectionHandler.java (source: http://goo.gl/vMwqCr) and
ServiceHandler.java (source: http://goo.gl/HTOIXp). 

1) ServerConnectionHandler.onRequest is called each time when new incoming TransportMessage arrives.
   We prepare TransportMessage instance for a response.

2) First we check if the request is a part of negotiation protocol, and then we return JSON-encoded configuration document.

3) When request is not processed yet we search for the service (ServiceHandler instance) accepting requests on the transport address
   of the transport message.
   (Note: this part of the code is work in progress, ServerConnectionHandler will store later all ServiceHandlers that accept
          requests on the opened connection).

4) When ServiceHandler.performCall() is executed it converts TransportMessage to a Message by using internal Protocol instance.
   Then bound Java method is invoked, result or exception of its execution is serialized into a response Message and then into
   a TransportMessage.

The client-side dispatch mechanism is implemented in the DefaultInvocationHandler class (source: http://goo.gl/bOzLzB).

Client side dispatch is required because we can send and receive messages asynchronously. 
Because of this the order in which responses arrive is not specified. We solve this problem by having a list of handlers
(that we call Pipeline) for pending responses. When we perform a call we add a handler to the pipeline and invoke asynchronous 
function that waits for the handler and then deserializes the result. 
When response arrives we search for a corresponding handler and pass a response (TransportMessage) to it.
Our asynchronous function deserializes the result and returns it to the caller.



More information about the Miwi-middleware mailing list

You can get more information about our cookies and privacy policies clicking on the following links: Privacy policy   Cookies policy