Skip to content

fdk

You might not need Dependency Injection in a Vertx application

java, vertx, dependency injection5 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!

The experiment

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.

The traditional way

Dependency Injection with Guice

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.

The vertx way

Via the Eventbus directly

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:

  • pub/sub messaging
  • point-to-point/request-response messaging

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!

Via vertx service proxy

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.

Conclusion

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! :)

References