I’ve been using Guice for several years so far. After some time, I encountered a problem of implementing startable services in one of my applications. I could not hardcode the bootstraping sequence, because the application had a modular design, and one could pick any modules he wanted. Providers are a bad solution for two reasons:
- they do not deal with closing the application and shutting down the service,
- recommendation of not having any I/O in providers.
I needed some time to figure out why the second statement is so important, but right now the answer is very simple: it breaks the Single Responsibility Principle. Guice is about putting objects one in another, not about their initialization, starting, etc. Simple, deterministic providers are the key for having reusable, and maintainable code base and this recommendation further saved me a lot of time and frustration.
It’s not that hard
I thought, “OK, running a sequence of start()
methods, and then a sequence of corresponding stop()
in the reverse order cannot be that hard”. It turned out that indeed, it wasn’t. The only problem was to figure it out at the first time. This is what I wanted to have:
- run a sequence of
start()
methods at the application startup, - run a sequence of
stop()
methods during the shutdown, - gracefully stop already started services, if some service fails to start,
- support for applications built of unknown number of modules:
- defining dependencies,
- finding out the correct startup order.
Our first task is to realize, what a service actually is. This is my proposal - a class that implements two methods for starting and stopping something:
Simple, isn’t it? Now let’s build some behavior around it!
Service runner
Service runner is a class that accepts a set of different startable services, and runs them. To determine the startup order, we’ll refer to a separate class, ServiceComposer
that will be described later. Right now all we want to know about it is that it can take a set and “magically” produce a list of services in the correct startup order.
Explanation:
- usually, we will want to execute some code once all the services are ready. We can do so by passing a runnable implementation to the body of
execute()
method, - we delegate the actual construction of the service startup order to a different object, to get more flexibility and allow us focusing here on the main problem,
- the core sequence ensures us that the runnable code will be executed only if all services start correctly, and that we’ll always attempt to close services at the end,
- each service is started, and remembered in the set of correctly started services. Any exception terminates the sequence and allows us closing the application gracefully,
- the stopping sequence is similar. The key difference is that we close only the services that have started correctly, and that errors do not terminate the loop.
I did not use the stream API here, for two reasons. First of all, I do not make any transformations here. Secondly, I do not expect thousands of services in my application; there will be only a couple of them. Streams add a huge overhead that pays back only in case of bigger collections, or more complex transformations.
Composing the services
To determine the startup order, I decided to use annotations. In my application, there is a dedicated interface for every service that defines the public API of it. The service is injected through that interface binding and the callers do not have to be aware of the internal implementation details. Interfaces are a good discriminator of the required functionality. If a service A must be started before B, this usually means that we inject A into B.
I have two annotations: @RequiresServices
and @ProvidesService
:
Take a look at them and imagine a sample service annotated with them:
Once we have several of such services, these annotations form a directed graph. The problem of finding the startup order for them is reduced to the topological sorting problem, and there are several ready-to-use algorithms that solve it. In my implementation, I used Kahn’s Algorithm whose pseudocode can be found at Wikipedia, and my code can be found on Github.
Guice to glue it together
The core implementation is independent from any dependency injection container and can be used as a standalone solution. However, Guice and other containers simplify the service registration process. We want to get a set of implementations of StartableService
interface, and we can use guice-multibindings extension for that:
As there is some boilerplate code, I recommend to put it into some static utility method that will help us with the correct registration. Now we can create the service runner and go:
Summary
The proposed solution is very simple, yet elegant and powerful enough for most of applications. It consists of two classes, three interfaces and two annotations. While I used Guice as a tool to glue it together, I think that there will be no problem in using any other dependency injection container. I’m thinking about giving Dagger a try in the near future, and I’m going to write about it.
The sample code for this post can be found in my dirtyplayground repository which I’m using for playing with new tools and concepts. The described classes are in com.zyxist.dirtyplayground.core.svc
package. I hope to have some time soon to split it as a separate repository. Stay tuned!