Service Graph

  • Designed to be used in combination with DataKernel DI and DataKernel 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 DataKernel DI dependencies graph, but can be customized by user-specified dependencies.
  • Supports multiple standard services like ThreadPool, Closeables, DataSource as well as DataKernel-specific services like eventloops, async servers and async services.
  • Fully supports async services provided in @Worker scope - in that case, a whole vector of such worker instances is started/stopped
  • 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 includes ServiceGraphModule.
  • HttpServerLauncher uses two services for it: an AsyncHttpServer and an Eventloop.
  • According to this service 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 example you need to clone DataKernel from GitHub:
$ git clone https://github.com/softindex/datakernel
And import it as a Maven project. Check out branch v3.0. Before running the example, build the project.
These examples are located at datakernel -> 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(String.format("|%s|", "Service starting".toUpperCase()));
			return CompletableFuture.completedFuture(null)
					.whenCompleteAsync(($1, $2) ->
							System.out.println("|SERVICE STARTED|"));
		}

		@Override
		public CompletableFuture<?> stop() {
			System.out.println(String.format("|%s|", "Service stopping".toUpperCase()));
			return CompletableFuture.completedFuture(null)
					.whenCompleteAsync(($1, $2) ->
							System.out.println("|SERVICE STOPPED|"));
		}
	}

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

EventloopServiceExample

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

public class EventloopServiceExample extends Launcher {
	@Inject CustomEventloopService testService;

	@Provides
	Eventloop eventloop() {
		return Eventloop.create();
	}

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

	@Provides
	CustomEventloopService customEventloopService(Eventloop eventloop, Executor executor) {
		return new CustomEventloopService(eventloop, executor);
	}

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

	@Override
	protected void run() { }

	private static final class CustomEventloopService implements EventloopService {
		private final Executor executor;
		private final Eventloop eventloop;

		public CustomEventloopService(Eventloop eventloop, Executor executor) {
			this.executor = executor;
			this.eventloop = eventloop;
		}

		@Override
		public @NotNull Eventloop getEventloop() {
			return eventloop;
		}

		@Override
		public @NotNull Promise<?> start() {
			System.out.println(String.format("|%s|", "Eventloop-Service starting".toUpperCase()));
			return Promise.ofBlockingRunnable(executor,
					() -> System.out.println(String.format("|%s|", "Eventloop-Service started".toUpperCase())));
		}

		@Override
		public @NotNull Promise<?> stop() {
			System.out.println(String.format("|%s|", "Eventloop-Service stopping".toUpperCase()));
			return Promise.ofBlockingRunnable(executor,
					() -> System.out.println(String.format("|%s|", "Eventloop-Service stopped".toUpperCase())));
		}
	}

	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. So, we have the following service graph:

And ServiceGraphModule will start and stop all those services in the proper order:

 === STARTING APPLICATION

Started java.util.concurrent.Executor 
Started io.datakernel.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.datakernel.eventloop.Eventloop 
Stopped AdvancedServiceExample$DBService

So, this application looks as follows:

public class AdvancedServiceExample extends Launcher {
	@Inject DBService dbService;
	@Inject AuthService authService;
	@Inject EmailService emailService;

	@Provides
	DBService dbService() {
		return new DBService();
	}

	@Provides
	EmailService emailService(DBService dbService, AuthService authService) {
		return new EmailService(dbService, authService);
	}

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