Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Tutorial: Building your first Asynchronous OSGi Remote Service"

(Define the Asynchronous Service)
(Registering and Exporting the Remote Service)
 
(32 intermediate revisions by the same user not shown)
Line 74: Line 74:
 
==Introduction==
 
==Introduction==
  
In a [[Tutorial:  Building your first OSGi Remote Service | previous tutorial]], we went through how to define, implement, and execute a simple OSGi Remote Service using ECF's Remote Service implementationBy definition, Remote Services require communication between processes/frameworks since local OSGi services are based upon synchronous invocation of a service method (i.e. the calling thread blocks while the service method is being invoked, and only continues after the service method returns).  With Remote Services, this means that the calling thread can block as the remote method invocation is sent over a network, handled by the remote service, and then the response (method return value) is returned over the network.  As well, the remote method invocation may fail outright...because of a network partition, or the remote service host failure.  The handling of such failures may block the calling thread for a significant length of time.
+
In a [[Tutorial:  Building your first OSGi Remote Service | previous tutorial]], we defined, implemented and used a simple OSGi Remote Service.  Local OSGi services are based upon synchronous invocation of a service method (i.e. the calling thread blocks while the service method is being invoked, and continues execution once the method returns).  With Remote Services, call-return semantics means that the calling thread can block, since the call is sent over a network, handled by the remote service, and then the response (method return value) is returned over the network.  Because of the I/O required, the thread that makes these remote calls can block.  As well, the remote method call may fail...because of a network partition or the remote service host failure.   
  
