/*
 *  Copyright (c) 2011-2015 The original author or authors
 *  ------------------------------------------------------
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *       The Eclipse Public License is available at
 *       http://www.eclipse.org/legal/epl-v10.html
 *
 *       The Apache License v2.0 is available at
 *       http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 */

= Vert.x Service Proxy :toc: left When you compose a Vert.x application, you may want to isolate a functionality somewhere and make it available to the rest of your application. That's the main purpose of service proxies. It lets you expose a _service_ on the event bus, so, any other Vert.x component can consume it, as soon as they know the _address_ on which the service is published. A _service_ is described with a Java interface containing methods following the _async pattern_. Under the hood, messages are sent on the event bus to invoke the service and get the response back. But for ease of use, it generates a _proxy_ that you can invoke directly (using the API from the service interface). == Using Vert.x service proxies To *use* Vert.x Service Proxies, add the following dependency to the _dependencies_ section of your build descriptor: * Maven (in your `pom.xml`): [source,xml,subs="+attributes"] ---- ${maven.groupId} ${maven.artifactId} ${maven.version} ---- * Gradle (in your `build.gradle` file): [source,groovy,subs="+attributes"] ---- compile '${maven.groupId}:${maven.artifactId}:${maven.version}' ---- To *implement* service proxies, also add: * Maven (in your `pom.xml`): [source,xml,subs="+attributes"] ---- ${maven.groupId} vertx-codegen ${maven.version} provided ---- * Gradle (in your `build.gradle` file): [source,groovy,subs="+attributes"] ---- compileOnly '${maven.groupId}:vertx-codegen:${maven.version}' ---- Be aware that as the service proxy mechanism relies on code generation, so modifications to the _service interface_ require to re-compile the sources to regenerate the code. To generate the proxies in different languages, you will need to add the _language_ dependency such as `vertx-lang-groovy` for Groovy. == Introduction to service proxies Let's have a look at service proxies and why they can be useful. Let's imagine you have a _database service_ exposed on the event bus, you should do something like this: [source,$lang] ---- Examples.example1(Vertx) ---- When creating a service there's a certain amount of boilerplate code to listen on the event bus for incoming messages, route them to the appropriate method and return results on the event bus. With Vert.x service proxies, you can avoid writing all that boilerplate code and concentrate on writing your service. You write your service as a Java interface and annotate it with the `@ProxyGen` annotation, for example: [source,java] ---- @ProxyGen public interface SomeDatabaseService { // A couple of factory methods to create an instance and a proxy static SomeDatabaseService create(Vertx vertx) { return new SomeDatabaseServiceImpl(vertx); } static SomeDatabaseService createProxy(Vertx vertx, String address) { return new SomeDatabaseServiceVertxEBProxy(vertx, address); } // Actual service operations here... void save(String collection, JsonObject document, Handler> resultHandler); } ---- Given the interface, Vert.x will generate all the boilerplate code required to access your service over the event bus, and it will also generate a *client side proxy* for your service, so your clients can use a rich idiomatic API for your service instead of having to manually craft event bus messages to send. The client side proxy will work irrespective of where your service actually lives on the event bus (potentially on a different machine). That means you can interact with your service like this: [source,$lang] ---- Examples.example2(Vertx) ---- You can also combine `@ProxyGen` with language API code generation (`@VertxGen`) in order to create service stubs in any of the languages supported by Vert.x - this means you can write your service once in Java and interact with it through an idiomatic other language API irrespective of whether the service lives locally or is somewhere else on the event bus entirely. For this don't forget to add the dependency on your language in your build descriptor: [source, java] ---- @ProxyGen // Generate service proxies @VertxGen // Generate the clients public interface SomeDatabaseService { // ... } ---- == Async Interface To be used by the service-proxy generation, the _service interface_ must comply to a couple of rules. First it should follow the async pattern. To return a result, the method should declare a `Handler>` parameter. `ResultType` can be another proxy (and so a proxies can be factories for other proxies). Let's see an example: [source,java] ---- @ProxyGen public interface SomeDatabaseService { // A couple of factory methods to create an instance and a proxy static SomeDatabaseService create(Vertx vertx) { return new SomeDatabaseServiceImpl(vertx); } static SomeDatabaseService createProxy(Vertx vertx, String address) { return new SomeDatabaseServiceVertxEBProxy(vertx, address); } // A method notifying the completion without a result (void) void save(String collection, JsonObject document, Handler> result); // A method providing a result (a json object) void findOne(String collection, JsonObject query, Handler> result); // Create a connection void createConnection(String shoeSize, Handler> resultHandler); } ---- with: [source,java] ---- @ProxyGen @VertxGen public interface MyDatabaseConnection { void insert(JsonObject someData); void commit(Handler> resultHandler); @ProxyClose void close(); } ---- You can also declare that a particular method unregisters the proxy by annotating it with the `@ProxyClose` annotation. The proxy instance is disposed when this method is called. More constraints on the _service interfaces_ are described below. == Code generation Service annotated with `@ProxyGen` annotation trigger the generation of the service helper classes: - The service proxy: a compile time generated proxy that uses the `EventBus` to interact with the service via messages - The service handler: a compile time generated `EventBus` handler that reacts to events sent by the proxy Generated proxies and handlers are named after the service class, for example if the service is named `MyService` the handler is called `MyServiceProxyHandler` and the proxy is called `MyServiceEBProxy`. In addition Vert.x Core provides a generator creating data object converters to ease data object usage in service proxies. Such converter provides a basis for the `JsonObject` constructor and the `toJson()` method that are necessary for using data objects in service proxies. The _codegen_ annotation processor generates these classes at compilation time. It is a feature of the Java compiler so _no extra step_ is required, it is just a matter of configuring correctly your build: Just add the `io.vertx:vertx-codegen:processor` and `io.vertx:vertx-service-proxy` dependencies to your build. Here a configuration example for Maven: [source,xml] ---- io.vertx vertx-codegen ${maven.version} processor io.vertx vertx-service-proxy ${maven.version} ---- This feature can also be used in Gradle: [source] ---- compile "io.vertx:vertx-codegen:${maven.version}:processor" compile "io.vertx:vertx-service-proxy:${maven.version}" ---- IDE provides usually support for annotation processors. The codegen `processor` classifier adds to the jar the automatic configuration of the service proxy annotation processor via the `META-INF/services` plugin mechanism. If you want you can use it too with the regular jar but you need then to declare the annotation processor explicitly, for instance in Maven: [source,xml] ---- maven-compiler-plugin io.vertx.codegen.CodeGenProcessor ---- == Exposing your service Once you have your _service interface_, compile the source to generate the stub and proxies. Then, you need some code to "register" your service on the event bus: [source, java] ---- Examples.register(Vertx) ---- This can be done in a verticle, or anywhere in your code. Once registered, the service becomes accessible. If you are running your application on a cluster, the service is available from any host. To withdraw your service, use the ServiceBinder.unregister(MessageConsumer<JsonObject>) method: [source, java] ---- Examples.unregister(Vertx) ---- == Proxy creation Now that the service is exposed, you probably want to consume it. For this, you need to create a proxy. The proxy can be created using the `ProxyHelper` class: [source, java] ---- Examples.proxyCreation(Vertx, DeliveryOptions) ---- The second method takes an instance of DeliveryOptions where you can configure the message delivery (such as the timeout). Alternatively, you can use the generated proxy class. The proxy class name is the _service interface_ class name followed by `VertxEBProxy`. For instance, if your _service interface_ is named `SomeDatabaseService`, the proxy class is named `SomeDatabaseServiceVertxEBProxy`. Generally, _service interface_ contains a `createProxy` static method to create the proxy. But this is not required: [source,java] ---- @ProxyGen public interface SomeDatabaseService { // Method to create the proxy. static SomeDatabaseService createProxy(Vertx vertx, String address) { return new SomeDatabaseServiceVertxEBProxy(vertx, address); } // ... } ---- == Error Handling Service methods may return errors to the client by passing a failed `Future` containing a ServiceException instance to the method's `Handler`. A `ServiceException` contains an `int` failure code, a message, and an optional `JsonObject` containing any extra information deemed important to return to the caller. For convenience, the ServiceException.fail factory method can be used to create an instance of `ServiceException` already wrapped in a failed `Future`. For example: [source,java] ---- public class SomeDatabaseServiceImpl implements SomeDatabaseService { private static final BAD_SHOE_SIZE = 42; private static final CONNECTION_FAILED = 43; // Create a connection void createConnection(String shoeSize, Handler> resultHandler) { if (!shoeSize.equals("9")) { resultHandler.handle(ServiceException.fail(BAD_SHOE_SIZE, "The shoe size must be 9!", new JsonObject().put("shoeSize", shoeSize)); } else { doDbConnection(result -> { if (result.succeeded()) { resultHandler.handle(Future.succeededFuture(result.result())); } else { resultHandler.handle(ServiceException.fail(CONNECTION_FAILED, result.cause().getMessage())); } }); } } } ---- The client side can then check if the `Throwable` it receives from a failed `AsyncResult` is a `ServiceException`, and if so, check the specific error code inside. It can use this information to differentiate business logic errors from system errors (like the service not being registered with the Event Bus), and to determine exactly which business logic error occurred. [source,java] ---- public void foo(String shoeSize, Handler> handler) { SomeDatabaseService service = SomeDatabaseService.createProxy(vertx, SERVICE_ADDRESS); service.createConnection("8", result -> { if (result.succeeded()) { // Do success stuff. } else { if (result.cause() instanceof ServiceException) { ServiceException exc = (ServiceException) result.cause(); if (exc.failureCode() == SomeDatabaseServiceImpl.BAD_SHOE_SIZE) { handler.handle(Future.failedFuture( new InvalidInputError("You provided a bad shoe size: " + exc.getDebugInfo().getString("shoeSize")) )); } else if (exc.failureCode() == SomeDatabaseServiceImpl.CONNECTION) { handler.handle(Future.failedFuture( new ConnectionError("Failed to connect to the DB"))); } } else { // Must be a system error (e.g. No service registered for the proxy) handler.handle(Future.failedFuture( new SystemError("An unexpected error occurred: + " result.cause().getMessage()) )); } } } } ---- If desired, service implementations may also return a sub-class of `ServiceException`, as long as a default `MessageCodec` is registered for it . For example, given the following `ServiceException` sub-class: [source,java] ---- class ShoeSizeException extends ServiceException { public static final BAD_SHOE_SIZE_ERROR = 42; private final String shoeSize; public ShoeSizeException(String shoeSize) { super(BAD_SHOE_SIZE_ERROR, "In invalid shoe size was received: " + shoeSize); this.shoeSize = shoeSize; } public String getShoeSize() { return extra; } public static AsyncResult fail(int failureCode, String message, String shoeSize) { return Future.failedFuture(new MyServiceException(failureCode, message, shoeSize)); } } ---- As long as a default `MessageCodec` is registered, the Service implementation can return the custom exception directly to the caller: [source,java] ---- public class SomeDatabaseServiceImpl implements SomeDatabaseService { public SomeDataBaseServiceImpl(Vertx vertx) { // Register on the service side. If using a local event bus, this is all // that's required, since the proxy side will share the same Vertx instance. SomeDatabaseService service = SomeDatabaseService.createProxy(vertx, SERVICE_ADDRESS); vertx.eventBus().registerDefaultCodec(ShoeSizeException.class, new ShoeSizeExceptionMessageCodec()); } // Create a connection void createConnection(String shoeSize, Handler> resultHandler) { if (!shoeSize.equals("9")) { resultHandler.handle(ShoeSizeException.fail(shoeSize)); } else { // Create the connection here resultHandler.Handle(Future.succeededFuture(myDbConnection)); } } } ---- Finally, the client can now check for the custom exception: [source,java] ---- public void foo(String shoeSize, Handler> handler) { // If this code is running on a different node in the cluster, the // ShoeSizeExceptionMessageCodec will need to be registered with the // Vertx instance on this node, too. SomeDatabaseService service = SomeDatabaseService.createProxy(vertx, SERVICE_ADDRESS); service.createConnection("8", result -> { if (result.succeeded()) { // Do success stuff. } else { if (result.cause() instanceof ShoeSizeException) { ShoeSizeException exc = (ShoeSizeException) result.cause(); handler.handle(Future.failedFuture( new InvalidInputError("You provided a bad shoe size: " + exc.getShoeSize()))); } else { // Must be a system error (e.g. No service registered for the proxy) handler.handle(Future.failedFuture( new SystemError("An unexpected error occurred: + " result.cause().getMessage()) )); } } } } ---- Note that if you're clustering `Vertx` instances, you'll need to register the custom Exception's `MessageCodec` with each `Vertx` instance in the cluster. == Restrictions for service interface There are restrictions on the types and return values that can be used in a service method so that these are easy to marshall over event bus messages and so they can be used asynchronously. They are: === Return types Must be one of: * `void` * `@Fluent` and return reference to the service (`this`): [source,java] ---- @Fluent SomeDatabaseService doSomething(); ---- This is because methods must not block and it's not possible to return a result immediately without blocking if the service is remote. === Parameter types Let `JSON` = `JsonObject | JsonArray` Let `PRIMITIVE` = Any primitive type or boxed primitive type Parameters can be any of: * `JSON` * `PRIMITIVE` * `List` * `List` * `Set` * `Set` * `Map` * `Map` * Any _Enum_ type * Any class annotated with `@DataObject` If an asynchronous result is required a last parameter of type `Handler>` can be provided. `R` can be any of: * `JSON` * `PRIMITIVE` * `List` * `List` * `Set` * `Set` * Any _Enum_ type * Any class annotated with `@DataObject` * Another proxy === Overloaded methods There must be no overloaded service methods. (_i.e._ more than one with the same name, regardless the signature). == Convention for invoking services over the event bus (without proxies) Service Proxies assume that event bus messages follow a certain format so they can be used to invoke services. Of course, you don't *have to* use client proxies to access remote service if you don't want to. It's perfectly acceptable to interact with them by just sending messages over the event bus. In order for services to be interacted with a consistent way the following message formats *must be used* for any Vert.x services. The format is very simple: * There should be a header called `action` which gives the name of the action to perform. * The body of the message should be a `JsonObject`, there should be one field in the object for each argument needed by the action. For example to invoke an action called `save` which expects a String collection and a JsonObject document: ---- Headers: "action": "save" Body: { "collection", "mycollection", "document", { "name": "tim" } } ---- The above convention should be used whether or not service proxies are used to create services, as it allows services to be interacted with consistently. In the case where service proxies are used the "action" value should map to the name of an action method in the service interface and each `[key, value]` in the body should map to a `[arg_name, arg_value]` in the action method. For return values the service should use the `message.reply(...)` method to send back a return value - this can be of any type supported by the event bus. To signal a failure the method `message.fail(...)` should be used. If you are using service proxies the generated code will handle this for you automatically.
/** * = Vert.x Service Proxy * :toc: left * * When you compose a Vert.x application, you may want to isolate a functionality somewhere and make it available to * the rest of your application. That's the main purpose of service proxies. It lets you expose a _service_ on the * event bus, so, any other Vert.x component can consume it, as soon as they know the _address_ on which the service * is published. * * A _service_ is described with a Java interface containing methods following the _async pattern_. Under the hood, * messages are sent on the event bus to invoke the service and get the response back. But for ease of use, * it generates a _proxy_ that you can invoke directly (using the API from the service interface). * * * == Using Vert.x service proxies * * To *use* Vert.x Service Proxies, add the following dependency to the _dependencies_ section of * your build descriptor: * * * Maven (in your `pom.xml`): * * [source,xml,subs="+attributes"] * ---- * <dependency> * <groupId>${maven.groupId}</groupId> * <artifactId>${maven.artifactId}</artifactId> * <version>${maven.version}</version> * </dependency> * ---- * * * Gradle (in your `build.gradle` file): * * [source,groovy,subs="+attributes"] * ---- * compile '${maven.groupId}:${maven.artifactId}:${maven.version}' * ---- * * To *implement* service proxies, also add: * * * Maven (in your `pom.xml`): * * [source,xml,subs="+attributes"] * ---- * <dependency> * <groupId>${maven.groupId}</groupId> * <artifactId>vertx-codegen</artifactId> * <version>${maven.version}</version> * <scope>provided</scope> * </dependency> * ---- * * * Gradle (in your `build.gradle` file): * * [source,groovy,subs="+attributes"] * ---- * compileOnly '${maven.groupId}:vertx-codegen:${maven.version}' * ---- * * Be aware that as the service proxy mechanism relies on code generation, so modifications to the _service interface_ * require to re-compile the sources to regenerate the code. * * To generate the proxies in different languages, you will need to add the _language_ dependency such as * `vertx-lang-groovy` for Groovy. * * == Introduction to service proxies * * Let's have a look at service proxies and why they can be useful. Let's imagine you have a _database service_ * exposed on the event bus, you should do something like this: * * [source,$lang] * ---- * {@link examples.Examples#example1(io.vertx.core.Vertx)} * ---- * * When creating a service there's a certain amount of boilerplate code to listen on the event bus for incoming * messages, route them to the appropriate method and return results on the event bus. * * With Vert.x service proxies, you can avoid writing all that boilerplate code and concentrate on writing your service. * * You write your service as a Java interface and annotate it with the `@ProxyGen` annotation, for example: * * [source,java] * ---- * &#64;ProxyGen * public interface SomeDatabaseService { * * // A couple of factory methods to create an instance and a proxy * static SomeDatabaseService create(Vertx vertx) { * return new SomeDatabaseServiceImpl(vertx); * } * * static SomeDatabaseService createProxy(Vertx vertx, * String address) { * return new SomeDatabaseServiceVertxEBProxy(vertx, address); * } * * // Actual service operations here... * void save(String collection, JsonObject document, * Handler<AsyncResult<Void>> resultHandler); * } * ---- * * Given the interface, Vert.x will generate all the boilerplate code required to access your service over the event * bus, and it will also generate a *client side proxy* for your service, so your clients can use a rich idiomatic * API for your service instead of having to manually craft event bus messages to send. The client side proxy will * work irrespective of where your service actually lives on the event bus (potentially on a different machine). * * That means you can interact with your service like this: * * [source,$lang] * ---- * {@link examples.Examples#example2(io.vertx.core.Vertx)} * ---- * * You can also combine `@ProxyGen` with language API code generation (`@VertxGen`) in order to create service stubs * in any of the languages supported by Vert.x - this means you can write your service once in Java and interact with it * through an idiomatic other language API irrespective of whether the service lives locally or is somewhere else on * the event bus entirely. For this don't forget to add the dependency on your language in your build descriptor: * * [source, java] * ---- * &#64;ProxyGen // Generate service proxies * &#64;VertxGen // Generate the clients * public interface SomeDatabaseService { * // ... * } * ---- * * == Async Interface * * To be used by the service-proxy generation, the _service interface_ must comply to a couple of rules. First it * should follow the async pattern. To return a result, the method should declare a `Handler<AsyncResult<ResultType>>` parameter. * `ResultType` can be another proxy (and so a proxies can be factories for other proxies). * * Let's see an example: * * [source,java] * ---- * &#64;ProxyGen * public interface SomeDatabaseService { * * // A couple of factory methods to create an instance and a proxy * * static SomeDatabaseService create(Vertx vertx) { * return new SomeDatabaseServiceImpl(vertx); * } * * static SomeDatabaseService createProxy(Vertx vertx, String address) { * return new SomeDatabaseServiceVertxEBProxy(vertx, address); * } * * // A method notifying the completion without a result (void) * void save(String collection, JsonObject document, * Handler<AsyncResult<Void>> result); * * // A method providing a result (a json object) * void findOne(String collection, JsonObject query, * Handler<AsyncResult<JsonObject>> result); * * // Create a connection * void createConnection(String shoeSize, * Handler<AsyncResult<MyDatabaseConnection>> resultHandler); * * } * ---- * * with: * * [source,java] * ---- * &#64;ProxyGen * &#64;VertxGen * public interface MyDatabaseConnection { * * void insert(JsonObject someData); * * void commit(Handler<AsyncResult<Void>> resultHandler); * * &#64;ProxyClose * void close(); * } * ---- * * You can also declare that a particular method unregisters the proxy by annotating it with the `@ProxyClose` * annotation. The proxy instance is disposed when this method is called. * * More constraints on the _service interfaces_ are described below. * * == Code generation * * Service annotated with `@ProxyGen` annotation trigger the generation of the service helper classes: * * - The service proxy: a compile time generated proxy that uses the `EventBus` to interact with the service via messages * - The service handler: a compile time generated `EventBus` handler that reacts to events sent by the proxy * * Generated proxies and handlers are named after the service class, for example if the service is named `MyService` * the handler is called `MyServiceProxyHandler` and the proxy is called `MyServiceEBProxy`. * * In addition Vert.x Core provides a generator creating data object converters to ease data object usage in * service proxies. Such converter provides a basis for the `JsonObject` constructor and the `toJson()` method * that are necessary for using data objects in service proxies. * * The _codegen_ annotation processor generates these classes at compilation time. It is a feature of the Java * compiler so _no extra step_ is required, it is just a matter of configuring correctly your build: * * Just add the `io.vertx:vertx-codegen:processor` and `io.vertx:vertx-service-proxy` * dependencies to your build. * * Here a configuration example for Maven: * * [source,xml] * ---- * <dependency> * <groupId>io.vertx</groupId> * <artifactId>vertx-codegen</artifactId> * <version>${maven.version}</version> * <classifier>processor</classifier> * </dependency> * <dependency> * <groupId>io.vertx</groupId> * <artifactId>vertx-service-proxy</artifactId> * <version>${maven.version}</version> * </dependency> * ---- * * This feature can also be used in Gradle: * * [source] * ---- * compile "io.vertx:vertx-codegen:${maven.version}:processor" * compile "io.vertx:vertx-service-proxy:${maven.version}" * ---- * * IDE provides usually support for annotation processors. * * The codegen `processor` classifier adds to the jar the automatic configuration of the service proxy annotation processor * via the `META-INF/services` plugin mechanism. * * If you want you can use it too with the regular jar but you need then to declare the annotation processor * explicitly, for instance in Maven: * * [source,xml] * ---- * <plugin> * <artifactId>maven-compiler-plugin</artifactId> * <configuration> * <annotationProcessors> * <annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor> * </annotationProcessors> * </configuration> * </plugin> * ---- * * == Exposing your service * * Once you have your _service interface_, compile the source to generate the stub and proxies. Then, you need some * code to "register" your service on the event bus: * * [source, java] * ---- * {@link examples.Examples#register(io.vertx.core.Vertx)} * ---- * * This can be done in a verticle, or anywhere in your code. * * Once registered, the service becomes accessible. If you are running your application on a cluster, the service is * available from any host. * * To withdraw your service, use the {@link io.vertx.serviceproxy.ServiceBinder#unregister(io.vertx.core.eventbus.MessageConsumer)} * method: * * [source, java] * ---- * {@link examples.Examples#unregister(io.vertx.core.Vertx)} * ---- * * == Proxy creation * * Now that the service is exposed, you probably want to consume it. For this, you need to create a proxy. The proxy * can be created using the `ProxyHelper` class: * * [source, java] * ---- * {@link examples.Examples#proxyCreation(io.vertx.core.Vertx, io.vertx.core.eventbus.DeliveryOptions)} * ---- * * The second method takes an instance of {@link io.vertx.core.eventbus.DeliveryOptions} where you can configure the * message delivery (such as the timeout). * * Alternatively, you can use the generated proxy class. The proxy class name is the _service interface_ class name * followed by `VertxEBProxy`. For instance, if your _service interface_ is named `SomeDatabaseService`, the proxy * class is named `SomeDatabaseServiceVertxEBProxy`. * * Generally, _service interface_ contains a `createProxy` static method to create the proxy. But this is not required: * * [source,java] * ---- * &#64;ProxyGen * public interface SomeDatabaseService { * * // Method to create the proxy. * static SomeDatabaseService createProxy(Vertx vertx, String address) { * return new SomeDatabaseServiceVertxEBProxy(vertx, address); * } * * // ... *} * ---- * * == Error Handling * * Service methods may return errors to the client by passing a failed `Future` containing a {@link io.vertx.serviceproxy.ServiceException} * instance to the method's `Handler`. A `ServiceException` contains an `int` failure code, a message, and an optional * `JsonObject` containing any extra information deemed important to return to the caller. For convenience, the * {@link io.vertx.serviceproxy.ServiceException#fail} factory method can be used to create an instance of * `ServiceException` already wrapped in a failed `Future`. For example: * * [source,java] * ---- * public class SomeDatabaseServiceImpl implements SomeDatabaseService { * private static final BAD_SHOE_SIZE = 42; * private static final CONNECTION_FAILED = 43; * * // Create a connection * void createConnection(String shoeSize, Handler<AsyncResult<MyDatabaseConnection>> resultHandler) { * if (!shoeSize.equals("9")) { * resultHandler.handle(ServiceException.fail(BAD_SHOE_SIZE, "The shoe size must be 9!", * new JsonObject().put("shoeSize", shoeSize)); * } else { * doDbConnection(result -> { * if (result.succeeded()) { * resultHandler.handle(Future.succeededFuture(result.result())); * } else { * resultHandler.handle(ServiceException.fail(CONNECTION_FAILED, result.cause().getMessage())); * } * }); * } * } * } * ---- * * The client side can then check if the `Throwable` it receives from a failed `AsyncResult` is a `ServiceException`, * and if so, check the specific error code inside. It can use this information to differentiate business logic * errors from system errors (like the service not being registered with the Event Bus), and to determine exactly * which business logic error occurred. * * [source,java] * ---- * public void foo(String shoeSize, Handler<AsyncResult<JsonObject>> handler) { * SomeDatabaseService service = SomeDatabaseService.createProxy(vertx, SERVICE_ADDRESS); * service.createConnection("8", result -> { * if (result.succeeded()) { * // Do success stuff. * } else { * if (result.cause() instanceof ServiceException) { * ServiceException exc = (ServiceException) result.cause(); * if (exc.failureCode() == SomeDatabaseServiceImpl.BAD_SHOE_SIZE) { * handler.handle(Future.failedFuture( * new InvalidInputError("You provided a bad shoe size: " + * exc.getDebugInfo().getString("shoeSize")) * )); * } else if (exc.failureCode() == SomeDatabaseServiceImpl.CONNECTION) { * handler.handle(Future.failedFuture( * new ConnectionError("Failed to connect to the DB"))); * } * } else { * // Must be a system error (e.g. No service registered for the proxy) * handler.handle(Future.failedFuture( * new SystemError("An unexpected error occurred: + " result.cause().getMessage()) * )); * } * } * } * } * ---- * * If desired, service implementations may also return a sub-class of `ServiceException`, as long as a * default `MessageCodec` is registered for it . For example, given the following `ServiceException` sub-class: * * [source,java] * ---- * class ShoeSizeException extends ServiceException { * public static final BAD_SHOE_SIZE_ERROR = 42; * * private final String shoeSize; * * public ShoeSizeException(String shoeSize) { * super(BAD_SHOE_SIZE_ERROR, "In invalid shoe size was received: " + shoeSize); * this.shoeSize = shoeSize; * } * * public String getShoeSize() { * return extra; * } * * public static <T> AsyncResult<T> fail(int failureCode, String message, String shoeSize) { * return Future.failedFuture(new MyServiceException(failureCode, message, shoeSize)); * } * } * ---- * * As long as a default `MessageCodec` is registered, the Service implementation can return the custom * exception directly to the caller: * * [source,java] * ---- * public class SomeDatabaseServiceImpl implements SomeDatabaseService { * public SomeDataBaseServiceImpl(Vertx vertx) { * // Register on the service side. If using a local event bus, this is all * // that's required, since the proxy side will share the same Vertx instance. * SomeDatabaseService service = SomeDatabaseService.createProxy(vertx, SERVICE_ADDRESS); * vertx.eventBus().registerDefaultCodec(ShoeSizeException.class, * new ShoeSizeExceptionMessageCodec()); * } * * // Create a connection * void createConnection(String shoeSize, Handler<AsyncResult<MyDatabaseConnection>> resultHandler) { * if (!shoeSize.equals("9")) { * resultHandler.handle(ShoeSizeException.fail(shoeSize)); * } else { * // Create the connection here * resultHandler.Handle(Future.succeededFuture(myDbConnection)); * } * } * } * ---- * Finally, the client can now check for the custom exception: * * [source,java] * ---- * public void foo(String shoeSize, Handler<AsyncResult<JsonObject>> handler) { * // If this code is running on a different node in the cluster, the * // ShoeSizeExceptionMessageCodec will need to be registered with the * // Vertx instance on this node, too. * SomeDatabaseService service = SomeDatabaseService.createProxy(vertx, SERVICE_ADDRESS); * service.createConnection("8", result -> { * if (result.succeeded()) { * // Do success stuff. * } else { * if (result.cause() instanceof ShoeSizeException) { * ShoeSizeException exc = (ShoeSizeException) result.cause(); * handler.handle(Future.failedFuture( * new InvalidInputError("You provided a bad shoe size: " + exc.getShoeSize()))); * } else { * // Must be a system error (e.g. No service registered for the proxy) * handler.handle(Future.failedFuture( * new SystemError("An unexpected error occurred: + " result.cause().getMessage()) * )); * } * } * } * } * ---- * * Note that if you're clustering `Vertx` instances, you'll need to register the custom Exception's `MessageCodec` * with each `Vertx` instance in the cluster. * * == Restrictions for service interface * * There are restrictions on the types and return values that can be used in a service method so that these are easy to * marshall over event bus messages and so they can be used asynchronously. They are: * * === Return types * * Must be one of: * * * `void` * * `@Fluent` and return reference to the service (`this`): * * [source,java] * ---- * &#64;Fluent * SomeDatabaseService doSomething(); * ---- * * This is because methods must not block and it's not possible to return a result immediately without blocking if * the service is remote. * * === Parameter types * * Let `JSON` = `JsonObject | JsonArray` * Let `PRIMITIVE` = Any primitive type or boxed primitive type * * Parameters can be any of: * * * `JSON` * * `PRIMITIVE` * * `List<JSON>` * * `List<PRIMITIVE>` * * `Set<JSON>` * * `Set<PRIMITIVE>` * * `Map<String, JSON>` * * `Map<String, PRIMITIVE>` * * Any _Enum_ type * * Any class annotated with `@DataObject` * * If an asynchronous result is required a last parameter of type `Handler<AsyncResult<R>>` can be provided. * * `R` can be any of: * * * `JSON` * * `PRIMITIVE` * * `List<JSON>` * * `List<PRIMITIVE>` * * `Set<JSON>` * * `Set<PRIMITIVE>` * * Any _Enum_ type * * Any class annotated with `@DataObject` * * Another proxy * * === Overloaded methods * * There must be no overloaded service methods. (_i.e._ more than one with the same name, regardless the signature). * * == Convention for invoking services over the event bus (without proxies) * * Service Proxies assume that event bus messages follow a certain format so they can be used to invoke services. * * Of course, you don't *have to* use client proxies to access remote service if you don't want to. It's perfectly acceptable * to interact with them by just sending messages over the event bus. * * In order for services to be interacted with a consistent way the following message formats *must be used* for any * Vert.x services. * * The format is very simple: * * * There should be a header called `action` which gives the name of the action to perform. * * The body of the message should be a `JsonObject`, there should be one field in the object for each argument needed by the action. * * For example to invoke an action called `save` which expects a String collection and a JsonObject document: * * ---- * Headers: * "action": "save" * Body: * { * "collection", "mycollection", * "document", { * "name": "tim" * } * } * ---- * * The above convention should be used whether or not service proxies are used to create services, as it allows services * to be interacted with consistently. * * In the case where service proxies are used the "action" value should map to the name of an action method in the * service interface and each `[key, value]` in the body should map to a `[arg_name, arg_value]` in the action method. * * For return values the service should use the `message.reply(...)` method to send back a return value - this can be of * any type supported by the event bus. To signal a failure the method `message.fail(...)` should be used. * * If you are using service proxies the generated code will handle this for you automatically. * */
@ModuleGen(name = "vertx-service-proxy", groupPackage = "io.vertx") @Document(fileName = "index.adoc") package io.vertx.serviceproxy; import io.vertx.codegen.annotations.ModuleGen; import io.vertx.docgen.Document;