Microservice is probably one of the most used term nowadays when speaking about software architecture. Although the underlying concepts aren’t new, it is one of the biggest buzzword of the two last years.
To summarize, big monolithic architectures tend to become not very maintainable and extendable when they grow over a certain size. Moreover, they don’t scale well (you scale by multiplying big applications), and you can’t replace old parts easily.
One of the biggest advantages of microservices is to address these concerns: instead of building an entire application as one block, one can build it as a set of services which will communicate over some kind of messaging system (most of the time, REST over HTTP). Given that, you will be able to replace one piece if you need to, you can scale only one piece because you need to, and so on.
But there is a counterpart: as you’re building a distributed system, you will have the disadvantages of a distributed system. Building a distributed system as microservices architecture is not that easy, but fortunately there are many frameworks to help you. This article will present some of them.
Requirements
I don’t want to make another comparison based on some "Hello World" application. Most of the frameworks provide a simple way to start a server and respond to a simple request with only one line.
But this is not the real life. In the real life, you should not use plain HTTP. You would use REST / HTTPS and have an HTTPS client. You would add a Location header after a successful creation, and add links to your JSON or XML representations. You should have metrics included, to monitor your application. You have some kind of security to protect the access to your API.
Here is a list of what I am going to evaluate.
-
I’m about to build a tiny RESTful API. UI is not considered in this article (note that every candidate framework has a mean to serve static resources easily)
-
Only use HTTPS to access the API
-
The API will have two sets of resources (aka two microservices): a simple hello world resource calling a cars resource to get cars available (what a useless example!). For simplicity’s sake, they will be deployed in the same server, but will communicate over HTTPS.
-
The resource will have a JSON representation
-
Each representation will have at least a self link to the resource
-
After a resource creation, client receive a Location header (the resource’s link)
-
The API will be accessible only by people providing a Facebook OAuth token
-
Each API must have a monitoring / metrics facility (not tested though)
-
Each service may be packaged as a standalone jar, to be run using
java -jar
.
The candidates
Here is the list of candidates:
Note that Restlet and Spark doesn’t claim to be Microservices frameworks, but Restlet is known to be a very good REST framework, and Spark is so lightweight that I couldn’t resist to try it against bigger frameworks like Spring Boot.
Prerequisites
Each sample run with a simple java -jar
command. You will need to set some properties to enable ssl: -Djavax.net.ssl.trustStore=… -Djavax.net.ssl.trustStorePassword=… -Djavax.net.ssl.keyStorePassword=… -Djavax.net.ssl.keyStorePath=…
.
In order to be able to run the samples, you need to create a keystore (see this tutorial for example), and install a self-signed certificate. You will need the Facebook certificate too (see here how to get it), and a user token (see https://developers.facebook.com/).
Don’t forget to modify the configuration files and system properties accordingly. All these steps are explained in the README file.
Dropwizard
As stated by its website, “Dropwizard is a Java framework for developing ops-friendly, high-performance, RESTful web services.”. It is a bundle of some successful libraries such as Jetty, Jersey and Jackson. The documentation is very good, I’ve found everything I needed inside; a good point, as I hate being forced to search for minutes (hours?) for something said simple.
Main
The main mehod is quite simple: you just build an Application
instance and run it:
public static void main(String[] args) throws Exception {
new DropwizardApplication().run(args);
}
The DropwizardApplication
class contains all the plumbing: resource and health checks registration, Guice bootstrapping and Jackson’s ObjectMapper
configuration. It is given an instance of a configuration class (DropwizardServerConfiguration
) which is a POJO holding the configuration properties read from the YAML file passed as parameter to our application.
The app is run with parameters: java -jar app.jar server config.yml
. The server
parameter is here to tell Dropwizard to start the server, but you can also manipulate the database (I didn’t try this).
Resources
Under the hood, Dropwizard uses Jersey, so the resources are just POJOs annotated with (a lot of) JAX-RS annotations:
@Path("/cars/{id}")
@Produces(MediaType.APPLICATION_JSON)
public class CarResource {
@Context
UriInfo uriInfo;
@Inject
private CarRepository carRepository;
@GET
public Response byId(@Auth User user, @PathParam("id") int carId) {
Optional<Car> car = carRepository.byId(carId);
return car.map(c -> {
CarRepresentation carRepresentation = new CarRepresentation(c);
carRepresentation.addLink(Link.self(uriInfo.getAbsolutePathBuilder().build(c.getId()).toString()));
return Response.ok(carRepresentation).build();
}).orElse(Response.status(Response.Status.NOT_FOUND).build());
}
}
All the underlying HTTP handling is done by the framework, you have no mandatory access to request and response objects. In this case, the return is a Response
but I could simply have returned the object; however, in that case the return code would not be the right one (201), so to have full control over it, I prefer that solution. Moreover, the 404 (Status.NOT_FOUND) is set on the response; I could throw an exception instead, and write a mapper to make an adequate response, but it’s overkill (and I hate so-called "Business Exceptions").
Note that injection is performed by Guice. It seems that there is a CDI container provided with Jersey (hk2), but I didn’t managed to make it work. Linking is handmade, and quite easy with the UriInfo
object.
Dropwizard uses Jackson to serialize / deserialize the object returned to JSON, so you have nothing special to do… but you have to configure the ObjectMapper
to disable errors on unknown properties (see Tolerant Reader).
HTTPS
HTTPS is configured in the YAML configuration file; the framework ignores the standard Java properties. The documentation explains exactly how to set it up, and there is no surprise here.
REST client
The REST client is built by Guice, as a singleton; it is not managed as documented, I didn’t managed to make it work this way. Otherwise, nothing special about the client, the API is fluent and simple:
@Override
public List<Car> getAllCars(String auth) {
WebTarget target = client.target("https://localhost:8443/app/cars");
Invocation invocation = target.request(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + auth)
.build(HttpMethod.GET);
Car[] cars = invocation.invoke(Car[].class);
return asList(cars);
}
The Client
, this time, uses the standard properties.
Security
The authentication requires two things: first, implement the Authenticator
interface. Note that he single method authenticate
returns an Optional<User>
, but not a Java 8’s Optional
, the Guava’s one! What a pity… Nevermind. Second, you need to register the authenticator against Jersey:
environment.jersey().register(AuthFactory.binder(
new OAuthFactory<>(guiceBundle.getInjector().getInstance(FacebookTokenAuthenticator.class),
getName() + "-Realm",
User.class)));
So far so good, it works as expected.
Monitoring
Dropwizard has a built-in monitoring system. You can register healthchecks to ensure that the app is up, and each resource can be metered simply using annotations. You can also add custom metrics, using the metrics registry obtained from the Environment
.
Conclusion
While a bit verbose due to all the plumbing involved in the setup, Dropwizard is a nice framework. It provides all the functions needed to build a MicroServices-based application. However, to build tiny services, the amount of plumbing required can be too high compared to the business code; I would not recommend to use it in that case. Otherwise, you cannot go wrong!
Vertx
“Vertx is a tool-kit for building reactive applications on the JVM.”. You can develop with it in Java of course, but also many languages running on the JVM (Javascript, Scala, Ruby, Python, Clojure).
It also provides an actors-like system, the "verticles", which allow deployment of independent, concurrent, and potentially written in different language, services communicating over an event bus. As stated by the documentation, you are not forced to use this model (I didn’t in this case, however I will give it a try!).
Main
The framework abstracts low level handling of HTTP, but you need to create the server by hand:
Vertx vertx = Vertx.create();
HttpServer server = vertx.createHttpServer(serverOptions);
Maybe you noticed the serverOptions parameter (sure you did!). You have to wait the HTTPS section to speak about that.
The main method creates the HTTP client, set the authentication system and binds "resources" to routes.
Resources
There is no resource class in Vertx. You just give handlers to routes:
CarResource carResource = new CarResource(carRepository);
router.get("/cars/:id").produces("application/json").handler(carResource::byId);
CarResource
is simply a POJO having a method named byId
with a RoutingContext
as parameter:
public void byId(RoutingContext routingContext) {
HttpServerResponse response = routingContext.response();
String idParam = routingContext.request().getParam("id");
if (idParam == null) {
response.setStatusCode(400).end();
} else {
Optional<Car> car = carRepository.byId(Integer.parseInt(idParam));
if (car.isPresent()) {
CarRepresentation carRepresentation = new CarRepresentation(car.get());
carRepresentation.addLink(self(routingContext.request().absoluteURI()));
response.putHeader("content-type", "application/json")
.end(Json.encode(carRepresentation));
} else {
response.setStatusCode(404).end();
}
}
}
As you can see, you have a total control on the response, and no choice about that. No problem, you know what you do, exactly. The JSON encoding is done by Jackson again, and you still have to disable the "fail on unknown property" feature.
Oh, by the way, this will not work without a subtle configuration on the route:
router.route("/cars*").handler(BodyHandler.create());
By default, Vertx ignores the body, so you have to explicitly say "I want to read it". Otherwise, you don’t get the body content.
Note that this time, there is no dependency injection, all is done manually. Not a big deal.
HTTPS
Remember the serverOptions in the Main section? This is the definition:
HttpServerOptions serverOptions = new HttpServerOptions()
.setSsl(true)
.setKeyStoreOptions(new JksOptions()
.setPath(System.getProperty("javax.net.ssl.keyStorePath"))
.setPassword(System.getProperty("javax.net.ssl.keyStorePassword")))
.setPort(8090);
This object allows to set the port and SSL properties. Vertx doesn’t automatically get the standard properties, so you have to do it yourself. Not really a problem, in my opinion (less magic, more control).
REST Client
Building a client is the same as building a server:
HttpClientOptions clientOptions = new HttpClientOptions()
.setSsl(true)
.setTrustStoreOptions(new JksOptions()
.setPath(System.getProperty("javax.net.ssl.trustStore"))
.setPassword(System.getProperty("javax.net.ssl.trustStorePassword")));
HttpClient httpClient = vertx.createHttpClient(clientOptions);
Usage is easy:
httpClient.getAbs("https://localhost:8090/cars")
.putHeader("Accept", "application/json")
.putHeader("Authorization", "Bearer " + routingContext.user().principal().getString("token"))
.handler(response ->
response.bodyHandler(buffer -> {
if (response.statusCode() == 200) {
List<Car> cars = new ArrayList<>(asList(Json.decodeValue(buffer.toString(), Car[].class)));
routingContext.response()
.putHeader("content-type", "test/plain")
.setChunked(true)
.write(cars.stream().map(Car::getName).collect(toList()).toString())
.write(", and then Hello World from Vert.x-Web!")
.end();
} else {
routingContext.response()
.setStatusCode(response.statusCode())
.putHeader("content-type", "test/plain")
.setChunked(true)
.write("Oops, something went wrong: " + response.statusMessage())
.end();
}
}))
.end();
I already said that you have full control, didn’t I? Well, as you can see, you indeed have full control (remember the raw servlet response?).
Security
There is an oauth module, but it doesn’t fulfill my needs. So again I did it by hand. Fortunately, I found a gist doing exactly what I want, but in Groovy. No problem, I wrote it in Java.
It consists of two classes, a AuthHandler
and an AuthProvider
. Once done, I just have to set the former as handler for all routes:
AuthHandler auth = new BearerAuthHandler(new FacebookOauthTokenVerifier(httpClient));
router.route("/*").handler(auth);
Monitoring
Vertx offers a metrics system, with many different metrics. Of course, you can register your own metrics using the MetricsRegistry.
Conclusion
Vertx is really complete. It is built on top of Netty, and offers a performant system. As it is written in Java 8, you can write a very elegant code.
As Vertx is asynchronous by nature, you can feel it puzzling at first sight for people like me that are not used to this programming style. But once you get the thing, it’s probably one of the best tools I have ever used.
Spring Boot
Spring Boot "takes an opinionated view of building production-ready Spring applications. Spring Boot favors convention over configuration and is designed to get you up and running as quickly as possible.".
Who, in the Java world, has never heard of Spring? It has been one the most popular framework ever in Java, so it was not possible to ignore it in this comparison.
Main
Very hard to do simpler:
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class);
}
}
There is a lot of magic. The @SpringBootApplication
triggers the component scan and configuration of the Spring application.
Note that you can switch the underlying web server, and choose between Tomcat, Jetty and Undertow. In the example, I used Jetty. This is done by choosing the module you want in the dependency management system (grade in this case).
Resources
Resource classes are annotated with a component annotation: @RestController
. Otherwise, it is much like JAX-RS: @RequestMapping for @Path, ResponseEntity
for Response
and so on.
@RestController
@RequestMapping(value = "/cars", produces = "application/json")
public class CarsController {
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public ResponseEntity<CarRepresentation> byId(@PathVariable("id") String carId) {
Optional<Car> car = carRepository.byId(Integer.parseInt(carId));
return car.map(c -> {
CarRepresentation rep = toCarRepresentation(c);
return new ResponseEntity<>(rep, HttpStatus.OK);
})
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
private CarRepresentation toCarRepresentation(Car c) {
String carId = String.valueOf(c.getId());
CarRepresentation rep = new CarRepresentation(c);
rep.add(linkTo(methodOn(CarsController.class).byId(carId)).withSelfRel());
return rep;
}
Linking is done using the spring-hateoas module instead of the handmade solution. Spring Boot uses Jackson for JSON serialization, but I didn’t configure the "ignore unknown property" feature: it is disabled by default (hurray)!
HTTPS
HTTPS is configured in the application.yml (or configuration.properties, as you want) file. It’s pretty much like the Dropwizard’s one. You just have to follow the documentation.
REST Client
The client is RestTemplate. It is very simple to use:
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Accept", "application/json");
headers.add("Authorization", authToken);
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(null, headers);
RestTemplate rest = new RestTemplate();
ResponseEntity<Car[]> responseEntity = rest.exchange("https://localhost:8443/cars", HttpMethod.GET, requestEntity, Car[].class);
List<Car> cars = asList(responseEntity.getBody());
Spring Boot uses the standard ssl properties.
Security
Once again, it pretty simple: you just have to annotate the resource class and configure the endpoint where you can check the token:
@EnableOAuth2Resource
@RestController
public class SampleController {
(...)
}
spring:
oauth2:
resource:
userInfoUri: https://graph.facebook.com/v2.4/me
preferTokenInfo: false
@EnableOAuth2Resource
isn’t bundled in Spring Boot, but comes from Spring Cloud Security.
Monitoring
Once again, you just have to drop a module to activate the metrics: Spring Boot Actuator. By default, there are metrics registered by the framework, but you can easily add your own. Under the hood, it uses Dropwizard metrics.
Conclusion
I really went fast using Spring Boot. I have just followed the documentation, and all worked as expected, immediately. Maybe it’s because it is not the first time I use Spring (but the first time for Boot), but I’m impressed with the ease with which I managed to build the app.
In my opinion, if you need to go fast, don’t want too many plumbing code, have a feature complete framework (Spring portfolio is huge), and if annotations don’t make you ill, go with Spring Boot. You won’t have regrets.
Restlet
"Restlet Framework is the most widely used open source solution for Java developers who want to create and use APIs.". I don’t know if Restlet is really the most widely used REST framework, many people heard of it but few use it.
Main
I decided to use Guice to enable dependency injection. That was my first candidate, and I thought I’d consider it as a criteria at first.
Anyway, the main class is pretty simple:
public static void main(String[] args) throws Exception {
Injector injector = Guice.createInjector(new SelfInjectingServerResourceModule(),
new RestletInfraModule(),
new CarModule(),
new HelloModule());
RestComponent component = injector.getInstance(RestComponent.class);
component.start();
}
The RestComponent
contains all the plumbing, such as route configuration, HTTPS settings, and so on. With Restlet, no configuration file, everything is done using code. I personally prefer this approach.
Resources
With Restlet, you use less annotations than with other frameworks:
public class CarsResource extends ServerResource {
@Inject
private CarRepository carRepository;
@Get("json")
public List<CarRepresentation> all() {
List<io.github.cdelmas.spike.common.domain.Car> cars = carRepository.all();
getResponse().getHeaders().add("total-count", String.valueOf(cars.size()));
return cars.stream().map(c -> {
CarRepresentation carRepresentation = new CarRepresentation(c);
carRepresentation.addLink(Link.self(new Reference(getReference()).addSegment(String.valueOf(c.getId())).toString()));
return carRepresentation;
}).collect(toList());
}
@Post("json")
public void createCar(io.github.cdelmas.spike.common.domain.Car car) {
carRepository.save(car);
setLocationRef(getReference().addSegment(String.valueOf(car.getId())));
setStatus(Status.SUCCESS_CREATED);
}
}
This is the first time I have to extend a framework class. Not a big deal yet. Annotations are straightforward to use. Note that you have to set the status, location to the class, not on a response object. The same for headers (getResponse().getHeaders().add…
). Attributes and parameters are retrieved from the resource object too. I don’t like this approach personally.
The worst thing I had to do (and it’s not documented), is the ObjectMapper
configuration. The code is really tricky: first, I need to write a custom converter (a complete class), and second I need to replace the existing converter after a complete iteration over the converters. Really, really bad…
HTTPS
Once again, it is very simple to set:
getClients().add(Protocol.HTTPS);
Server secureServer = getServers().add(Protocol.HTTPS, 8043);
Series<Parameter> parameters = secureServer.getContext().getParameters();
parameters.add("sslContextFactory", "org.restlet.engine.ssl.DefaultSslContextFactory");
parameters.add("keyStorePath", System.getProperty("javax.net.ssl.keyStorePath"));
Restlet uses the standard properties, almost. The keystorePath, surprisingly, doesn’t use the standard property. Otherwise, it’s quite well documented and easy to do.
REST Client
There is a client provided by Restlet. It is not easy to use, really. After having set the HTTPS properties (with a mix of standard-or-not properties), I had to use an interface with an annotated method (a resource-like interface) and wrap it with the client. Well, after all it’s a client resource, not a simple HTTP client. I’ve found this approach a bit complicated.
Security
To secure the API, I simply add a guard on the route. The guard runs a verifier, which is a custom one, and, I have to say, it is not straightforward to write it. Once done, all works well. As far as I remember, I had to search on forums to implement the solution.
Monitoring
Restlet doesn’t provide a monitoring facility. It is only a REST framework after all, and is not competiting against Spring Boot or Dropwizard.
Of course, you can add Dropwizard metrics, but it’s a lot of additional work.
Conclusion
Restlet is not a Microservices framework. However, it is good as a REST framework, as long as you don’t need to customize it. Then, you will have a lot of troubles, at least I had.
SparkJava
Don’t confuse Spark with Apache Spark. It has nothing in common. “Spark is a tiny Sinatra inspired framework for creating web applications in Java 8 with minimal effort”. Indeed it is tiny, but very feature rich.
Main
The main method is pretty much like other frameworks main: plumbing to wire routes. Note that Spark doesn’t use Jackson as mapper, but optionally uses a mapper passed as parameter of some route configuration calls. A mapper is simply a method taking an object as parameter and returning a String.
All configuration is done calling static methods on the Spark class.
Resources
There is no resource class in Spark, only routes. A route is simply a method taking a request and a response as parameters, and can return almost anything as result.
public String createCar(Request request, Response response) {
Car car;
try {
car = objectMapper.readValue(request.body(), Car.class);
carRepository.save(car);
response.header("Location", request.url() + "/" + car.getId());
response.status(201);
} catch (IOException e) {
response.status(400);
}
return "";
}
HTTPS
As everything in Spark, there is a static method call to set HTTPS:
String keystoreFile = System.getProperty("javax.net.ssl.keyStorePath");
String keystorePassword = System.getProperty("javax.net.ssl.keyStorePassword");
String truststoreFile = System.getProperty("javax.net.ssl.trustStore");
String truststorePassword = System.getProperty("javax.net.ssl.trustStorePassword");
secure(keystoreFile, keystorePassword, truststoreFile, truststorePassword);
REST Client
Spark is a server framework and doesn’t provide a client. To fill the gap, I use a great REST client library called Unirest. It looks like Spark as all calls are static method calls to the Unirest class.
The documentation helps, so I just follow it to make it work. Note that, Unirest doesn’t use Jackson as mapper, but provides an interface (a simplified version of Jackson’s ObjectMapper
, called… ObjectMapper
!) which you can implement using Jackson.
Security
Spark allows to define filters using before
and after
. In my case, I juste have to define a before filter:
AccessTokenVerificationCommandFactory accessTokenVerificationCommandFactory = new AccessTokenVerificationCommandFactory();
AuthenticationFilter authenticationFilter = new AuthenticationFilter(accessTokenVerificationCommandFactory);
before(authenticationFilter::filter);
filter
is, as always in Spark, a method taking a Request
and a Response
as parameters. Simple!
Monitoring
As a simple library, Spark doesn’t provide a mean to monitor your application. However, there is a simple library you can use to enhance Spark: spark-metrics. It is simply a collection of route decorators.
Conclusion
One can go very fast to implement a complete server using Spark. It is lightweight, runs fast and very simple to use. Unfortunately, it misses some features compared to others, but these are easily added using third party libraries, as easy-to-use that Spark itself.
I would definitely recommend to use it for projects of all size, unless you need advanced features quickly, although you can add it by hand.
End of the journey
It’s been a long journey. The study is not as complete as I first wanted, but it’s a good start. I will try to measure performance of each framework in another article. All the frameworks start very quickly (from few milliseconds to few seconds), respond well, but how do they behave under heavy load?
I tried all these frameworks after having used JAX-RS for a long time. Some of them are really similar, others are really disconcerting. In my opinion, you should always try a library or framework before you use it in production. This is what I’ve done, and I had a good time. I hope this article will be helpful in a way or another.
All the code is available on github (https://github.com/cdelmas/microservices-comparison). You are free and encouraged to fork, play with the code and give feedback.