[Miwi-middleware] KIARA/DFKI Java Design Document Revised

Dmitri Rubinstein Dmitri.Rubinstein at dfki.de
Sun Sep 28 12:43:55 CEST 2014


In the attachment is a revised KIARA/DFKI Java Design Document. Comments 
are as usually welcome.

Best,

Dmitri
-------------- next part --------------
KIARA Java-API Proposal (Draft, Version 0.3, September 29th 2014)
by Dmitri Rubinstein und Philipp Slusallek

The following document described a scalable API approach for the Java
binding of our KIARA middleware. It is a proposal and subject to
change. Specifically this is not a formal definition yet but instead
describes our approach for the purpose of early internal discussions
about other suggestions.

Upfront let us define some terminology that we are using. Instead of
"static" and "dynamic" we are using the term "IDL-derived" and
"application-derived" to distinguis between the API used in the
current version of KIARA and the traditional ones used in other
middleware. Note that both cases are static in the sense that they
define a fixed mapping of some data static type to a message at
compile time. The mapping cannot be changed during run time. This is
in contrast to a truely dynamic API where a request can be
programmatically constructed. We might want to add this eventually (as
discussed already before) and should therefore reserve the name
dynamic for that case.

These terms apply to the API used to send data via the middleware as
well as to the management interface for setting up the
communication. It can also be used to describe interface functions and
the data types used in the process.

Our goal was to design an API that is *scalable*, where simple thing
can be done with a simple API but where advanced functionality is also
available with the same identical base API but they might require the
use of some additional API calls.

Due to the original KIARA API design being flexible and generic, the
addition of the IDL-derived API has actually been pretty straight
forward and integrates well with the existing KIARA API as shown
below.

Since we want to move fast and are now focusing on the design of a
simple (traditional) API for RPC-style communication, we compare in
this document three API versions: 
i)   The current Thrift API (Java) as an expample for a traditional middleware API (IDL-derived),
ii)  Our proposed simple IDL-derived KIARA API (called static previously)
iii) And our current KIARA-API using the application-derived approach as described before.

We hope that it shows nicely how the case (iii), which we have
advocated since the beginnng, is a natural extension of the
traditional API approach and actually integrates nicely with it.

We also show that there is hardly any difference between a traditional
API (Thrift in this case) and the new IDL-derived variant of the KIARA
API. So there should be no reason to perfer the one over the other. We
argue for the now proposed API as it is part of athe scalable API
design that enables us to also expose in a straight forward way the
much more powerful API that we have used in KIARA so far.

We also shows that the traditional API can simply be seen as an API
where the programmer does not have native types (yet) to be used in
the middleware comunication or simply prefers to let these types be
defined by the middleware (for whatever reason). There are no
*conceptual differences* in the API between the IDL and application
derived approaches.

This is actually an important point as it makes it almost trivial to
switch between the two approaches. This might happen for example as
the internal data types of an application change (e.g. during
refactoring) while the external API should stay the same (or vice
versa). In that case, the developer can simply declare the new
internal data types to the middleware, define the mapping for the new
types (simple), and go on as if nothing has ever happened.



1. IDL definition

We proposed to use the Thrift IDL as also suggested by Jaime
before. The current KIARA IDL is almost identical to the Thrift
version with a few additions (mainly for annotations).

For the following comparison we use the example from the middleware
comparison at
http://mnb.ociweb.com/mnb/MiddlewareNewsBrief-201004.html.

We use this example as we have used the exact same example also for
our benchmarks, testing, and perfromance comparisons of our KIARA
implementation with a significant number of other middleware
implementations (see Dmitri's email from Friday).

Here are data types used (appreviated):

     struct MarketDataEntry {
     ...
     }

     struct MarketData {
     ...
     }

     struct RelatedSym {
     ...
     }

     struct QuoteRequest {
     ...
     }

     service Benchmark {
       MarketData sendMarketData(1:MarketData marketData);
       QuoteRequest sendQuoteRequest(1:QuoteRequest quoteRequest);
     }

2. Preparation of Data Types

a) Thrift

   Thrift requires an IDL to source code generation pass at compile time,
   which will produce following files:

     Benchmark.java       - synchronous and asynchronous interfaces
     MarketData.java      - class representing struct MarketData
     MarketDataEntry.java - class representing struct MarketDataEntry
     QuoteRequest.java    - class representing struct QuoteRequest
     RelatedSym.java      - class representing struct RelatedSym

b) KIARA (IDL-derived API) 

   The KIARA API can use the same IDL-derived API approach. This
   requires the same generation of stubs and skeletons. We can
   probably completely reuse the Thrift code generation here. However,
   as shown below, the user will not instantiate these types and API
   functions directly but requests them via the KIARA API, which will
   automatically take care of instantiation and initialization of that
   code. We argue that this is actually a cleaner approach from a
   SW-Engineering point of view than the traditional way. The
   differences to Thrift are minimal.

