Skip to main content

Service Graph

Overview#

ServiceGraph is a powerful tool for controlling service start and stop routine. Upon start up it creates a graph of all the required services based on provided dependencies. This graph is used to start and stop services concurrently which leads to faster startup time without services interfering with each other.

Features#

  • Designed to be used in combination with ActiveJ Inject and Launcher as a means to start/stop application services according to their dependency graph
  • It starts services by following the multithreaded graph traversal algorithm: leaf services first and so on
  • It stops services in the opposite direction
  • Services dependency graph is automatically built upon ActiveJ Inject dependencies graph, but can be customized by user-specified dependencies.
  • Supports multiple standard services like ThreadPool, Closeables, DataSource as well as Active-specific services like eventloops, async servers and async services.
  • Can be configured to support other services as well with user-provided adapters

To get a basic understanding of ServiceGraph's role, let's have 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);
}
}
  • This application extends predefined HttpServerLauncher which features ServiceGraphModule
  • HttpServerLauncher uses two services: AsyncHttpServer and Eventloop.
    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 be stopped in the opposite direction: 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 v4.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 which 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|");
}
}

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);
}
}

AdvancedServiceExample#

Service Graph can manage more complex service dependencies. For example, let's assume we have an e-mail service prototype. To work properly, it requires two services - an authorization service and a database service. The authorization service also requires a database service, along with 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 those services in the proper order:

=== STARTING APPLICATION
Started java.util.concurrent.Executor
Started io.activej.eventloop.Eventloop
Started AdvancedServiceExample$DBService
Started AdvancedServiceExample$AuthService
Started AdvancedServiceExample$EmailService
=== STOPPING APPLICATION
Stopped AdvancedServiceExample$EmailService
Stopped AdvancedServiceExample$AuthService
Stopped java.util.concurrent.Executor
Stopped io.activej.eventloop.Eventloop
Stopped 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.ofBlockingRunnable(executor,
() -> System.out.println("AuthService started"));
}
@Override
public @NotNull Promise<?> stop() {
return Promise.ofBlockingRunnable(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();
}
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();
}
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();
}
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);
}
}