/* * 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"] * ---- * <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] * ---- * @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] * ---- * @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<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] * ---- * @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] * ---- * @ProxyGen * @VertxGen * public interface MyDatabaseConnection { * * void insert(JsonObject someData); * * void commit(Handler<AsyncResult<Void>> 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-service-proxy:processor` dependency to your build. * * Here a configuration example for Maven: * * [source,xml] * ---- * <dependency> * <groupId>io.vertx</groupId> * <artifactId>vertx-service-proxy</artifactId> * <version>${maven.version}</version> * <classifier>processor</classifier> * </dependency> * ---- * * This feature can also be used in Gradle: * * [source] * ---- * compile "io.vertx:vertx-service-proxy:${maven.version}:processor" * ---- * * IDE provides usually support for annotation processors. * * The `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.serviceproxy.ServiceProxyProcessor</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.ProxyHelper#unregisterService(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] * ---- * @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] * ---- * @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;