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, reactive servers and reactive 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")
.toPromise();
}

public static void main(String[] args) throws Exception {
Launcher launcher = new HttpHelloWorldExample();
launcher.launch(args);
}
}
graph LR HttpServer --> Eventloop
  • According to this graph, Service Graph starts Eventloop first. The dependent HttpServer is started afterwards
  • When the application stops, the services will are stopped in reverse order: HttpServer 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 v6.0-beta2. 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

ReactiveServiceExample

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

public class ReactiveServiceExample extends Launcher {

@Provides
Reactor reactor() {
return Eventloop.create();
}

@Provides
@Eager
CustomReactiveService customEventloopService(Reactor reactor) {
return new CustomReactiveService(reactor);
}

@Override
protected Module getModule() {
return ServiceGraphModule.create();
}

@Override
protected void run() {
System.out.println("|RUNNING|");
}

private static final class CustomReactiveService extends AbstractReactive implements ReactiveService {
public CustomReactiveService(Reactor reactor) {
super(reactor);
}

@Override
public 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 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 {
ReactiveServiceExample example = new ReactiveServiceExample();
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.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(Reactor reactor, Executor executor, DBService dbService) {
return new AuthService(reactor, executor, dbService);
}

@Provides
Reactor reactor() {
return Eventloop.builder()
.withCurrentThread()
.build();
}

@Provides
Executor executor() {
return Executors.newCachedThreadPool();
}

@Override
protected Module getModule() {
return ServiceGraphModule.create();
}

@SuppressWarnings("FieldCanBeLocal")
private static class AuthService extends AbstractReactive
implements ReactiveService {
private final Executor executor;
private final DBService dbService;

public AuthService(Reactor reactor, Executor executor, DBService dbService) {
super(reactor);
this.executor = executor;
this.dbService = dbService;
}

@Override
public Promise<?> start() {
System.out.println("AuthService starting");
return Promise.ofBlocking(executor,
() -> System.out.println("AuthService started"));
}

@Override
public 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