/*
* 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;