To support asynchronous (non-blocking) remote service invocation, ECF's implementation of OSGi Remote Services ECF has created [[Asynchronous Remote Services]].  This provides developers of Remote Services the option to expose non-blocking/asynchronous remote method invocation to consumers of their remote service, without '''any''' additional programming.  This tutorial will step through the process of defining and using an asynchronous remote service.
+
To support asynchronous non-blocking remote service invocation, ECF's implementation of OSGi Remote Services has created [[Asynchronous Remote Services]].  This provides developers of Remote Services the option to expose non-blocking/asynchronous remote method calling to consumers, without '''any''' additional programming.  This tutorial will step through the process of defining and using an asynchronous remote service, first by using the Java Concurrent API Future class, and then describing the use of Java8 CompletableFuture.
  
==Define the Asynchronous Service==
+
==Define the Asynchronous Remote Service==
  
 
In the [[Tutorial:  Building your first OSGi Remote Service | previous tutorial]] a simple time service was defined:
 
In the [[Tutorial:  Building your first OSGi Remote Service | previous tutorial]] a simple time service was defined:
Line 88: Line 88:
  
 
public Long getCurrentTime();
 
public Long getCurrentTime();
 
 
}
 
}
 
</source>
 
</source>
Line 94: Line 93:
 
As described above, when the consumer calls/invokes the '''getCurrentTime()''' method, without any implementation error it's possible for this method to block for a relatively long time...and/or throw a runtime exception...because the communication failed between the consumer and the remote service host.
 
As described above, when the consumer calls/invokes the '''getCurrentTime()''' method, without any implementation error it's possible for this method to block for a relatively long time...and/or throw a runtime exception...because the communication failed between the consumer and the remote service host.
  
ECF uniquely supports the creation of an Asynchronous Remote Service interface...that will automatically be available to clients of Remote Services.  For example, here is the asynchronous service interface for the '''ITimeService''':
+
ECF uniquely supports the creation of an Asynchronous Remote Service interface...that will automatically be available to clients of Remote Services.  For example, here is the asynchronous service interface for the '''ITimeService''' given above:
  
 
<source lang="java">
 
<source lang="java">
Line 100: Line 99:
  
 
import java.util.concurrent.Future;
 
import java.util.concurrent.Future;
import org.eclipse.ecf.remoteservice.IAsyncRemoteServiceProxy;
 
  
public interface ITimeServiceAsync extends IAsynchRemoteServiceProxy {
+
public interface ITimeServiceAsync {
  
 
public Future<Long> getCurrentTimeAsync();
 
public Future<Long> getCurrentTimeAsync();
 
 
}
 
}
 
</source>
 
</source>
  
Note:
+
Note the following about this interface:
  
#The name of the asynchronous remote service interface is '''ITimeServiceAsync''', which is the same as '''ITimeService''' + '''Async'''.  In general, this async remote service interface must be named '''OriginalServiceInterfaceNameAsync'''.
+
#The name of the asynchronous remote service interface is '''ITimeServiceAsync''', which is the same as ITimeService + Async.  In general, this async remote service interface must be named '''OriginalServiceInterfaceNameAsync'''.
#The name of the method is '''getCurrentTimeAsync''', which is the same oas '''getCurrentTime''' and '''Async'''Like the name of the service interface, in general the async method must be named '''OriginalServiceMethodNameAsync'''
+
#The name of the method is '''getCurrentTimeAsync''', which is the same as getCurrentTime and Async.  In general the async method must be named '''originalServiceMethodNameAsync'''
#The return value is of type '''Future<Long>'''.  This uses the Java Concurrent API '''Future''' class along with the original type returned by the '''getCurrentTime''' method (Long) as the generic qualifier for the Future class.  In general, the pattern here must be '''Future<OriginalType>'''
+
#The return value is of type '''Future<Long>'''.  This uses the Java Concurrent API [http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Future.html Future] class along with the type returned by the getCurrentTime method (Long) as the generic qualifier for the Future class.  In general, the pattern here must be Future<OriginalType> or unqualified...e.g. Future.  Note that the '''Void''' type can be used for methods that return void...e.g. Future<Void>.
 +
#The contract for this asynchronous remote service is that getCurrentTimeAsync will not block, and immediately return a non-null instance of Future<Long>.  At the discretion of the caller, the Future<Long> instance may be queried about the availability of the result/completion of the call, and then able to access the underlying result via the methods exposed by the Future class.
  
 +
This single asynchronous service interface is all that is needed for the remote service host to expose non-blocking access to the getCurrentTime() method to consumers.  '''No''' actual implementation is necessary, since it's provided automatically by ECF Remote Services implementation when discovered on the consumer.
  
 +
The source code for the ITimeService and ITimeServiceAsync are available as an example project in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git ECF git repo] in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice com.mycorp.examples.timeservice] project.
  
The purpose of this service is simply to provide the current time...in the form of a long value specifying the number of milliseconds since 1970 returned from a call to '''getCurrentTime()'''.  Of course, declarations of much more complex services are possible, with multiple methods, multiple arguments for each method, and complex types as arguments and return values.  In short, any java interface can be used as the service interface.  OSGi also allows the use of multiple interfaces exposed by a single implementation.
+
==Registering and Exporting the Remote Service==
  
With OSGi services it's a best practice to put the service interface (and any class referred to by the service interface) in a distinct bundle.  This creates a clear, modular, framework-enforced separation between the service API, the service implementation, and any code that uses the service.   This minimizes the coupling between subsystems. For Remote Services such separation and low-coupling is particularly important, since the host implementation is in a separate process from the service client/consumers...making it highly desirable to minimize the coupling and clarify the interface between systems.
+
In the initial tutorial, the host exports the remote service by setting some standardized service properties, and registering the service...here is the remote service registration from the [[Tutorial:_Building_your_first_OSGi_Remote_Service#Service_Host:_Register_the_Service | previous tutorial]]
 
+
[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice Here is a complete source for the bundle with the ITimeService interface class].  The package containing this class is exported via the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice/META-INF/MANIFEST.MF manifest] so that the Host and Consumer bundles (below) can refer to the service interface in their own code.
+
  
 
<source lang="java">
 
<source lang="java">
Export-Package: com.mycorp.examples.timeservice;version="1.0.0"
+
Dictionary<String, String> props = new Hashtable<String, String>();
</source>
+
props.put("service.exported.interfaces", "*");
 +
// 'ecf.generic.server' is the config used in this example, but others are
 +
// possible...for example see https://wiki.eclipse.org/Tutorial:_Creating_a_RESTful_Remote_Service_Provider
 +
props.put("service.exported.configs","ecf.generic.server");
  
Note also the version information associated with the package...OSGi Remote Services supports versioning of service interfaces.  Being able to version service interfaces, and have the underlying framework automatically restrict the service binding to compatible versions is a very useful feature for enhancing and maintaining APIs over time.
+
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);  
 
+
==Service Host: Implement the Service==
+
 
+
Next, it's necessary to create a service 'host' implementing the '''ITimeService''' interface.  
+
 
+
<source lang="java">
+
package com.mycorp.examples.timeservice.host;
+
 
+
import com.mycorp.examples.timeservice.ITimeService;
+
 
+
public class TimeServiceImpl implements ITimeService {
+
 
+
public Long getCurrentTime() {
+
System.out.println("TimeServiceImpl.  Received call to getCurrentTime()");
+
return new Long(System.currentTimeMillis());
+
}
+
}
+
 
</source>
 
</source>
  
This implementation simply returns the local system's current time.  To refer to the '''ITimeService''' service the host bundle manifest must import the '''com.mycorp.examples.timeservice''' package from the bundle defined above by adding a line like this to the host bundle manifest:
+
To allow the remote service consumer access to the Asynchronous Remote Service (e.g. ITimeServiceAsync) a new/additional service property must be set:
 
+
<source lang="java">
+
Import-Package: com.mycorp.examples.timeservice;version="1.0.0"
+
</source>
+
 
+
==Service Host: Register the Service==
+
 
+
In the OSGi Service model it's necessary to '''register''' the service with the OSGi service registry.   There are multiple ways of registering a service...programmatically java code,  declaratively via Declarative Services (DS).  For introductions about various approaches to register an OSGi Service see [http://www.vogella.com/articles/OSGiServices/article.html Vogella's OSGi Services Tutorial].  For this tutorial we will register our TimeServiceImpl via a single line of java code:
+
 
+
<source lang="java">
+
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(),null);
+
</source>
+
 
+
With a local OSGi Service the above line of java is all that's necessary to register the service.  For a Remote Service, only a little more is required to trigger the distribution provider to export the service:
+
  
 
<source lang="java">
 
<source lang="java">
 
Dictionary<String, String> props = new Hashtable<String, String>();
 
Dictionary<String, String> props = new Hashtable<String, String>();
// OSGi Standard Property - indicates which of the interfaces of the service will be exported.  '*' means 'all'.
 
 
props.put("service.exported.interfaces", "*");
 
props.put("service.exported.interfaces", "*");
// OSGi Standard Property (optional) - indicates which provider config(s) will be used to export the service
+
// 'ecf.generic.server' is the config used in this example, but others are
// (If not explicitly given here, the provider is free to choose a default configuration for the service)
+
// possible...for example see https://wiki.eclipse.org/Tutorial:_Creating_a_RESTful_Remote_Service_Provider
 
props.put("service.exported.configs","ecf.generic.server");
 
props.put("service.exported.configs","ecf.generic.server");
// Register a new TimeServiceImpl with the above props
+
// ECF RS property allowing ITimeServiceAsync interface to be automatically
 +
// exposed to consumers of the ITimeService by ECF RS implementation
 +
props.put("ecf.exported.async.interfaces","*");
 +
 
 
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);  
 
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);  
 
</source>
 
</source>
  
With the ECF RS implementation present in the host runtime, adding these service properties will result in the following occurring before the registerService call returns:
+
The '''ecf.exported.async.interfaces''' service property, similar to the '''service.exported.interfaces''' standard property, tells the ECF Remote Service implementation to instrument the consumer's proxy for asynchronous access via the asynchronous remote service interface.
  
#The TimeServiceImpl will be exported via the distribution provider identified via config..i.e. "ecf.generic.server"
+
The source code for the time service host is available as an example project in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git ECF git repo] in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.host com.mycorp.examples.timeservice.host] project.
#The exported service will be published for discovery, using any installed and available network discovery providers
+
  
The full source for the host bundle, with the host implementation and registration code can be found in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.host com.mycorp.examples.timeservice.host project].
+
==Consumer:  Discover and Use the Remote Service==
  
Note that there are no references to additional OSGi classes...or ECF classes...but rather only classes exposed by the service interface (in this case '''ITimeService''')For all of the remoting information, standardized service properties are used to qualify the '''ITimeService''' API as a remote service, and to select and configure a remote services provider.  This provides a great deal of flexibility to the '''ITimeService''' implementer, and allows the dynamic selection and use of a variety of distribution providers...from ECF or not...and multiple configurations.
+
To invoke methods on a remote service, the consumer must first discover the service.  Asynchronous Remote Service discovery works exactly the same as normal remote service discovery and so for details of that process see the [[Tutorial:_Building_your_first_OSGi_Remote_Service#Consumer:_Discover_and_Use_the_Service | previous tutorial]].
  
==Consumer:  Discover and Use the Service==
+
In the previous tutorial, upon discovery, the '''ITimeService''' instance is injected in the consumer via the '''bindTimeService''' method:
 
+
For consumers to use a remote service they must first discover the service.  With OSGi Remote Services this discovery can be accomplished several ways.  The easiest and most automatic is to use a network discovery protocol, such that when the remote service is exported by a host and published via a network discovery protocol (as described above), a consumer can then automatically discover the remote service over the network.
+
 
+
For this tutorial, we'll assume that one ECF's supported LAN-based network discovery providers is present, meaning that if the remote service host and consumer are on the same LAN, the consumer will automatically discover the remote service via the network discovery protocol (e.g. Zeroconf).  With network discovery, the consumer can use OSGi Declarative Services to automatically bind/inject the service into client code:
+
  
 
<source lang="java">
 
<source lang="java">
Line 199: Line 166:
 
System.out.println("Discovered ITimeService via DS");
 
System.out.println("Discovered ITimeService via DS");
 
// Call the service and print out result!
 
// Call the service and print out result!
System.out.println("Current time is: " + timeService.getCurrentTime());  // Call the ITimeService remote service
+
System.out.println("Current time is: " + timeService.getCurrentTime());   
 
}
 
}
 
}
 
}
 
</source>
 
</source>
  
OSGi Declarative Services will automatically inject the '''ITimeService''' proxy by calling the bindTimeService method when the remote service is discoveredWith the code above, the '''ITimeService.getCurrentTime()''' method will be called only when the binding occurs.
+
All that is necessary to access the '''ITimeServiceAsync''' methods is to query the injected timeService instance for the desired asynchronous remote service interfaceFor example:
 
+
As with the host, to refer to the '''ITimeService''' the consumer must also import the '''com.mycorp.examples.timeservice''' package by importing the package via it's manifest:
+
  
 
<source lang="java">
 
<source lang="java">
Import-Package: com.mycorp.examples.timeservice;version="1.0.0"
+
void bindTimeService(ITimeService timeService) {
 +
    if (timeService instanceof ITimeServiceAsync) {
 +
        ITimeServiceAsync asyncTimeService = (ITimeServiceAsync) timeService;
 +
        System.out.println("Discovered ITimeServiceAsync via DS");
 +
        // Call the asynchronous remote service.  Unlike the synchronous getTimeService(),
 +
        // this method will not block
 +
        Future<Long> currentTimeFuture = asyncTimeService.getCurrentTimeAsync();
 +
        // potentially do other operations here...
 +
        System.out.println("Current time is: " + currentTimeFuture.get()); 
 +
    }
 +
}
 
</source>
 
</source>
  
The only other requirement for the consumer is to have standard DS markup so that DS can know to call '''bindTimeService''' when the '''ITimeService''' proxy is registered by the consumer's distribution providerThe required DS markup is available in [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds/OSGI-INF/timeservicecomponent.xml this xml file].
+
With ECF's implementation of remote services, if an '''ITimeService''' proxy is created for the consumer, and an asynchronous service interface (i.e. '''ITimeServiceAsync''') is present then the proxy will implement the asynchronous service interface.  The actual implementation is provided directly by ECF Remote Services, and neither the host nor the consumer must do anything further for the asynchronous remote service to be usable by consumersAll that's necessary is to declare the asynchronous remote service (ITimeServiceAsync) in the same package as the original (ITimeService), and export a host instance of the remote service as described by the [[Tutorial:_Building_your_first_OSGi_Remote_Service#Service_Host:_Register_the_Service | previous tutorial]].
  
The complete project for this consumer (TimeServiceComponent class, DS markup, and manifest.mf) [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds is available here].
+
The source code for the time service consumer is available as an example project in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git ECF git repo] in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds com.mycorp.examples.timeservice.consumer.ds] project.
  
With the three bundles defined...i.e.
+
==Using Java8 CompletableFuture==
  
#[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice the service interface bundle]
+
In Java 8, in addition to lambdas, streams and other features, a new type of Future was introduced, called [http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html CompletableFuture]. Unlike java.util.concurrent.Future, CompletableFuture can be used in a way guaranteed not to block, whereas calling Future.get() can block...if the underlying result does not yet exist.
#[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.host the host bundle]
+
#[http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds the consumer bundle]
+
  
along with the ECF remote service implementation, we may now run the '''ITimeService''' host implementation. The consumer can then be run...to discovery, inject, and to use the '''ITimeService'''.
+
In ECF 3.8.1/Luna support for CompletableFuture has been added.  As shown above, async proxy interfaces can return '''either java.util.concurrent.Future''' as above...or java8's '''java.util.concurrent.CompletableFuture'''. For example, ITimeServiceAsync can be defined to return CompletableFuture rather than Future:
  
==Starting the Host==
+
<source lang="java">
 +
package com.mycorp.examples.timeservice;
  
In the com.mycorp.examples.timeservice.host project, in the launch directory is a launch config named TimeServiceHost.launch.  In Eclipse, you can launch the host by right-clicking on this launch config and choosing '''Run As -> TimeServiceHost'''.  In the console output, you should see something like this
+
import java.util.concurrent.CompletableFuture;
  
<pre>
+
public interface ITimeServiceAsync {
osgi> MyTimeService host registered with registration={com.mycorp.examples.timeservice.ITimeService}=...
+
Service Exported by RemoteServiceAdmin.  EndpointDescription Properties=...
+
</pre>
+
  
This indicates that the service was registered and exported by ECF's remote service implementation.
+
public CompletableFuture<Long> getCurrentTimeAsync();
 +
}
 +
</source>
  
==Starting the Consumer to Execute the Remote Service==
+
The source code for Java8 asynchronous time service is available as an example project in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git ECF git repo] in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.async com.mycorp.examples.timeservice.async] project.
  
In the com.mycorp.examples.timeservice.consumer.ds project, in the launch directory is a launch config named TimeServiceConsumer DS.launch. In Eclipse, you may launch the consumer by right-clicking on this launch config and choosing '''Run As -> TimeServiceConsumer DS'''.  In the console output, you should immediately see:
+
==Consumer: Discover and Use the Java8 CompletableFuture==
  
<pre>
+
With the remote service host's use of the '''ecf.exported.async.interfaces''' service property (as described in [[Tutorial:_Building_your_first_Asynchronous_OSGi_Remote_Service#Registering_the_Remote_Service | Registering the Remote Service above]], it's possible on the consumer to have the '''ITimeServiceAsync''', when discovered, to be injected directly into a declarative services component
osgi>
+
</pre>
+
  
But then after a few seconds (for the network discovery protocol to operate) the service will be discovered, the proxy created via the ECF distribution provier, and the proxy injected into the TimeServiceComponent via the call to '''bindTimeService''', resulting in console output:
+
<source lang="java">
 +
void bindTimeService(ITimeServiceAsync timeService) {
 +
System.out.println("Discovered ITimeServiceAsync via DS");
 +
// Get the CompletableFuture...no blocking here
 +
CompletableFuture<Long> cf = timeService.getCurrentTimeAsync();
 +
// print out time when done...no blocking anywhere!
 +
cf.thenAccept((time) -> System.out.println("Remote time is: " + time));
 +
}
 +
</source>
  
<pre>
+
In this line:
osgi> Discovered ITimeService via DS
+
Current time is: 1386354647672
+
</pre>
+
  
As well as host console output resulting from the call to '''TimeServiceImpl.getCurrentTime()''':
+
<source lang="java">
 +
CompletableFuture<Long> cf = timeService.getCurrentTimeAsync();
 +
</source>
  
<pre>
+
the asynchronous remote service is being called, and CompletableFuture instance is returnedThe implementation of this method is provided by the ECF Remote Service proxy, constructed automatically as part of the Remote Service implementation.
TimeServiceImplReceived call to getCurrentTime()
+
</pre>
+
  
This constitutes a successful test of the remote time service.
+
The client may then call [http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#thenAccept-java.util.function.Consumer- CompletableFuture.thenAccept] to execute a given block of code '''when the result of the remote service is available'''
 +
 
 +
<source lang="java">
 +
cf.thenAccept((time) -> System.out.println("Remote time is: " + time));
 +
</source>
 +
 
 +
Note that the given System.out.println("Remote time is: " + time)); is '''only executed when the underlying remote access is completed, and a time result is available'''.  This means that there is no blocking...i.e. no matter how long the remote service takes to complete, the thread that calls timeService.getCurrentTimeAsync and cf.thenAccept is guaranteed not to block.
 +
 
 +
The source code for Java8 asynchronous time service consumer is available as an example project in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git ECF git repo] in the [http://git.eclipse.org/c/ecf/org.eclipse.ecf.git/tree/examples/bundles/com.mycorp.examples.timeservice.consumer.ds.async com.mycorp.examples.timeservice.consumer.ds.async] project.
  
 
==Background and Related Articles==
 
==Background and Related Articles==
 +
 +
[[Asynchronous Proxies for Remote Services]]
  
 
[[Getting Started with ECF's OSGi Remote Services Implementation]]
 
[[Getting Started with ECF's OSGi Remote Services Implementation]]
  
 
[[OSGi Remote Services and ECF]]
 
[[OSGi Remote Services and ECF]]
 
[[Asynchronous Proxies for Remote Services]]
 
  
 
[[File-based Discovery | Static File-based Discovery of Remote Service Endpoints]]
 
[[File-based Discovery | Static File-based Discovery of Remote Service Endpoints]]

Latest revision as of 16:27, 22 April 2014


Introduction

In a previous tutorial, we defined, implemented and used a simple OSGi Remote Service. Local OSGi services are based upon synchronous invocation of a service method (i.e. the calling thread blocks while the service method is being invoked, and continues execution once the method returns). With Remote Services, call-return semantics means that the calling thread can block, since the call is sent over a network, handled by the remote service, and then the response (method return value) is returned over the network. Because of the I/O required, the thread that makes these remote calls can block. As well, the remote method call may fail...because of a network partition or the remote service host failure.

To support asynchronous non-blocking remote service invocation, ECF's implementation of OSGi Remote Services has created Asynchronous Remote Services. This provides developers of Remote Services the option to expose non-blocking/asynchronous remote method calling to consumers, without any additional programming. This tutorial will step through the process of defining and using an asynchronous remote service, first by using the Java Concurrent API Future class, and then describing the use of Java8 CompletableFuture.

Define the Asynchronous Remote Service

In the previous tutorial a simple time service was defined:

package com.mycorp.examples.timeservice;
 
public interface ITimeService {
 
	public Long getCurrentTime();
}

As described above, when the consumer calls/invokes the getCurrentTime() method, without any implementation error it's possible for this method to block for a relatively long time...and/or throw a runtime exception...because the communication failed between the consumer and the remote service host.

ECF uniquely supports the creation of an Asynchronous Remote Service interface...that will automatically be available to clients of Remote Services. For example, here is the asynchronous service interface for the ITimeService given above:

package com.mycorp.examples.timeservice;
 
import java.util.concurrent.Future;
 
public interface ITimeServiceAsync {
 
	public Future<Long> getCurrentTimeAsync();
}

Note the following about this interface:

  1. The name of the asynchronous remote service interface is ITimeServiceAsync, which is the same as ITimeService + Async. In general, this async remote service interface must be named OriginalServiceInterfaceNameAsync.
  2. The name of the method is getCurrentTimeAsync, which is the same as getCurrentTime and Async. In general the async method must be named originalServiceMethodNameAsync
  3. The return value is of type Future<Long>. This uses the Java Concurrent API Future class along with the type returned by the getCurrentTime method (Long) as the generic qualifier for the Future class. In general, the pattern here must be Future<OriginalType> or unqualified...e.g. Future. Note that the Void type can be used for methods that return void...e.g. Future<Void>.
  4. The contract for this asynchronous remote service is that getCurrentTimeAsync will not block, and immediately return a non-null instance of Future<Long>. At the discretion of the caller, the Future<Long> instance may be queried about the availability of the result/completion of the call, and then able to access the underlying result via the methods exposed by the Future class.

This single asynchronous service interface is all that is needed for the remote service host to expose non-blocking access to the getCurrentTime() method to consumers. No actual implementation is necessary, since it's provided automatically by ECF Remote Services implementation when discovered on the consumer.

The source code for the ITimeService and ITimeServiceAsync are available as an example project in the ECF git repo in the com.mycorp.examples.timeservice project.

Registering and Exporting the Remote Service

In the initial tutorial, the host exports the remote service by setting some standardized service properties, and registering the service...here is the remote service registration from the previous tutorial

Dictionary<String, String> props = new Hashtable<String, String>();
props.put("service.exported.interfaces", "*");
// 'ecf.generic.server' is the config used in this example, but others are
// possible...for example see https://wiki.eclipse.org/Tutorial:_Creating_a_RESTful_Remote_Service_Provider
props.put("service.exported.configs","ecf.generic.server");
 
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);

To allow the remote service consumer access to the Asynchronous Remote Service (e.g. ITimeServiceAsync) a new/additional service property must be set:

Dictionary<String, String> props = new Hashtable<String, String>();
props.put("service.exported.interfaces", "*");
// 'ecf.generic.server' is the config used in this example, but others are
// possible...for example see https://wiki.eclipse.org/Tutorial:_Creating_a_RESTful_Remote_Service_Provider
props.put("service.exported.configs","ecf.generic.server");
// ECF RS property allowing ITimeServiceAsync interface to be automatically 
// exposed to consumers of the ITimeService by ECF RS implementation
props.put("ecf.exported.async.interfaces","*");
 
bundleContext.registerService(ITimeService.class, new TimeServiceImpl(), props);

The ecf.exported.async.interfaces service property, similar to the service.exported.interfaces standard property, tells the ECF Remote Service implementation to instrument the consumer's proxy for asynchronous access via the asynchronous remote service interface.

The source code for the time service host is available as an example project in the ECF git repo in the com.mycorp.examples.timeservice.host project.

Consumer: Discover and Use the Remote Service

To invoke methods on a remote service, the consumer must first discover the service. Asynchronous Remote Service discovery works exactly the same as normal remote service discovery and so for details of that process see the previous tutorial.

In the previous tutorial, upon discovery, the ITimeService instance is injected in the consumer via the bindTimeService method:

package com.mycorp.examples.timeservice.consumer.ds;
 
import com.mycorp.examples.timeservice.ITimeService;
 
public class TimeServiceComponent {
 
	void bindTimeService(ITimeService timeService) {
		System.out.println("Discovered ITimeService via DS");
		// Call the service and print out result!
		System.out.println("Current time is: " + timeService.getCurrentTime());  
	}
}

All that is necessary to access the ITimeServiceAsync methods is to query the injected timeService instance for the desired asynchronous remote service interface. For example:

void bindTimeService(ITimeService timeService) {
    if (timeService instanceof ITimeServiceAsync) {
        ITimeServiceAsync asyncTimeService = (ITimeServiceAsync) timeService;
        System.out.println("Discovered ITimeServiceAsync via DS");
        // Call the asynchronous remote service.  Unlike the synchronous getTimeService(),
        // this method will not block
        Future<Long> currentTimeFuture = asyncTimeService.getCurrentTimeAsync();
        // potentially do other operations here...
        System.out.println("Current time is: " + currentTimeFuture.get());  
    }
}

With ECF's implementation of remote services, if an ITimeService proxy is created for the consumer, and an asynchronous service interface (i.e. ITimeServiceAsync) is present then the proxy will implement the asynchronous service interface. The actual implementation is provided directly by ECF Remote Services, and neither the host nor the consumer must do anything further for the asynchronous remote service to be usable by consumers. All that's necessary is to declare the asynchronous remote service (ITimeServiceAsync) in the same package as the original (ITimeService), and export a host instance of the remote service as described by the previous tutorial.

The source code for the time service consumer is available as an example project in the ECF git repo in the com.mycorp.examples.timeservice.consumer.ds project.

Using Java8 CompletableFuture

In Java 8, in addition to lambdas, streams and other features, a new type of Future was introduced, called CompletableFuture. Unlike java.util.concurrent.Future, CompletableFuture can be used in a way guaranteed not to block, whereas calling Future.get() can block...if the underlying result does not yet exist.

In ECF 3.8.1/Luna support for CompletableFuture has been added. As shown above, async proxy interfaces can return either java.util.concurrent.Future as above...or java8's java.util.concurrent.CompletableFuture. For example, ITimeServiceAsync can be defined to return CompletableFuture rather than Future:

package com.mycorp.examples.timeservice;
 
import java.util.concurrent.CompletableFuture;
 
public interface ITimeServiceAsync {
 
	public CompletableFuture<Long> getCurrentTimeAsync();
}

The source code for Java8 asynchronous time service is available as an example project in the ECF git repo in the com.mycorp.examples.timeservice.async project.

Consumer: Discover and Use the Java8 CompletableFuture

With the remote service host's use of the ecf.exported.async.interfaces service property (as described in Registering the Remote Service above, it's possible on the consumer to have the ITimeServiceAsync, when discovered, to be injected directly into a declarative services component

void bindTimeService(ITimeServiceAsync timeService) {
	System.out.println("Discovered ITimeServiceAsync via DS");
	// Get the CompletableFuture...no blocking here
	CompletableFuture<Long> cf = timeService.getCurrentTimeAsync();
	// print out time when done...no blocking anywhere!
	cf.thenAccept((time) -> System.out.println("Remote time is: " + time));
}

In this line:

CompletableFuture<Long> cf = timeService.getCurrentTimeAsync();

the asynchronous remote service is being called, and CompletableFuture instance is returned. The implementation of this method is provided by the ECF Remote Service proxy, constructed automatically as part of the Remote Service implementation.

The client may then call CompletableFuture.thenAccept to execute a given block of code when the result of the remote service is available

cf.thenAccept((time) -> System.out.println("Remote time is: " + time));

Note that the given System.out.println("Remote time is: " + time)); is only executed when the underlying remote access is completed, and a time result is available. This means that there is no blocking...i.e. no matter how long the remote service takes to complete, the thread that calls timeService.getCurrentTimeAsync and cf.thenAccept is guaranteed not to block.

The source code for Java8 asynchronous time service consumer is available as an example project in the ECF git repo in the com.mycorp.examples.timeservice.consumer.ds.async project.

Background and Related Articles

Asynchronous Proxies for Remote Services

Getting Started with ECF's OSGi Remote Services Implementation

OSGi Remote Services and ECF

Static File-based Discovery of Remote Service Endpoints

Download ECF Remote Services/RSA Implementation

How to Add Remote Services/RSA to Your Target Platform

Back to the top