c) KIARA (application-derived API)

   This KIARA API does not require any code generation at compile
   time, however users need to define an interface they want to use
   for the communication. This step is usually quite simple, since the
   user can use data types that are already used in the application.

   In the example below we are using data types that have a 1:1
   relationship with the IDL that will be used. More complex mappings
   are possible as shown in our C/C++ version of the API but are not
   yet implemented for Java.

   So the developer simply defines an interface in terms of native
   data structures as follows:

     public static interface Benchmark {
       public MarketData sendMarketData(MarketData marketData);

       public QuoteRequest sendQuoteRequest(QuoteRequest quoteRequest);
     }


3. Client: Opening connection and making a call

   The following code described the basic API calls that a client has
   to do to establish a connection to a service and use it.

a) Thrift

   Thrift requires an explicit instantiation of transport and protocol
   layers. This enforces that the application has to explicitly manage
   these aspects in all cases.

     TTransport transport = new TSocket(host, 9090);
     TProtocol protocol = new TBinaryProtocol(transport);

     // Benchmark.Client is a stub generated by the Thrift IDL compiler
     Benchmark.Client client = new Benchmark.Client(protocol);
     transport.open();

     // Make a call
     client.sendQuoteRequest(Util.createQuoteRequestData());

   Note, that there is no security here. Any security has to be
   managed explicitly by the application and would make setting up the
   connection *significantly* more complex (managing certificates,
   agreeing on common security mechanisms to be used on both sides,
   setting up the security mechanism, etc.)

b) KIARA (IDL-derived API)

   KIARA requires no explicit instantiation, but only the URI of the
   server. Since the data at this URL describes the service to be
   used, KIARA is able to derive the best connection details
   (transport mechanism and transport protocol) automatically
   (negotiation). 

   Note that a similar step is required anyway to properly handle
   security between the two peers. However, KIARA can handle this
   internally based on security policies defined by the application
   through either the IDL or via the API (not shown here yet, but
   already in the C/C++ version).

   This negotiation is only done once during setup of the connection
   and can be influenced by the applications (also not shown here).

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

     // Note that Connection.getServiceInterface() is a generic
     // method, it uses Class.forName() to dynamically load the
     // IDL-derived and precompiled class Benchmark.Stub Benchmark
     Benchmark client = connection.getServiceInterface(Benchmark.class);

     // A call to the remote service then looks exactly as in Thrift
     client.sendQuoteRequest(Util.createQuoteRequestData());

c) KIARA Client (application-derived API)

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

     // In the case of 1:1 mapping the API actually looks identical to
     // the case with the IDL-derived API. 
     // Note: The type Benchmark is the one defined by the application
     // (not the middleware) in step 2c above.
     Benchmark client = connection.getServiceInterface(Benchmark.class);

     // A call is done in the same way as in Thrift
     client.sendQuoteRequest(Util.createQuoteRequestData());

   Note that making a call (which is really the critical aspect of the
   API as this is what is used throughout the application code) is
   absolutely identical across both the KIARA and Thrift APIs. The
   only differences are in the opening of a connection and requesting
   the stub to be used.

   Extension: In case that a mapping is needed between the
   application-defined types and the IDL types we can additionally
   provide such a mapping before requesting the interface. Below we
   show a simple case of an 1:1 mapping explicitly given through the
   API (more complex cases can be defined and are implemented already
   in the C/C++ version of KIARA).

     // Because Benchmark interface is defined by the user,
     // we need to tell KIARA how to bind its methods to IDL methods
     MethodBinding<Benchmark> binder = new MethodBinding<>(Benchmark.class)
       .bind("benchmark.sendMarketData", "sendMarketData")
       .bind("benchmark.sendQuoteRequest", "sendQuoteRequest");

     // In this step we get the stub implementation generated by KIARA
     Benchmark client = connection.getServiceInterface(binder);

   As shown this simply requires a single new API call that
   established the mapping between API and IDL data types and
   interfaces. It is only required if complex mapping are required
   (see example above).

   As part of the BMBF project ARVIDA we are aurrently defining
   semantic API descriptions that would even allow us to derive these
   mapping from the additional semantic information automatically.
   This will require only a few changes to the extended API described
   here.


4. Server: Defining the service to be called

a) Thrift

   Again Thrift requires explicit instantiation of transport and
   protocol layers:

     // BenchmarkHandler is a skeleton generated by the Thrift IDL compiler
     // and implemented by the user
     BenchmarkHandler handler = new BenchmarkHandler();
     Benchmark.Processor processor = new Benchmark.Processor(handler);

     TServerTransport serverTransport = new TServerSocket(9090);
     TServer server = 
     	  new TSimpleServer(new Args(serverTransport).processor(processor));
     server.serve();

