Launcher examples

Purpose

In this guide we will learn how to build your application using launchers.

Introduction

Datakernel provides you with opportunity to create your own application using launchers.

Launchers are basically full featured applications. They use ServiceGraph to properly boot your application with all services and Google Guice to inject dependencies.

For some standard cases (HttpServer, RpcServer, RemoteFsServer, etc…) there are number of predefined launchers for you to use.

What you will need:

  • JDK 1.8 or higher
  • Maven 3.0 or higher
  • DataKernel (installation)

What modules will be used:

  • Launchers
  • Eventloop
  • HTTP
  • Boot

To proceed with this guide you have 2 options:

1. Working Example

To run the complete example, enter next commands:

$ git clone https://github.com/softindex/datakernel-examples
$ cd datakernel-examples/tutorials/launchers
$ mvn clean package exec:java -Dexec.mainClass=io.datakernel.examples.HttpSimpleServer

Then, go to testing section.

2. Step-by-step guide

Firstly, create a folder for application and build an appropriate project structure:

launchers
└── pom.xml
└── src
    └── main
        └── java
            └── io
                └── datakernel
                    └── examples
                        └── HelloWorldLauncher.java
                        └── HttpSimpleServer.java
                        └── HttpServerScratch.java

Next, configure your pom.xml file. We will need the following dependencies: datakernel-http, datakernel-boot and some logger (Note: we don’t need to specify eventloop, because it already is a transitive dependency of both datakernel-boot and datakernel-http modules). So your pom.xml should look like following:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>io.datakernel</groupId>
    <artifactId>launchers</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>io.datakernel</groupId>
            <artifactId>datakernel-boot</artifactId>
            <version>2.5.10-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>io.datakernel</groupId>
            <artifactId>datakernel-http</artifactId>
            <version>2.5.10-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>io.datakernel</groupId>
            <artifactId>datakernel-launchers</artifactId>
            <version>2.5.10-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.1.3</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Now lets create simple hello world launcher.

public class HelloWorldLauncher {
    public static void main(String[] args) throws Exception {
        Launcher launcher = new Launcher() {
            @Override
            protected Collection<Module> getModules() {
                return Collections.emptyList();
            }

            @Override
            protected void run() {
                System.out.println("Hello World!");
            }
        };
        launcher.launch(true, args);
    }
}

In example above we see how to create simple launcher and execute it. Don’t mind getModules right now, we will come back to it later.

  • Firstly we create our launcher. You can override these methods:
    • onStart will be executed first.
    • run is your application main method, all logic must be in it.
    • finally onStop method is executed.
  • Next we launching our launcher, by passing args and EagerSingletonMode constant, which is passed to Guice.

Now let’s move to something more complex, we will build echo http server from scratch.

public class HttpServerScratch extends Launcher {
    final static int PORT = 25565;

    @Override
    protected Collection<Module> getModules() {
        return asList(
                ServiceGraphModule.defaultInstance(),
                ConfigModule.create(Config.ofValue(ofInetSocketAddress(), new InetSocketAddress(PORT))
                ),
                new SimpleModule() {
                    @Singleton
                    @Provides
                    Eventloop eventloop() {
                        return Eventloop.create()
                                .withFatalErrorHandler(rethrowOnAnyError());
                    }

                    @Singleton
                    @Provides
                    AsyncServlet servlet() {
                        return new AsyncServlet() {
                            @Override
                            public Stage<HttpResponse> serve(HttpRequest request) {
                                logger.info("Received connection");
                                return Stage.of(HttpResponse.ok200().withBody(encodeAscii("Hello from HTTP server")));
                            }
                        };
                    }

                    @Singleton
                    @Provides
                    AsyncHttpServer server(Eventloop eventloop, AsyncServlet servlet, Config config) {
                        return AsyncHttpServer.create(eventloop, servlet)
                                .withListenAddress(config.get(ofInetSocketAddress(), Config.THIS));
                    }
                }
        );
    }

    @Override
    protected void run() throws Exception {
        awaitShutdown();
    }

    public static void main(String[] args) throws Exception {
        Launcher launcher = new HttpEchoServer();
        launcher.launch(true, args);
    }
}

In code above you can see, that getModules is used to provide Modules with dependencies.

What is going on:

  • Providing dependencies:
    • ServiceGraphModule will start components of your application in the right order.
    • ConfigModule will provide Config to your components.
    • SimpleModule will provide AsyncHttpServer and since it needs Eventloop and AsyncServlet as dependencies we providing them too.
  • run method just awaits shutdown of application(Keyboard Interruption, for example).
  • main method launches our application.

Setting up simple HTTP server is a common task, so Datakernel provides you with few predefined launchers to make your life easier.

Let’s take a look how simple it would be if we use HttpServerLauncher:

public class PredefinedEchoServer {
    private static final int SERVICE_PORT = 25565;

    public static void main(String[] args) throws Exception {
        Launcher launcher = new HttpServerLauncher() {
            @Override
            protected Collection<Module> getBusinessLogicModules() {
                return singletonList(
                        new SimpleModule() {
                            @Singleton
                            @Provides
                            AsyncServlet rootServlet() {
                                return new AsyncServlet() {
                                    @Override
                                    public Stage<HttpResponse> serve(HttpRequest request) {
                                        logger.info("Connection received");
                                        return Stage.of(HttpResponse.ok200().withBody(encodeAscii("Hello from HTTP server")));
                                    }
                                };
                            }
                        }
                );
            }

            @Override
            protected Collection<Module> getOverrideModules() {
                return singletonList(
                        ConfigModule.create(Config.create()
                                .with("http.listenAddresses", "" + SERVICE_PORT)
                        )
                );
            }
        };

        launcher.launch(parseBoolean(EAGER_SINGLETONS_MODE), args);
    }
}

When you are using predefined launchers you need to override these methods:

  • getBusinessLogicModules to specify actual logic of application.
  • getOverrideModules if you want to override default modules.

In example above we are overriding default port with our own and providing servlet that will handle each connection.

That’s it. Lets test our code.

Testing

Firstly, HelloWorldLauncher:

mvn clean package exec:java -Dexec.mainClass=io.datakernel.examples.HelloWorldLauncher

The next will be http servers:

mvn clean package exec:java -Dexec.mainClass=io.datakernel.examples.HttpServerScratch

If you now try to connect to localhost port 25565 using your browser or curl you will see “Hello from HTTP server” string

Exactly the same result will be in this case:

mvn clean package exec:java -Dexec.mainClass=io.datakernel.examples.HttpSimpleServer