— java, vertx, dependency injection — 5 min read
For some time I heard/read a lot about the vertx toolkit and since I'm a real fan of reactive programming and event-driven architectures, but also spending most of my time in the java world, it sounded very promising to me and I decided to give it a try. So I started to create my first vertx application.
As always I thought it can't be that hard. I will just start with a practical example and will learn how the framework/toolkit works automatically, without reading the docs at all. Really bad idea! As vertx is really unopiniated, you can write the code in any style and it works. But you should at least follow some basic rules to profit from vertx. So my advice would be at least read the core manual.
At some point (still hadn't read the docs completely) I thought about how to apply Dependency Injection when my application is going to be more complex. I had absolutely no idea, so I started to find an answer by using the search engine with search terms like 'vertx dependency injection' and so on. The results - there were just very few related - I got weren't official ones from the vertx team but some custom written libraries like vertx-guice. That seems to be a way doing it. Let's try it out!
As an example I'm creating a fictional project with some pseudo CRUD operations around a Foo
entity.
There is a FooRepository
interface offering those methods. The implementation of them can be found at the InMemoryFooRepository
class.
In all 3 different appraoches this is going to be our business logic - we only change the way of interacting with it.
Let's start with some code and the example descripted above. For our entity class we're using lombok to keep it clean and expressive.
We want to have reactive implementations, so we are going to use rxjava and our FooRepository
interface is defined like this:
The corresponding implementation with a @Singleton
annotation looks like this:
With Guice we're creating a Module
to define which implementation should be bound to the FooRepository
when we're going to inject it anywhere.
Here we go:
All the guice setup is done and we are ready to use it in our main application.
As usual in a vertx application our MainVerticle
is extending from AbstractVerticle
. We are overriding the init
method to get a FooRepository
instance here. When starting the verticle we are using this instance to execute some example operations. Basically we're saving one Foo entity and trying to find it by id again.
Our last class is a simple Main
class launching our MainVerticle
out of the IDE.
Okay, let's get the output of our application.
Our implemenation seems to work and everything is fine. Let's go ahead.
Whenever you'll read something about vertx you will see something like
The event bus is the nervous system of Vert.x
You can rely on it and should devinitely use it a much as possible. It supports message strategies like:
In our case we will use the last one.
Let's try this out.
Here is all the code that wasn't changed except of removing the @Singleton
annotation form the InMemoryFooRepository
class.
Of course we are removing the guice FooModule
from the previous example and instead adding a further verticle called FooVerticle
.
So what are we doing here? We're instantiating a InMemoryFooRepository
in the init
method. So far so easy.
When starting the verticle we register a JsonObject
consumer on the event bus for a certain address.
Depending on which action header is received from the incoming message we are invoking the corresponding
repository method and sending the result by replying to the message. That's mainly it, let's start using it.
It looks really similar to the approach with guice. Instead of using the repository directly we are sending messages over the event bus.
Therefore we have to decorate our messages with specific action headers and the payload as a JsonObject
.
We have a little bit more code but in this version we are completely decoupled from any implementation and any domain objects like Foo
. That's cool!
In our Main
class we're deploying the FooVerticle
before the MainVerticle
to ensure that we're not missing any message.
Here is the output.
Our application is still working and it works as expected!
For the last approach we are trying out the so called service proxies which are offered as a separate module by the vertx toolkit.
How do they work? Basically it's almost the same like those in the previous approach except we have less boilerplate code by using some code generation.
Our Foo
class is the only untouched piece of code.
Due to some restrictions you have to follow when using service proxies, we have to change our FooRepository
interface.
You can only use primitive types, JsonObject
and some other limited types as parameter - you can find them here and the last parameter always has to be the result handler also returning one of the limited types.
Both annotations @ProxyGen
and @VertxGen
are used by vertx for code generation.
So we also have to add further dependencies and some build config to our project. To focus on code I won't list it here but you can find it in the code repository.
According to the changes in our FooRepository
interface we also have to change the implementation.
The logic remains but we have to use the result handler. To successfully generate the proxy code we have to add one further file - a package-info.java
.
Enough conventions and limitations let's start using it. But first of all we have to register our service in the FooVerticle
.
That's cool - the registration is much cleaner than in the previous approach.
With the help of the ServiceBinder
class we register our InMemoryFooRepository
on a certain address we are going to use in our MainVerticle
.
Via vertx code generation also reactive implementations can be generated. That's why we see the rxSave
and rxFindById
method calls here.
At the init
method we are building these reactive service proxy clients via the ServiceProxyBuilder
class.
All direct interactions with the event bus are isolated in the generated proxy classes.
When taking a look into these we notice that the implementation is pretty similar to ours from the previous approach.
Here our switch statement on the action header with cases for each service method is.
And on the client-side we can also see that the params are put into separate properties of a JsonObject
and the action header is set, too.
Our Main
class is the same as before.
Is the application still working? Here is the output.
Yes it is.
Let's recap - With the event bus we get a mighty instrument from the vertx toolkit, which we should use.
It's almost as easy as guice to setup, the code isn't looking more complicated and is even stronger decoupled.
So my advice would be: In most cases you don't need a DI library within a vertx application and whenever you would create
a guice module start creating a new verticle exposing a service on the event bus.
You have to follow some restrictions like using limited types, but it isn't really a disadvantage since it also helps to stay decoupled and if you really need it, there is the @DataObject
annotation to rescue.
The only limitation I found so far is that you can't stream results over the event bus but I could imagine that this will be added at some point.
That's it - hope it helps! :)