Startable services with Guice

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:

public interface StartableService {
   default public void start() throws Exception {
   }

   default public void stop() throws Exception {
   }
}

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.

public class ServiceRunnerImpl implements ServiceRunner {
   private static final Logger log = LoggerFactory.getLogger(ServiceRunnerImpl.class);
   private final ServiceComposer serviceComposer;
   private final Set<StartableService> services;
   
   @Inject
   public ServiceRunnerImpl(ServiceComposer composer, @Assisted Set<StartableService> services) {
      this.serviceComposer = Objects.requireNonNull(composer);
      this.services = Objects.requireNonNull(services);
   }

   // 1
   @Override
   public void execute(Runnable serviceAwareCode) {
      // 2
      List<StartableService> orderedServices = serviceComposer.compose(services);
      List<StartableService> stopOrderedServices = Lists.reverse(orderedServices);
      Set<StartableService> correctlyStarted = new HashSet<>();
      
      // 3
      try {
         if (startServices(orderedServices, correctlyStarted)) {
            serviceAwareCode.run();
         }
      } finally {
         stopServices(stopOrderedServices, correctlyStarted);
      }
   }
   
   private boolean startServices(List<StartableService> services, Set<StartableService> correctlyStarted) {
      // 4
      for (StartableService svc: services) {
         String name = svc.getClass().getSimpleName();
         try {
            log.info("Starting service: " + name);
            svc.start();
            log.info("Started service: " + name);
            correctlyStarted.add(svc);
         } catch (Exception exception) {
            log.error("Service " + name + " failed during startup.", exception);
            return false;
         }
      }
      return true;
   }
   
   private void stopServices(List<StartableService> services, Set<StartableService> correctlyStarted) {
      // 5
      for (StartableService svc: services) {
         if (correctlyStarted.contains(svc)) {
            String name = svc.getClass().getSimpleName();
            try {
               log.info("Stopping service: " + name);
               svc.stop();
               log.info("Stopped service: " + name);
            } catch (Exception exception) {
               log.error("Service " + name + " failed during shutdown.", exception);
            }
         }
      }
   }
}

Explanation:

  1. 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,
  2. 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,
  3. 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,
  4. 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,
  5. 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:

@Retention(RUNTIME)
@Target(TYPE)
public @interface RequiresServices {
   Class<?>[] value();
}

@Retention(RUNTIME)
@Target(TYPE)
public @interface ProvidesService {
	Class<?> value();
}

Take a look at them and imagine a sample service annotated with them:

public interface JoeService {
   public void doSomething();
}
@RequiresServices({FooService.class, BarService.class})
@ProvidesService(JoeService.class)
public class JoeServiceImpl implements JoeService, StartableService {
   @Inject
   public JoeServiceImpl(FooService foo, BarService bar) {
      // ...
   }
   
   // start(), stop() etc. methods go here
}

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:

public class CoreModule extends AbstractModule {
   protected void configure() {
      bind(BarServiceImpl.class).in(Singleton.class);
      bind(BarService.class).to(Key.get(BarServiceImpl.class));

      Multibinder<StartableService> serviceBinder = Multibinder.newSetBinder(binder, StartableService.class);
      serviceBinder.addBinding().to(Key.get(BarServiceImpl.class));
   }
}

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:

public class Bootstrap {
   private final ServiceRunner runner;
   
   @Inject
   public Bootstrap(ServiceRunnerFactory factory, Set<StartableService> services) {
      runner = factory.create(services);
   }
   
   public void execute() {
      runner.execute(() -> System.out.println("Hi, universe!"));
   }
}

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!