[Miwi-middleware] KIARA/DFKI Java Design Document

Dmitri Rubinstein rubinstein at cs.uni-saarland.de
Fri Sep 26 14:35:47 CEST 2014


Jaime requested me to send this separately. Please note this is a first 
draft, and I would like to see comments, issues, etc.

FYI, in the directory Benchmarks of the kiara-java repository:

https://github.com/dmrub/kiara-java/tree/master/Benchmarks

you will find user scenario based on 
http://mnb.ociweb.com/mnb/MiddlewareNewsBrief-201004.html implemented 
with different middlewares:

ApacheAxis2
ApacheThrift
RMI
RabbitMQ
Zero ICE
Plain Java TCP sockets

They are also used for benchmarking.

Best,

Dmitri
-------------- next part --------------
This documents describes design ideas behind the KIARA Java API
that was developed by DFKI: https://github.com/dmrub/kiara-java

Our starting point was the design of the KIARA/DFKI C/C++ API,
but we also took into account features specific to Java.

API examples are in KIARA/src/main/java/de/dfki/kiara/example

Notes: 

This API is the result of our internal discussions. We are always open
to other ideas and would be happy to consider alternative designs
(given suitable suggestions).

This API design is driven by the original KIARA ideas about a unified
middleware API that 
-- operates using the native data types of the application,
-- supports several different transport mechanisms and protocols,
-- supports to negotiate and select a suitable protocol/mechanims at run-time,
-- can transfers data directly between the memories of the two application (e.g. by RDMA),
-- allows for optimized transformation between the native data and the used protocol formats.

This current Java API is designed to support Remote Procedure Call
(RPC) pattern, so far. Changes to support PubSub should be minimal and
are under consideration now. RDMA-like support might require a small
additional API to register instances of data types with the middleware
as send/receive buffers.

This API is compatible with Java SE version 7 and higher.

-- Client --

1. Opening connection

   Like in the C++ version we start first with creating context and using it
   for opening the connection. A URI is used as the universal server address:

     Context context = Kiara.createContext();
     Connection connection = context.openConnection("http://localhost:8080/service");

   We assume following IDL in our example:

    namespace * calc "
    service calc {
      i32 add(i32 a, i32 b)
      float addf(float a, float b) "
    }

   After the connection has been opened:
    1) An IDL provided by the server under the URL is loaded and parsed
    2) The best protocol and transport available by both client and server are
       selected.

2. Binding Java methods to the IDL

   As the interface to KIARA will be in terms of the native types of
   the application, the next step is to bind suitable Java methods
   (specified/defined through Java interfaces) to the remote service
   methods provided by the server (and described in the IDL). Later we
   will invoke the bound Java methods, which will trigger the
   communication and execute the remote service methods on the server.

   In the C/C++ API we bind the interface methods to individual
   functions. Because Java does not support single functions (like
   .NET delegates or C function pointers), here we bind to methods of
   a Java interface:

    1) User provides Java interface that corresponds to the service that
       he would like to call:

         public interface Calc {
             public int add(int a, int b);

             public float addFloat(float a, float b);
         }

    2) User uses the generic KIARA class MethodBinding<T> to describe
       which methods of the interface are bound to which service
       methods in the IDL provided by the server.

         MethodBinding<Calc> binder
           = new MethodBinding<>(Calc.class)
           .bind("calc.add", "add")
           .bind("calc.addf", "addFloat");

       The first argument to the bind method is the full name of the
       service, the second argument is the name of the Java interface
       method.  The third optional variable length argument allows to
       pass classes in order to identify overloaded interface methods.

       Parameterizing MethodBinding with the interface type enables
       KIARA to check whether the passed interface method names are
       correctly typed.

3. Now the user can generate instance of the interface Calc with
   bindings registered with MethodBinding class:

     Calc calc = connection.generateClientFunctions(binder);

   This operation might throw an exception when the service methods
   registered through binder are not available in the IDL or when
   argument or result types are not compatible.
     
   All of the above code can easily be hiden in a setup
   function. Internally our implementation uses
   java.lang.reflect.Proxy for implementing this.

4. Once the setup it has been performed, the user can now execute
   remote service methods by calling the generated interface methods:

     {
       int result = calc.add(21, 32);
       System.out.printf("calc.add: result = %d\n", result);
     }

5. Finally Context and Connection can be closed with the close method:

     connection.close();
     context.close();

A1. Note: Besides the Calc interface the object returned by
   generateClientFunctions() also implements KIARA's RemoteInterface
   interface. It can provide additional information about the remote
   service (e.g. the used connection):

     RemoteInterface ri = (RemoteInterface) calc;
     Connection c = ri.getConnection();

-- Server --

1. Creating the service
   
   As in the client we use the C/C++ API as the basis:

     Context context = Kiara.createContext();
     Service service = context.newService();

   The service class represents the remote service that is provided by
   the server.  A single server instance can provide multiple
   services.

2. Specifying the IDL

   At first user need to define what the service will provide by loading IDL:

     service.loadServiceIDLFromString("KIARA",
         "namespace * calc "
       + "service calc { "
       + "    i32 add(i32 a, i32 b) "
       + "    float addf(float a, float b) "
       + "} ");

3. Binding the Java class to the IDL

   Let's assume we are given an implementation of the service as follows:

     public class CalcImpl {
        public int add(int a, int b) {
            return a + b;
        }

        public float add(float a, float b) {
            return a + b;
        }
     }

  Given this implementation, the user can now create an instance of
  CalcImpl and bind it to the IDL:

     CalcImpl impl = new CalcImpl();

     service.registerServiceFunction("calc.add", impl, "add", Integer.TYPE, Integer.TYPE);
     service.registerServiceFunction("calc.addf", impl, "addf", Float.TYPE, Float.TYPE);

  The first argument is the service name, the second argument is the
  implementation instance, the third argument is the name of the
  implementing method. The finally arguments provide the types of the
  arguments.

4. Creating the server.

   Again this step is similar to the C/C++ API:

     // Create new server on specified port and host
     // negotiation document with path: /service
     Server server = context.newServer("0.0.0.0", port, "/service");

     // Register service under the relative URL "/rpc/calc"
     // and available via "jsonrpc" protocol.
     // Other options are possible as well.
     server.addService("/rpc/calc", "jsonrpc", service);

     // Run server.
     server.run();

5. Finally the Context and Server objects can be closed:

    server.close()
    context.close()


-- Asynchronous API --

What we described before allow us to perform synchronous API calls.
However, our API is designed in such a way that no extension is required in order to
call methods asynchronously. The user only needs to change signature of the interface
by using java.util.concurrent.Future<V> class. For example:

  public interface Calc {
    public Future<Integer> add(int a, int b);

    public Future<Float> addFloat(float a, float b);
  }

Now when we call Calc.add() or Calc.addFloat() methods we will get no immediate
results but a future handle. Program execution will continue since we are
not waiting for a result. A call to Future<V>.get() will enforce
immediate execution and block until result or an error arrives.

The only limitation of this approach is that Future<V> type does not support
notification of the computation end. We can either block and wait for the result
or check if the result is available. As an extension we also support  
ListenableFuture<T> which provides support for notification callback:

https://code.google.com/p/guava-libraries/wiki/ListenableFutureExplained

Future-s can be used as argument and result types in both client interfaces
and server implementation classes.


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