Skip to main content

Service Graph

Overview

ServiceGraph is a tool for managing the procedure of starting and stopping services. At start up, it creates a graph of all required services based on dependencies provided. This graph is used to start and stop services concurrently, resulting in faster startup times without services interfering with each other.

Features

  • Designed to be used in combination with ActiveJ Inject and Launcher as a means of starting/stopping application services according to their dependency graph
  • It starts services following the multithreaded graph traversal algorithm: leaf services first, and so on
  • It stops services in reverse order
  • The services dependency graph is automatically built based on the ActiveJ Inject dependencies graph, but can be customized based on user-specified dependencies.
  • Supports many standard services like ThreadPool, Closeables, DataSource, as well as Active-specific services such as eventloops, async servers and async services.
  • Can be configured to support other services with user-provided adapters

To get a basic understanding of the ServiceGraph role, let's take a look at a very simple example of an HTTP Server:

public final class HttpHelloWorldExample extends HttpServerLauncher {  @Provides  AsyncServlet servlet() {    return request -> HttpResponse.ok200().withPlainText("Hello World");  }
  public static void main(String[] args) throws Exception {    Launcher launcher = new HttpHelloWorldExample();    launcher.launch(args);  }}
graph LR AsyncHttpServer --> Eventloop
  • According to this graph, Service Graph starts Eventloop first. The dependent AsyncHttpServer is started afterwards
  • When the application stops, the services will are stopped in reverse order: AsyncHttpServer first and Eventloop next

Examples

note

To run the examples, you need to clone ActiveJ from GitHub

git clone https://github.com/activej/activej

And import it as a Maven project. Check out tag v5.4.3. Before running the examples, build the project. These examples are located at activej/examples/core/boot

SimpleServiceExample

In this example, we create an application that extends Launcher and has a simple custom service that basically only starts and stops:

public class SimpleServiceExample extends Launcher {  public static void main(String[] args) throws Exception {    SimpleServiceExample example = new SimpleServiceExample();    example.launch(args);  }
  @Inject CustomService customService;
  @Override  protected Module getModule() {    return ServiceGraphModule.create();  }
  @Inject  private static class CustomService implements Service {    @Override    public CompletableFuture<?> start() {      System.out.println("|SERVICE STARTING|");      return CompletableFuture.completedFuture(null);    }
    @Override    public CompletableFuture<?> stop() {      System.out.println("|SERVICE STOPPING|");      return CompletableFuture.completedFuture(null);    }  }
  @Override  protected void run() {    System.out.println("|RUNNING|");  }}

See full example on GitHub

EventloopServiceExample

Service Graph is also able to start and stop your custom services:

public class EventloopServiceExample extends Launcher {
  @Provides  Eventloop eventloop() {    return Eventloop.create();  }
  @Provides  @Eager  CustomEventloopService customEventloopService(Eventloop eventloop) {    return new CustomEventloopService(eventloop);  }
  @Override  protected Module getModule() {    return ServiceGraphModule.create();  }
  @Override  protected void run() {    System.out.println("|RUNNING|");  }
  private static final class CustomEventloopService implements EventloopService {    private final Eventloop eventloop;
    public CustomEventloopService(Eventloop eventloop) {      this.eventloop = eventloop;    }
    @Override    public @NotNull Eventloop getEventloop() {      return eventloop;    }
    @Override    public @NotNull Promise<?> start() {      System.out.println("|CUSTOM EVENTLOOP SERVICE STARTING|");      return Promises.delay(Duration.ofMillis(10))          .whenResult(() -> System.out.println("|CUSTOM EVENTLOOP SERVICE STARTED|"));    }
    @Override    public @NotNull Promise<?> stop() {      System.out.println("|CUSTOM EVENTLOOP SERVICE STOPPING|");      return Promises.delay(Duration.ofMillis(10))          .whenResult(() -> System.out.println("|CUSTOM EVENTLOOP SERVICE STOPPED|"));    }  }
  public static void main(String[] args) throws Exception {    EventloopServiceExample example = new EventloopServiceExample();    example.launch(args);  }}

See full example on GitHub

