By Gerald Mücke | September 15, 2017

Modular Router Design for Vert.x Microservices
When developing micro services with the Vert.x framework I stumbled more than once over the question how to organize Verticles and achieve a modular design. Vert.x is unopinionated allowing various ways of accomplishing this. In this article I’d like to discuss two options for building modular services.
In Vert.x the endpoints of a micro-service are defined as routes of a router which is bound to a http server. A http server may share a TCP port which allows to run multiple instances of a Verticle listening on the same port. A router however is not shared. So when is set as a handler on a http server listening on a shared port, only the first router receives requests, while the other receive nothing. Thus, deploying Verticles with different routers on the same port won’t make all endpoints available. In my research I found out this is not an uncommon problem to solve (see here,here or here).
There are two options to solve this problem
- a single Verticle that mounts various subrouters, provided by various classes
- sharing a single router instance across various Verticles
Single Verticle
The basic idea is, that only a single Verticle creates the HTTP server and a router.
Endpoints of the various services are added by mounting sub-router according to Vert.x documentation.
The sub-routers are defined by classes that define the set of endpoints which can be maintained by different
teams. In order to achieve a dynamic composition of services the ServiceLoader
mechanism can be used so that
additional services are mounted automatically if they are present in the classpath. Nevertheless, it is not
possible to deploy additional functionality at runtime.
First, we define an interface which services have to implement. The interface defines the mountpoint for the subrouter and the actual router for the endpoints
public interface ServiceEndpoint {
String mountPoint();
Router router(Vertx vertx);
}
Further, we create an implementation for the ServiceEndpoint
public class OneService implements ServiceEndpoint {
@Override
public String mountPoint() {
return "/1";
}
@Override
public Router router(Vertx vertx) {
Router router = Router.router(vertx);
router.get("/one").handler(ctx -> ctx.response().end("One OK"));
return router;
}
}
In the META-INF/services
we create a file with the fully qualified name of the ServiceEndpoint interface, i.e.
io.devcon5.vertx.examples.ServiceEndpoint
containing the fully qualified name of one or more implementing
classes, i.e. io.devcon5.vertx.examples.OneService
. These services can be defined in the same or other
jars, the ServiceLoader mechanism is able to collect all implementor classes.
We load all the implementors during initialization of the http server and mount their routers to the central router.
public class ServerVerticle extends AbstractVerticle {
@Override
public void start(final Future<Void> startFuture) throws Exception {
//create a service loader for the ServiceEndpoints
ServiceLoader<ServiceEndpoint> loader = ServiceLoader.load(ServiceEndpoint.class);
//iterate over all endpoints and mount all their endpoints to a single router
Router main = StreamSupport.stream(loader.spliterator(), false)
.collect(() -> Router.router(vertx), //the main router
(r, s) -> r.mountSubRouter(s.mountPoint(), s.router(vertx)),
(r1, r2) -> {});
//bind the main router to the http server
vertx.createHttpServer().requestHandler(main::accept).listen(8080, res -> {
if (res.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(res.cause());
}
});
}
}
Now, you can dynamically put together services using a single server. The ServerVerticle can be deployed multiple times to achieve scalability. This solution is probably more “vertxy” than the shared router solution, because it doesn’t rely on sharing an instance between threads, but the service endpoints can not be used and deployed as separate Verticles.
The full example can be found on GitHub
Shared Router
The core idea is to use a single main router which is shared between Verticles to mount their sub-routers to it.
The Vert.x’ RouterImpl
class is thread safe, so it shouldn’t be a problem to share the instance between
different Verticles potentially running on different threads.
We need a wrapper or extension for the Router to make it Shareable
. The router can then be shared between
Verticles but not accross a cluster. We also define a method to create this shareable router which ensures
only one router is created, even if multiple Verticle try to create a new router.
public class ShareableRouter extends RouterImpl implements Shareable {
public static Router router(Vertx vertx) {
return (Router) vertx.sharedData()
.getLocalMap("router")
.computeIfAbsent("main", n -> new ShareableRouter(vertx));
}
ShareableRouter(final Vertx vertx) {
super(vertx);
}
}
Now each Verticle that defines a self-contained set of endpoints, including an HttpServer can mount their routers as sub-router to this shared router or define direct routes themselves - which is not recommendable due to potential endpoint collisions.
public class HttpOneVerticle extends AbstractVerticle {
@Override
public void start(final Future<Void> startFuture) throws Exception {
//create a router defining the endpoints of the service
final Router router = Router.router(vertx);
router.get("/one").handler(ctx -> ctx.response().end("OK one"));
//mount the router as subrouter to the shared router
final Router main = ShareableRouter.router(vertx).mountSubRouter("/1", router);
vertx.createHttpServer().requestHandler(main::accept).listen(8080, res -> {
if(res.succeeded()){
startFuture.complete();
} else {
startFuture.fail(res.cause());
}
});
}
}
Although this approach is a bit less “vertxy” than the other one, it is valid as Vert.x is unopinionated as Tim Fox pointed out. This approach has the advantages that each service can run standalone and may be deployed or undeployed dynamically at runtime.
The full example can be found on GitHub