b) KIARA (IDL-derived API)

   In the IDL-defined API server initialization is straight forward.

   Common initialization code:

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

   Defining the service then is trivial and actually even simple than
   with Thrift. 

     // BenchmarkImpl skeleton is generated by the IDL compiler 
     // and then implemented by the user in a derived class
     BenchmarkImpl benchmarkImpl = new BenchmarkUserImpl();

     // Since mapping is known to the KIARA, registration is simple
     service.register(benchmarkImpl);

   KIARA automatically handles all negotiations of transport
   mechanisms, transport protocols, and security mechanisms based on
   information provided to it (not shown here yet). By default it
   offers all options on all network interfaces and chooses the most
   appropriate one supported by both peers at runtime.  

   Final server construction and execution, which is same in both
   KIARA cases (IDL- and application-derived):

     // Create a server and register service, with the default setting
     Server server = context.newServer("benchmarkService");
     server.addService(service);
     server.run();

   This sets up a basic KIARA server on the local machine. It provides
   the service descriptions of all added services (in JSON) via an
   embedded HTTP server using the given name as the local base URL. By
   default this server listens on all network interfaces using port
   8080.

   Adding a new service makes that available via the server as
   well. By default it listens on a separate port that is advertised
   via the service description of the server.

   These default policies can be also be overridden if needed:

     Server server = context.newServer("0.0.0.0", port, "benchmarkService");
     server.addService("tcp://0.0.0.0:53212", protocol, service);


c) KIARA (application-derived API)
  
   Again the IDL- and application derived APIs are very similar. The
   main difference is that in the application-derived approach, the
   stubs and skeletons are derived from the IDL, which is also made
   available to the remote clients as part of the setup. Thus, the IDL
   must be declared to KIARA first.

     // The following step is unique to the app-derived KIARA since it
     // generates stubs and skeletons at the runtime based on the IDL,
     // which is defined here (or possible through a file, network, or
     // such)
     service.loadServiceIDLFromString(KIARA_IDL,
           "namespace * benchmark" + 
	   "...");
  
   Optionally, we could also generate the IDL from the interface of an
   application defined type directly. This is much simpler of course
   but has the danger that it implicitly changes the IDL whenever the
   internal interfaces might change. At some point this is an issue of
   taste and a trade-off between development speed and interface
   stability. Note, that the API allow to annotate the interface in a
   suitable way (not shown here yet for simplicity).

     // Generate service IDL from interfaces
     service.generateIDLFromInterface(Benchmark.class);

   Once the IDL is defined we can simply instantiate and register the
   service implementation with KIARA. This is as simple as it gets:

     // BenchmarkUserImpl is an implementation of the Benchmark
     // interface as defined by the user
     BenchmarkUserImpl benchmarkImpl = new BenchmarkUserImpl();

     // In case of a 1:1 mapping between the IDL and the
     // application-specific service implementation we can
     // simply register the service directly
     service.registerService(benchmarkImpl);

   In case a mapping between the IDL and the application data
   types/interfaces in necessary, this can *optionally* be provided as
   well by registering each service function separately:

     // We need to register BenchmarkUserImpl methods with IDL methods
     // for correct dispatch. We register the services individually as not
     // all services may need to be proided here
     service.registerServiceFunction(
		"benchmark.sendMarketData", 
     		benchmarkUserImpl, "sendMarketData");
     service.registerServiceFunction(
		"benchmark.sendQuoteRequest",
		benchmarkUserImpl, "sendQuoteRequest");


5. Advanced mapping for the IDL-generated KIARA API

   IDL-derived approaches normally have a single fixed mapping from
   the IDL to specific data types of a given language (Java here). We
   propose to allow to specify variants of this mapping when compiling
   the IDL to Java code. 

   Such a 'mapping' document would define how to map generic data
   types that can be used in the Thrift IDL to a selection of specific
   but compatible data types in Java, e.g.:
   
     Thrift IDL     Java
     -------------------------
     list<T>   ->   Collection<T> or List<T> or T[]
     set<T>    ->   Set<T> or Collection<T> or List<T> or T[]
     map<K,V>  ->   Map<K,V> or List<KeyValuePair<K,V>> where the KeyValuePair
     	       	    class is defined by the user

    Additionally, in an exteneded version this approach could also be
    used to map IDL tapes to essentially arbitrary application data
    types. This would be similar to the mappings defined in the Java
    API (see above) but would be compiled into the "static" stubs and
    skeletons. 

    Essentially, this would be an intermediate form where the mapping
    between IDL and application types is predefined at compile time.
    The stubs and skeletons can then be generated in advance. This has
    not been worked out in detail yet, but look slike an interesting
    option.

    For the simple case, one of the ways to achieve this is to provide
    text templates of some form, e.g.:

       list<$T> = List<$T>

    where $NAME at the left side corresponds to the IDL type and on
    the right side to the application type.

    For example:

    list<$T> = $T[]
    set<$T>  = Set<$T>
    map<$K,$V> = List<KeyValuePair<$K,$V>>
    
    TODO: Adding of elements to the array, list, set, and map is a
          different operation, which would require templates as well.

    Possibly the best approach is to use template engine like Apache
    Velocity Templates which could drive the code generator:    
    http://www.onjava.com/pub/a/onjava/2004/05/05/cg-vel1.html


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