AdvancedServiceExample

The Service Graph can manage more complex service dependencies. For example, suppose we have a prototype e-mail service. It needs two services to work properly - an authorization service and a database service. The authorization service also requires a database service, as well as Eventloop and Executor. As a result, we have the following service graph:

graph LR id1(EmailService) --> id2(AuthService) id2 --> id3(DBService) id1 --> id3 id2 --> Executor id2 --> Eventloop

And ServiceGraphModule will start and stop all these services in the right order:

=== STARTING APPLICATION
Started java.util.concurrent.ExecutorStarted io.activej.eventloop.EventloopStarted AdvancedServiceExample$DBService
Started AdvancedServiceExample$AuthService
Started AdvancedServiceExample$EmailService
 === STOPPING APPLICATION
Stopped AdvancedServiceExample$EmailService
Stopped AdvancedServiceExample$AuthService
Stopped java.util.concurrent.ExecutorStopped io.activej.eventloop.EventloopStopped AdvancedServiceExample$DBService

This application looks as follows:

public class AdvancedServiceExample extends Launcher {  @Provides  @Eager  DBService dbService() {    return new DBService();  }
  @Provides  @Eager  EmailService emailService(DBService dbService, AuthService authService) {    return new EmailService(dbService, authService);  }
  @Provides  @Eager  AuthService authService(Eventloop eventloop, Executor executor, DBService dbService) {    return new AuthService(eventloop, executor, dbService);  }
  @Provides  Eventloop eventloop() {    return Eventloop.create().withCurrentThread();  }
  @Provides  Executor executor() {    return Executors.newCachedThreadPool();  }
  @Override  protected Module getModule() {    return ServiceGraphModule.create();  }
  @SuppressWarnings("FieldCanBeLocal")  private static class AuthService implements EventloopService {    private final Eventloop eventloop;    private final Executor executor;    private final DBService dbService;
    public AuthService(Eventloop eventloop, Executor executor, DBService dbService) {      this.executor = executor;      this.eventloop = eventloop;      this.dbService = dbService;    }
    @Override    public @NotNull Eventloop getEventloop() {      return eventloop;    }
    @Override    public @NotNull Promise<?> start() {      System.out.println("AuthService starting");      return Promise.ofBlocking(executor,          () -> System.out.println("AuthService started"));    }
    @Override    public @NotNull Promise<?> stop() {      return Promise.ofBlocking(executor,          () -> System.out.println("AuthService stopped"));    }  }
  private static class DBService implements Service {    @Override    public CompletableFuture<?> start() {      System.out.println("DBService is starting");      return CompletableFuture.runAsync(() -> {        try {          Thread.sleep(1000);        } catch (InterruptedException e) {          e.printStackTrace();          Thread.currentThread().interrupt();        }        System.out.println("DBService is started");      });    }
    @Override    public CompletableFuture<?> stop() {      System.out.println("DBService is stopping");      return CompletableFuture.runAsync(() -> {        try {          Thread.sleep(2000);        } catch (InterruptedException e) {          e.printStackTrace();          Thread.currentThread().interrupt();        }        System.out.println("DBService is stopped");      });    }  }
  @SuppressWarnings("FieldCanBeLocal")  private static class EmailService implements Service {    private final DBService service;    private final AuthService authService;
    public EmailService(DBService service, AuthService authService) {      this.service = service;      this.authService = authService;    }
    @Override    public CompletableFuture<?> start() {      System.out.println("EmailService is starting");      return CompletableFuture.runAsync(() -> {        try {          Thread.sleep(1000);        } catch (InterruptedException e) {          e.printStackTrace();          Thread.currentThread().interrupt();        }        System.out.println("EmailService is started");      });    }
    @Override    public CompletableFuture<?> stop() {      System.out.println("EmailService is stopping");      return CompletableFuture.runAsync(() -> System.out.println("EmailService is stopped"));    }  }
  @Override  protected void run() {  }
  public static void main(String[] args) throws Exception {    AdvancedServiceExample example = new AdvancedServiceExample();    example.launch(args);  }}

See full example on GitHub