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
You can get more information about our cookies and privacy policies clicking on the following links: Privacy policy Cookies policy