Service Graph

  • Designed to be used in combination with DataKernel DI and DataKernel Launcher as a mean to start/stop application services according to their dependency graph
  • Starts services by multithreaded graph traversal algorithm: leaf services first and so on
  • Stops services in 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 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 -> Promise.of(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: an AsyncHttpServer and an Eventloop for it.
  • According to this service graph, Service Graph starts Eventloop first. Only after that it starts the dependent AsyncHttpServer.
  • When application stops, the services will be stopped in 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. Before running the example, build the project.
These examples are located at datakernel -> examples -> core -> boot

SimpleServiceExample

In this example we create an application, which 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(String.format("|%s|", "Service started".toUpperCase())));
		}

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

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

EventloopServiceExample

You can create your custom services and Service Graph will also start and stop them:

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 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 services dependencies. For example, let’s assume we have an e-mail service prototype. To work properly, it requires two services - authorization service and database service. Authorization service also requires 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);
	}
}