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 "ECF/Asynchronous Remote Services"

< ECF
(Java 8 CompletableFuture)
 
(33 intermediate revisions by 3 users not shown)
Line 1: Line 1:
[[ECF]]'s Remote Services API, which is used to implement OSGi 4.2 remote services, has had the ability for consumers/clients to use asynchronous/non-block remote method calls for at least the last 2 years.
+
<!-- This CSS wraps code blocks in pretty boxes -->
 +
<css>
 +
.mw-code {
 +
  background-color: #fafafa;
 +
  padding: 20px;
 +
  border-color: #ddd;
 +
  border-width: 3px;
 +
  border-style: solid;
 +
  border-radius: 5px;
 +
  margin-left: 10px;
 +
  margin-right: 10px;
 +
  overflow-x: auto; [[File:Example.jpg]]
 +
}
  
For background on the discussion about asynchronous remote services invocation in general and possible future standardization by the OSGI standards organization see [http://www.osgi.org/blog/2010/04/calling-your-cake-and-sending-it-too.html Peter Kriens blog entry], and [http://eclipseecf.blogspot.com/2010/04/osgi-remote-services-and-ecf.html Scott Lewis' blog entry].
+
.mw-headline {
 +
  font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
 +
  color: rgb(51, 51, 51);
 +
}
  
===Normal/Synchronous Services===
+
p {
 +
  font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
 +
  text-align: justify;
 +
}
 +
 
 +
h1 {
 +
  border-bottom-color: rgb(238, 238, 238);
 +
  font-weight: bold;
 +
  padding-bottom: 17px;
 +
  font-size: 30px;
 +
  line-height: 36px;
 +
}
 +
 
 +
h2 {
 +
  border-bottom-color: rgb(238, 238, 238);
 +
  padding-bottom: 12px;
 +
  font-weight: bold;
 +
  font-size: 24px;
 +
  line-height: 36px;
 +
}
 +
 
 +
h3 {
 +
  border-bottom-width: 1px;
 +
  border-bottom-style: solid;
 +
  border-bottom-color: rgb(238, 238, 238);
 +
  padding-bottom: 8px;
 +
  font-size: 18px;
 +
  line-height: 27px;
 +
}
 +
 
 +
h4 {
 +
  font-size: 14px;
 +
}
 +
 
 +
h5 {
 +
  font-size: 12px;
 +
}
 +
 
 +
h6 {
 +
  font-size: 11px;
 +
  color: #EBEBEB;
 +
  text-transform: uppercase;
 +
}
 +
 
 +
a {
 +
  color: rgb(0, 105, 214);
 +
  font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
 +
}
 +
 
 +
a:visited {
 +
  color: rgb(0, 105, 214);
 +
}
 +
</css>
 +
===Synchronous Remote Services===
  
When an OSGi service is actually invoked today, this is done by the caller calling a method on the service.  For example, consider a simple 'hello' service:
+
Whether a remote or local OSGi service, a service is invoked by calling a method on the service interface.  For example, consider a simple IHello service interface:
  
 
<source lang="java">package org.eclipse.ecf.examples.remoteservices.hello;
 
<source lang="java">package org.eclipse.ecf.examples.remoteservices.hello;
Line 16: Line 84:
 
</source>
 
</source>
  
Once a consumer of this service gets a valid service reference (i.e. through ServiceTracker, declarative services, getServiceReference or however), it can actually invoke/use the service via a method call on 'hello':
+
Once the consumer gets a valid service instance (i.e. through ServiceTracker, injection via declarative services (DS) or other injection framework, BundleContext.getServiceReference or other methods), it can actually invoke by calling the service interface method:
  
 
<source lang="java">
 
<source lang="java">
 
 
String response = helloService.hello("slewis");
 
String response = helloService.hello("slewis");
 
 
</source>
 
</source>
  
With OSGi remote services, this 'helloService' may actually be a proxy.  If invoked as above, under the covers, a proxy will marshall the method arguments (i.e. 'slewis' String in this case), and then communicate with the remote service host via some network (i.e. via some protocol).  If there is a result of the call, then it will then be unmarshalled and return to the caller thread.  With '''synchronous''' invocation the thread that calls 'hello' will block until this entire process is complete.
+
With remote services, the helloService instance will typically be a proxy.  When invoked/called, the proxy will marshall any arguments (i.e. 'slewis' String in this case), communicate with the remote service host via some protocol, the host will execute the associated code, and if there is a result, it will be unmarshalled and the result value returned to the caller thread.  As per typical java call-return semantics, the thread that calls 'hello' will block until this entire process is complete.
  
===Asynchronous Services===
+
===Asynchronous Remote Services===
  
ECF has just added the ability to declare asynchronous access to a remote method, so that the consumer can ge guaranteed that calling the service will not block indefinitelySo, for example, it's now possible to make a call to the hello service like this
+
ECF has added the ability for the service host to declare asynchronous access to a remote method, so that the consumer can be guaranteed that invoking/calling the service will not block.  For example, it's possible for a consumer to make a non-blocking call to the IHello service like this
  
 
<source lang="java">
 
<source lang="java">
 
+
Future<String> future = helloAsyncService.helloAsync("slewis");
helloAsyncService.helloAsync("slewis", new IAsyncCallback<String>() {
+
    public void onSuccess(String result) {
+
        // Do something with result
+
    }
+
    public void onFailure(Throwable t) {
+
        // Deal with network failure in appropriate manner
+
    }
+
});
+
 
</source>
 
</source>
  
With an ECF created asynchronous proxy, the consumer thread that calls helloAsyncService.helloAsync is guaranteed '''not''' to block, and the appropriate IAsyncCallback method will be executed when the remote call completes (with either success or failure). The callback is called (by an arbitrary ECF thread) sometime '''after''' the helloAsync method completes. 
+
With ECF asynchronous proxies (AP), the consumer thread that calls helloAsyncService.helloAsync is guaranteed '''not''' to block, and the returned Future&gt;String&lt;
 +
can be used to subsequently access the String result (or exception/failure).
  
A nice thing is that neither the consumer nor the host have to actually implement the asynchronous proxy.  The proxy is automatically constructed by the ECF remote services implementation when accessed by a consumer.
+
With ECF's AP support, neither the consumer nor the host have to implement this asynchronous behavior.  The proxy is automatically and dynamically constructed by the ECF remote services implementation on the consumer.
  
At this point, you might ask:  But how is the asynchronous proxy defined?  i.e. where does the '''helloAsyncService''' come from?
+
You might ask:  How is the asynchronous proxy defined?  i.e. where does the '''helloAsyncService''' come from?
  
The answer to this is that it is defined in a new/second service interface...called the asynchronous service interface that is related to the '''IHello''' service interface:
+
The answer is that it is defined in a new/second service interface...called the '''asynchronous service interface'''.  For example, here is how to declare an asynchronous service interface for the '''IHello''' service:
  
 
<source lang="java">
 
<source lang="java">
public interface IHelloAsync extends IAsyncRemoteServiceProxy {
 
  
public void helloAsync(String from, IAsyncCallback<String> callback);
+
public interface IHelloAsync {
 +
 
 +
public Future<String> helloAsync(String from);
 
}
 
}
 +
 
</source>
 
</source>
  
Notice that this asynchronous service interface declaration resembles the IHello service interface declaration, but differs from it in a couple of ways:
+
Notice that this declaration resembles the IHello service interface declaration, but differs from it in several important ways:
  
<ol><li>The name is <b>IHelloAsync</b> rather than <b>IHello</b>
+
<ol><li>The name of the asynchronous service interface is <b>IHelloAsync</b> rather than <b>IHello</b>
<li>It extends <b>IAsyncRemoteServiceProxy</b></li>
+
<li>The method name(s) is/are <b>helloAsync</b> rather than <b>hello</b></li>
<li>The method name is <b>helloAsync</b> rather than <b>hello</b></li>
+
<li>The return value is '''Future&lt;String&gt;''' (or with Java 8 '''CompletableFuture&lt;String&gt;''' see below) rather than String</li>
<li>The method arguments are String and IAsyncCallback rather than just String</li>
+
 
</ol>
 
</ol>
  
With ECF 3.3 remote services, when a proxy is created, '''iff''' an interface class with the name '''[fq service interface name]Async''' can be loaded and it extends IAsyncRemoteServiceProxy then the proxy will implement that interface.  So, for our hello example the proxy service reference will implement both the '''IHello''' and '''IHelloAsync''' methods, and the consumer can use '''either''' of the methods declared.  So, for example
+
With ECF remote services, on the consumer when a IHello proxy is created, the proxy will also automatically implement the IHelloAsync interface, and the consumer is able to use either the synchronous IHello service interface or the IHelloAsync asynchronous service interface.  So, for example
  
 
<source lang="java">
 
<source lang="java">
IHello helloService = ...get service reference via declarative services/injection, or ServiceTracker, or other...
+
 
 +
IHello helloService = ...get service via DS/injections, ServiceTracker or otherServiceTracker, or other...
 
if (helloService instanceof IHelloAsync) {
 
if (helloService instanceof IHelloAsync) {
 
   IHelloAsync helloServiceAsync = (IHelloAsync) helloService;
 
   IHelloAsync helloServiceAsync = (IHelloAsync) helloService;
   // call it asynchronously
+
   // call it asynchronously...no blocking
   helloAsyncService.helloAsync("slewis", new IAsyncCallback<String>() {
+
   Future<String> future = helloAsync("slewis");
      public void onSuccess(String result) {
+
  // do other things
          // Do something with result
+
  String result = future.get();
      }
+
  // do something with result...
      public void onFailure(Throwable t) {
+
          // Deal with network failure in appropriate manner
+
      }
+
  });
+
 
}
 
}
 +
 
</source>
 
</source>
  
This gives the consumer maximum flexibility in determining how a given invocation will occur (i.e. synchronously or asynchronously.  The caller can use the synchronous service proxy, or simply cast to the asynchronous service proxy.  Or both invocation methods can be used as desired.
+
or
  
All that's required to get this automatic creation of an asynchronous proxy is for the service interface creator to declare an *Async class using these rules:
+
<source lang="java">
  
<ol>
+
IHelloAsync helloAsyncService = ...get service via DS/injections, ServiceTracker or other
<li>Class name must be <b>[service interface classname]Async</b>
+
// call it asynchronously...no blocking
<ul><li>Example:  <b>IHello -> IHelloAsync</b></li></ul></li>
+
Future<String> future = helloAsync("slewis");
<li>The class must extend [http://www.eclipse.org/ecf/org.eclipse.ecf.docs/api/org/eclipse/ecf/remoteservice/IAsyncRemoteServiceProxy.html IAsyncRemoteServiceProxy]. 
+
// do other things
<ul><li>Example:  <b>IHelloAsync extends IAsyncRemoteServiceProxy</b></li></ul></li>
+
String result = future.get();
<li>For any methods that should be exposed to the consumer via the asynchronous proxy
+
// do something with result...
<ul>
+
<li>Name the method <b>[methodName]Async</b>
+
<ul><li>Example:  <b>hello -> helloAsync</b></li></ul></li>
+
<li>Add [http://www.eclipse.org/ecf/org.eclipse.ecf.docs/api/org/eclipse/ecf/remoteservice/IAsyncCallback.html IAsyncCallback] as the <b>last</b> argument 
+
<ul><li>Example:  <b>hello(String) -> helloAsync(String,IAsyncCallback)</b></li></ul></li>
+
<li>The return value should always be <b>void</b>, as there is not any synchronous result...the asynchronous call result is given in '''onSuccess''' callback</li>
+
</ul>
+
</li>
+
</ol>
+
  
====Example====
 
=====Original Service Interface=====
 
<source lang="java">package org.eclipse.ecf.examples.remoteservices.hello;
 
 
public interface IHello {
 
 
public String hello(String from);
 
 
}
 
 
</source>
 
</source>
=====Asynchronous Service Interface=====
 
<source lang="java">package org.eclipse.ecf.examples.remoteservices.hello;
 
import org.eclipse.ecf.remoteservice.IAsyncCallback;
 
import org.eclipse.ecf.remoteservice.IAsyncRemoteServiceProxy;
 
  
public interface IHelloAsync extends IAsyncRemoteServiceProxy {
+
Having both the synchronous (IHello) and asynchronous (IHelloAsync) service types gives the consumer flexibility in determining how a given remote invocation will occur (i.e. synchronously/blocking or asynchronously/non-blocking).  Synchronous, asynchronous, or both invocation methods may be used as appropriate for the consumer's use case(s) for the service.
  
public void helloAsync(String from, IAsyncCallback<String> callback);
+
===Java 8 CompletableFuture===
+
}
+
</source>
+
  
====A Second Example====
+
Java 8 introduces a new type of Future called '''[http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html java.util.concurrent.CompletableFuture]'''.  With ECF 3.8.1/[https://wiki.eclipse.org/Luna/Simultaneous_Release_Plan Luna] remote services, it's now possible for asynchronous service interfaces to return a CompletableFuture:
=====Original Service Interface=====
+
<source lang="java">package my.package;
+
  
public interface IFoo {
 
 
public String bar(URI uri, String something);
 
}
 
</source>
 
=====Asynchronous Service Interface=====
 
 
<source lang="java">package my.package;
 
<source lang="java">package my.package;
import org.eclipse.ecf.remoteservice.IAsyncCallback;
+
import java.util.concurrent.CompletableFuture;
import org.eclipse.ecf.remoteservice.IAsyncRemoteServiceProxy;
+
import org.eclipse.equinox.concurrent.future.IFuture;
+
  
public interface IFooAsync extends IAsyncRemoteServiceProxy {
+
public interface IHelloAsync {
  
     public void barAsync(URI uri, String something, IAsyncCallback callback);
+
     public CompletableFuture<String> helloAsync(String from);
    public IFuture barAsync(URI uri, String something);
+
 
}
 
}
 
</source>
 
</source>
  
===Future Results===
+
with CompletableFuture it's not necessary for the consumer to call Future.get (and possibly block) directly...but rather you can write succinct, simple, and '''guaranteed to be non-blocking''' calls such as:
 
+
[http://www.eclipse.org/ecf/org.eclipse.ecf.docs/api/org/eclipse/ecf/remoteservice/IAsyncCallback.html IAsyncCallback] is one approach to achieving asynchronous invocation of remote services.  Another that is also supported by ECF 3.3 remote services is [http://en.wikipedia.org/wiki/Futures_and_promises futures].  Future objects allow the consumer to perform operations '''in between''' initiating a remote call and receiving a result. 
+
So, for example, here is a use of a future result to initiate a remote call, perform some additional local operations and then get a result from the future.
+
  
 
<source lang="java">
 
<source lang="java">
// This will not block/return immediately
 
IFuture future = fooServiceAsync.barAsync(uri,something);
 
// ...do other local computation here...
 
// get result
 
String result = (String) future.get();
 
// ...
 
</source>
 
  
ECF 3.3 remote service supports asynchronous access via either [http://www.eclipse.org/ecf/org.eclipse.ecf.docs/api/org/eclipse/ecf/remoteservice/IAsyncCallback.html IAsyncCallback] or futures, or both.  To expose the '''IHello.hello''' method for asynchronous access via both callback and future approaches the '''IHelloAsync''' class would be declared like this
+
IHelloAsync helloAsync = ...get remote service via DS/injections, ServiceTracker or other
  
<source lang="java">package org.eclipse.ecf.examples.remoteservices.hello;
+
CompletableFuture<String> cf = helloAsync("slewis");
import org.eclipse.ecf.remoteservice.IAsyncCallback;
+
import org.eclipse.ecf.remoteservice.IAsyncRemoteServiceProxy;
+
import org.eclipse.equinox.concurrent.future.IFuture;
+
  
public interface IHelloAsync extends IAsyncRemoteServiceProxy {
+
cf.thenAccept((response) -> System.out.println("response to slewis helloAsync was: " + response));
  
    // async via callback
 
    public void helloAsync(String from, IAsyncCallback<String> callback);
 
    // async via future
 
    public IFuture helloAsync(String from);
 
 
}
 
 
</source>
 
</source>
  
So the rules shown above can be amended
+
The use of CompletableFuture has some very nice properties for service design, as well as some nice guarantees for the service consumer.  For remote service designers, they may declare and implement normal OSGi remote services (IHello) without having to concern themselves with the consumer's invocation style (synchronous or asynchronous).
  
<ol>
+
Remote service consumers can be assured that there will be no blocking in their code (via the guarantees of CompletableFuture). They may also use lambda to succinctly express arbitrary eventual execution...guaranteed to be done asynchronously when the response becomes available.
  <li>Class name must be <b>[service interface classname]Async</b></li>
+
 
  <li>The class must extend [http://www.eclipse.org/ecf/org.eclipse.ecf.docs/api/org/eclipse/ecf/remoteservice/IAsyncRemoteServiceProxy.html IAsyncRemoteServiceProxy]</li>
+
Note that with ECF remote services there is no need for either the host implementation or the consumer to implement the asynchronous service interface.  The dynamically constructed proxy will automatically have/expose the asynchronous service interface to the consumer, and this proxy will provide the underlying implementation.
  <li>For any methods that should be exposed to the consumer via the asynchronous proxy
+
    <ul>
+
      <li>Name the method <b>[methodName]Async</b>
+
        <li>For asynchronous callback
+
          <ol>
+
            <li>Add [http://www.eclipse.org/ecf/org.eclipse.ecf.docs/api/org/eclipse/ecf/remoteservice/IAsyncCallback.html IAsyncCallback] as the <b>last</b> argument</li>
+
            <li>The return type should be <b>void</b></li>
+
          </ol>
+
        </li>
+
        <li>For future results
+
          <ol>
+
            <li>The return type should be <b>org.eclipse.equinox.concurrent.future.IFuture</b>
+
              <ul>
+
                <li>Example:  <b>String hello(String) -> IFuture helloAsync(String)</b></li>
+
              </ul>
+
            </li>
+
          </ol>
+
        </li>
+
      </li>
+
    </ul>
+
  </li>
+
</ol>
+
  
===Full Example with Source===
+
Note another advantage...for both the service designer and consumer...is that there are no extra class dependencies in the synchronous or asynchronous service interfaces (e.g. IHello and IHelloAsync).  That is, there are no dependencies on OSGi, OSGi Remote Services, or ECF classes...meaning that if they wish to use/reuse these services in other runtime contexts (e.g. outside of OSGi, OSGi Remote Services, and/or ECF) in the service interfaces or in any client code.  The only dependencies are to other service types and java.util.concurrent.CompletableFuture or java.util.concurrent.Future.
  
A full, working example of the hello world remote service, including the IHello service declaration, the IHelloAsync asynchronous service declaration, as well as the server host impl, consumer, and code showing the use of both synchronous proxy asynchronous proxies (both callback and future-based) is available here: [[Getting Started with ECF's OSGi Remote Services Implementation]]
+
<references/>
  
 
{{ECF}}
 
{{ECF}}

Latest revision as of 23:15, 18 May 2014


Synchronous Remote Services

Whether a remote or local OSGi service, a service is invoked by calling a method on the service interface. For example, consider a simple IHello service interface:

package org.eclipse.ecf.examples.remoteservices.hello;
 
public interface IHello {
 
	public String hello(String from);
 
}

Once the consumer gets a valid service instance (i.e. through ServiceTracker, injection via declarative services (DS) or other injection framework, BundleContext.getServiceReference or other methods), it can actually invoke by calling the service interface method:

String response = helloService.hello("slewis");

With remote services, the helloService instance will typically be a proxy. When invoked/called, the proxy will marshall any arguments (i.e. 'slewis' String in this case), communicate with the remote service host via some protocol, the host will execute the associated code, and if there is a result, it will be unmarshalled and the result value returned to the caller thread. As per typical java call-return semantics, the thread that calls 'hello' will block until this entire process is complete.

Asynchronous Remote Services

ECF has added the ability for the service host to declare asynchronous access to a remote method, so that the consumer can be guaranteed that invoking/calling the service will not block. For example, it's possible for a consumer to make a non-blocking call to the IHello service like this

Future<String> future = helloAsyncService.helloAsync("slewis");

With ECF asynchronous proxies (AP), the consumer thread that calls helloAsyncService.helloAsync is guaranteed not to block, and the returned Future>String< can be used to subsequently access the String result (or exception/failure).

With ECF's AP support, neither the consumer nor the host have to implement this asynchronous behavior. The proxy is automatically and dynamically constructed by the ECF remote services implementation on the consumer.

You might ask: How is the asynchronous proxy defined? i.e. where does the helloAsyncService come from?

The answer is that it is defined in a new/second service interface...called the asynchronous service interface. For example, here is how to declare an asynchronous service interface for the IHello service:

public interface IHelloAsync {
 
	public Future<String> helloAsync(String from);
}

Notice that this declaration resembles the IHello service interface declaration, but differs from it in several important ways:

  1. The name of the asynchronous service interface is IHelloAsync rather than IHello
  2. The method name(s) is/are helloAsync rather than hello
  3. The return value is Future<String> (or with Java 8 CompletableFuture<String> see below) rather than String

With ECF remote services, on the consumer when a IHello proxy is created, the proxy will also automatically implement the IHelloAsync interface, and the consumer is able to use either the synchronous IHello service interface or the IHelloAsync asynchronous service interface. So, for example

IHello helloService = ...get service via DS/injections, ServiceTracker or otherServiceTracker, or other...
if (helloService instanceof IHelloAsync) {
   IHelloAsync helloServiceAsync = (IHelloAsync) helloService;
   // call it asynchronously...no blocking
   Future<String> future = helloAsync("slewis");
   // do other things
   String result = future.get();
   // do something with result...
}

or

IHelloAsync helloAsyncService = ...get service via DS/injections, ServiceTracker or other
// call it asynchronously...no blocking
Future<String> future = helloAsync("slewis");
// do other things
String result = future.get();
// do something with result...

Having both the synchronous (IHello) and asynchronous (IHelloAsync) service types gives the consumer flexibility in determining how a given remote invocation will occur (i.e. synchronously/blocking or asynchronously/non-blocking). Synchronous, asynchronous, or both invocation methods may be used as appropriate for the consumer's use case(s) for the service.

Java 8 CompletableFuture

Java 8 introduces a new type of Future called java.util.concurrent.CompletableFuture. With ECF 3.8.1/Luna remote services, it's now possible for asynchronous service interfaces to return a CompletableFuture:

package my.package;
import java.util.concurrent.CompletableFuture;
 
public interface IHelloAsync {
 
    public CompletableFuture<String> helloAsync(String from);
}

with CompletableFuture it's not necessary for the consumer to call Future.get (and possibly block) directly...but rather you can write succinct, simple, and guaranteed to be non-blocking calls such as:

IHelloAsync helloAsync = ...get remote service via DS/injections, ServiceTracker or other
 
CompletableFuture<String> cf = helloAsync("slewis");
 
cf.thenAccept((response) -> System.out.println("response to slewis helloAsync was: " + response));

The use of CompletableFuture has some very nice properties for service design, as well as some nice guarantees for the service consumer. For remote service designers, they may declare and implement normal OSGi remote services (IHello) without having to concern themselves with the consumer's invocation style (synchronous or asynchronous).

Remote service consumers can be assured that there will be no blocking in their code (via the guarantees of CompletableFuture). They may also use lambda to succinctly express arbitrary eventual execution...guaranteed to be done asynchronously when the response becomes available.

Note that with ECF remote services there is no need for either the host implementation or the consumer to implement the asynchronous service interface. The dynamically constructed proxy will automatically have/expose the asynchronous service interface to the consumer, and this proxy will provide the underlying implementation.

Note another advantage...for both the service designer and consumer...is that there are no extra class dependencies in the synchronous or asynchronous service interfaces (e.g. IHello and IHelloAsync). That is, there are no dependencies on OSGi, OSGi Remote Services, or ECF classes...meaning that if they wish to use/reuse these services in other runtime contexts (e.g. outside of OSGi, OSGi Remote Services, and/or ECF) in the service interfaces or in any client code. The only dependencies are to other service types and java.util.concurrent.CompletableFuture or java.util.concurrent.Future.


Eclipse Communication Framework
API
API DocumentationJavadocProviders
Development
Development GuidelinesIntegrators Guide

Back to the top