Tutorial on Microservices with Kotlin, SpringBoot, Akka and Docker – part 1

Intro

Hi all! Recently I have begun my journey toward the “microservices” world, and I have found out that there are actually a lot of different ways to implement that simple concept, i.e. split a complex project in small, well focused, components. So, I decided to create a “microservices” architecture stub that could be used as a model for *real* implementations. In that stub I will put different technologies, not necessarily to be always used, but to work as template. This tutorial will follow step-by-step the project development: I will start from a multi-module SpringBoot project and “Dockerize” it… have fun!!!!

Tutorial parts

  1. Project setup and registration service
  2. Accessing db with persistence service
  3. Long task to be consumed asynchronously with timeconsuming service
  4. Asking data to other services with configuration service
  5. Adding an Akka server with actorserver service
  6. Create the Akka client service to talk with the actoreserver

Frameworks and architecture

The project will be written in Kotlin (there will be very few lines of codes, so it should not be an issues), and will use Spring Cloud as framework and Docker as container manager. The microservices orchestration will be entrusted to the Eureka service. I would like to manage everything, even the Docker images, with maven goals, so I have looked around for available plugins. At the end I have choosen the RedHat Fabric8 plugin, of which you can read here.

 System requirements

All the development will be done on an OpenSuSE Linux, where I have installed Docker (1.12.6) and Oracle JDK 1.8.0_65. Last, I use IntelliJ IDEA as IDE, but the project will be 100% Maven-based, there should be no issues with other IDEs. Check that your Docker environment is working before proceeding.

Ready? Go on….

Setup project

You may download the full code from the repository:

git clone https://github.com/gitgabrio/container-microservices-tutorial-1.git

service-parent (parent pom/multimodule)

The project will be a Maven multimodule, so let’s begin creating the base pom.xml:

<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>net.microservices.tutorial</groupId>
    <artifactId>container-microservice</artifactId>
    <version>0.1</version>
    <packaging>pom</packaging>
    <parent>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-parent</artifactId>
        <version>Dalston.RELEASE</version>
        <relativePath></relativePath>
    </parent>
    <properties>
        <!-- TEST -->
        <junit.version>4.12</junit.version>
        <!-- LOG -->
        <slf4j.version>1.7.12</slf4j.version>
        <!-- PLUGINS -->
        <maven-compiler-plugin.version>3.1</maven-compiler-plugin.version>
        <maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version>
        <maven-gpg-plugin.version>1.6</maven-gpg-plugin.version>
        <!-- CONFIGURATIONS -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.source>1.8</java.source>
        <java.target>1.8</java.target>
        <kotlin.version>1.1.0</kotlin.version>
    </properties>
    <dependencies>
        <dependency>
            <!-- Setup Spring Boot -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <!-- Setup Spring MVC & REST, use Embedded Tomcat -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <!-- Setup Spring Data common components -->
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-commons</artifactId>
        </dependency>
        <dependency>
            <!-- Testing starter -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <!-- Setup Spring Data JPA Repository support -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <!-- In-memory database for testing/demos -->
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
        </dependency>
        <dependency>
            <!-- Spring Cloud starter -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter</artifactId>
        </dependency>
        <dependency>
            <!-- Eureka service registration -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
        <dependency>
            <!-- Kotlin -->
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>src/main/kotlin</sourceDirectory>
        <testSourceDirectory>src/test/kotlin</testSourceDirectory>
        <resources>
            <resource>
                <filtering>true</filtering>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.yml</include>
                    <include>**/*.sql</include>
                    <include>**/*.html</include>
                    <include>**/*.conf</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
        <testResources>
            <testResource>
                <directory>src/test/resources</directory>
            </testResource>
        </testResources>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>${maven-compiler-plugin.version}</version>
                <configuration>
                    <source>${java.source}</source>
                    <target>${java.target}</target>
                    <maxmem>256M</maxmem>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-plugin</artifactId>
                <version>${kotlin.version}</version>
                <executions>
                    <execution>
                        <id>compile</id>
                        <phase>process-sources</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                        <configuration>
                            <sourceDirs>
                                <source>src/main/kotlin</source>
                            </sourceDirs>
                        </configuration>
                    </execution>
                    <execution>
                        <id>test-compile</id>
                        <phase>process-test-sources</phase>
                        <goals>
                            <goal>test-compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-gpg-plugin</artifactId>
                <version>${maven-gpg-plugin.version}</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

That’s it. I have declared spring-cloud-starter-parent as parent to have available all the SpringBoot + Eureka stack out-of-the-box.
For the moment being, it is just an empty, useless box, but we will begin to fill it immediately.

service-common

First of all, let’s create a “common” module: it will be used to contain classes and utilities used by real “microservices”. I have read somewhere that there should be absolutely no code-linking between the microservices, i.e. they should not have any code in common: clearly this “service-common” breaks the rule, and I understand the sense in keeping code-bases completely isolated between them, in real situations, with really complex projects. But on the other side I think that for this tutorial it could be accepted, just to avoid repeated code.
Let’s add it as “module” to the “parent” pom, just below the close of the “parent” tag, in the parent pom:

... 
</parent>
<modules>
    <module>servicecommon</module>
</modules>
<properties>
...

Here’s the “servicecommon” pom.xml:

<?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>
    <artifactId>servicecommon</artifactId>
    <packaging>jar</packaging>
    <parent>
        <groupId>net.microservices.tutorial</groupId>
        <artifactId>container-microservice</artifactId>
        <version>0.1</version>
        <relativePath>../</relativePath>
    </parent>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.5.2.RELEASE</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Please note the spring-boot-maven-plugin configuration, to avoid “repackage” goal to be executed in this module.

registration-service

The registration service will be the first “microservice” to be implemented. Add it as a module, just below the “servicecommon”, in the parent pom:

... 
</parent>
<modules>
  <module>servicecommon</module>
  <module>registrationservice</module>
</modules>
<properties>
...

Now, let’s create the “registrationservice” pom.xml:

<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>
    <artifactId>registrationservice</artifactId>
    <packaging>jar</packaging>
    <parent>
        <groupId>net.microservices.tutorial</groupId>
        <artifactId>container-microservice</artifactId>
        <version>0.1</version>
        <relativePath>../</relativePath>
    </parent>
    <properties>
        <start-class>net.microservices.tutorial.registrationservice.RegistrationServer</start-class>
        <finalName>registrationservice-${project.version}</finalName>
        <service.port>1111</service.port>
        <container.ip>localhost</container.ip>
    </properties>
    <build>
        <finalName>${finalName}</finalName>
    </build>
</project>

Please take note of the service.port and container.ip properties: more about them later.

code

We will use the Eureka protocol for microservices orchestration. Simply put, there is (at least) one service that act as “registrar” to keep note of all the other running instances (microservice type and ip). All other services are “client”s, and register themselves with the registration service. Whenever one service needs to talk with another one, it asks the registration service for the address of the first available instance (there is a load-balancing in there) of that type of service, and then use the received address for the communication.
Using SpringBoot dark magic, setting up a registrar is a matter of few lines of yml and annotations:
registrationservice/src/main/resources/application.yml

# Configure this Discovery Server
eureka:
  instance:
    hostname: ${CONTAINER_IP:@container.ip@}
    #enableSelfPreservation: false
    preferIpAddress: true
    # DO NOT DO THIS IN PRODUCTION
    leaseRenewalIntervalInSeconds: 5
  client:
    registerWithEureka: false
    fetchRegistry: false

server:
  port: ${SERVICE_PORT:@service.port@}
# Discovery Server Dashboard uses FreeMarker.  Don't want Thymeleaf templates
spring:
  thymeleaf:
    enabled: false
security:
  basic:
    enabled: false
management:
  security:
    enabled: false

Please note the two “MAVEN-style” properties ${CONTAINER_IP:@container.ip@} and ${SERVICE_PORT:@service.port@}: here we are telling the compiler to use the first variable (CONTAINER_IP, SERVICE_PORT) if available, otherwise the second one (@container.ip@, @service.port@) if not.
CONTAINER_IP and SERVICE_PORT will be populated later during docker image creation, inside the fabric8 plugin configuration, while @container.ip@ and @service.port@ are injected from the pom.xml. With this in place we can run the service stand-alone, starting it with

mvn spring-boot:start

from the registrationservice directory, or inside the docker container, as we will see later.
registrationservice/src/main/kotlin/net/microservices/tutorial/registrationservice/RegistrationServer.kt

@file:JvmName("RegistrationServer")
package net.microservices.tutorial.registrationservice

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer
import org.springframework.context.annotation.ComponentScan
import org.springframework.context.annotation.Configuration

@Configuration
@EnableAutoConfiguration(exclude = arrayOf(DataSourceAutoConfiguration::class, DataSourceTransactionManagerAutoConfiguration::class, HibernateJpaAutoConfiguration::class))
@ComponentScan
@EnableEurekaServer
open class RegistrationServer {

    companion object {

            /**
             * Run the application using Spring Boot and an embedded servlet engine.

             * @param args
             * *            Program arguments - ignored.
             */
            @JvmStatic fun main(args: Array<String>) {
                SpringApplication.run(RegistrationServer::class.java, *args)
            }
        }
}

Please note the open class definition: this is needed, as in all the Kotlin classes managed by Spring, because at runtime Spring creates proxies of these classes, and by default Kotlin classes are final (Kotlin, I love you!).

Actually, all the magic is here:

@EnableEurekaServer

With this annotation we are telling Spring to setup a Eureka Registrar Server, using as properties the ones defined (by default) in the application.yml file.

docker

Last step will be the creation of the docker module.
To cut a long story short, fabric8 plugin uses the maven-assembly-plugin to build the images. Unfortunately, making this plugin work for a multimodule project is a little bit tricky (you may find detailed explanation here), so one solution is to create an “empty” module used just to create the images.
Let’s go back to the parent pom, and add the module declaration:

... 
</parent>
<modules>
  <module>servicecommon</module>
  <module>registrationservice</module>
  <module>docker</module>
</modules>
<properties>
...

Now, the pom.xml:

<?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">
    <parent>
        <artifactId>container-microservice</artifactId>
        <groupId>net.microservices.tutorial</groupId>
        <version>0.1</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>docker</artifactId>
    <packaging>pom</packaging>
    <properties>
        <!-- PLUGINS -->
        <docker.maven.plugin.fabric8.version>0.21.0</docker.maven.plugin.fabric8.version>
        <!-- CONFIGURATIONS -->
        <docker.repo>(YOUR-DOCKER-REPO-NAME)</docker.repo>
        <!-- services properties -->
        <registration.service.port>1111</registration.service.port>
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.microservices.tutorial</groupId>
            <artifactId>registrationservice</artifactId>
            <version>0.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <!-- DOCKERIZE WITH FABRIC8 -->
            <plugin>
                <!-- The Docker Maven plugin is used to create docker image with the fat jar -->
                <groupId>io.fabric8</groupId>
                <artifactId>docker-maven-plugin</artifactId>
                <version>${docker.maven.plugin.fabric8.version}</version>
                <configuration>
                    <logDate>default</logDate>
                    <autoPull>true</autoPull>
                    <images>
                        <!-- Registration service -->
                        <image>
                            <!-- Alias name which can used for linking containers during runtime -->
                            <alias>registrationservice</alias>
                            <name>${docker.repo}/registration-service:${project.version}</name>
                            <!-- ....................................... -->
                            <!-- Build configuration for creating images -->
                            <!-- ....................................... -->
                            <build>
                                <from>java:8u40</from>
                                <!-- Assembly descriptor holds the reference to the created artifact-->
                                <assembly>
                                    <descriptor>${basedir}/../registrationservice/src/main/fabric8/assembly.xml
                                    </descriptor>
                                </assembly>
                                <!-- Expose ports -->
                                <ports>
                                    <port>${registration.service.port}</port>
                                </ports>
                                <!-- Default command for the build image -->
                                <cmd>java -Djava.security.egd=file:/dev/./urandom -jar /maven/registrationservice.jar
                                </cmd>
                            </build>
                            <!-- ............................................................... -->
                            <!-- Runtime configuration for starting/stopping/linking containers -->
                            <!-- ............................................................... -->
                            <run>
                                <!-- Assign dynamically mapped ports to maven variables (which can be reused in integration tests) -->
                                <ports>
                                    <port>${registration.service.port}:${registration.service.port}</port>
                                </ports>
                                <env>
                                    <SERVICE_PORT>${registration.service.port}</SERVICE_PORT>
                                    <CONTAINER_IP>${docker.container.registrationservice.ip}</CONTAINER_IP>
                                </env>
                                <wait>
                                    <!-- Check for this URL to return a 200 return code .... -->
                                    <url>http://${docker.host.address}:${registration.service.port}/</url>
                                    <!-- ... but at max 20 seconds -->
                                    <time>20000</time>
                                </wait>
                                <log>
                                    <prefix>TC</prefix>
                                    <color>cyan</color>
                                </log>
                            </run>
                        </image>
                    </images>
                </configuration>
                <!-- Hooking into the lifecycle -->
                <executions>
                    <execution>
                        <id>docker-build</id>
                        <goals>
                            <goal>build</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>start</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>build</goal>
                            <goal>start</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>stop</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>stop</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>1.5.2.RELEASE</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Well, there is some hot stuff going on here.
Note that the packaging is pom, since there is no code in that project.
Also, please note the dependency on the registrationservice artifact, needed for the assembly plugin to work.
Now, the interesting part.
The guys at Fabric8 decided that all (or almost all) the image configuration (creation and starting) should be inside the pom, so there are a lot a properties reflecting the ones of the standard Dockerfile.
The configuration is made of two parts: build and run.
Inside the build we will put information for the image creation:

  • : the base image (java:8u40 in this case)
  • : list of ports exposed by the container (please note, this is not the  external_port:internal_port mapping)
  • : the command to be executed inside the container
  • : this is the pointer to the maven-assembly plugin descriptor; basically, this descriptor tells maven what should be put inside the built image and what should be the name of the jar (the assembly.xml will be shown below).

Inside the run we will put information for the container starting:

  • : list of  external_port:internal_port mapping
  • : variables injected inside the container
  • : command to execute to check that the container has started successfully.

Beside that, there are some properties that are populated when the container starts, for example:

  • docker.container.(image-name).ip: this contain the “public” ip of the running container;
  • docker.host.address: this contain the host address of the running container (localhost);

To make them available inside the container, we can declare them in the env tag, and the property name will be the one created inside the container; e.g.:

<env> 
   <SERVICE_PORT>${registration.service.port}</SERVICE_PORT>
   <CONTAINER_IP>${docker.container.registrationservice.ip}</CONTAINER_IP>
</env>

Inside the container we will have $SERVICE_PORT (with the registration.service.port value, statically defined in the pom) and $CONTAINER_IP (with the docker.container.registrationservice.ip value, dynamically defined at start time).
There are another couple of run tags used for images linking/dependency, but we will look at them in the next part of the tutorial.

Assembly

The following snippet instruct the plugin to look for the assembly description in a specific file:

...
<assembly>
       <descriptor>${basedir}/../registrationservice/src/main/fabric8/assembly.xml
       </descriptor>
</assembly>
...

The file is inside the registrationservice module:
registrationservice/src/main/fabric8/assembly.xml

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">
    <!-- Assembly specifying Dockerbuild for fabric8/docker-maven-plugin -->
    <dependencySets>
        <dependencySet>
            <useProjectArtifact>false</useProjectArtifact>
            <includes>
                <include>net.microservices.tutorial:registrationservice</include>
            </includes>
            <outputDirectory>.</outputDirectory>
            <outputFileNameMapping>registrationservice.jar</outputFileNameMapping>
        </dependencySet>
    </dependencySets>
</assembly>

Here we are telling maven to include only the registrationservice artifact and to put it inside registrationservice.jar
Note that it is possible to avoid that file, putting its content inside the assembly directive, in the inline tag:

...
<assembly>
    <inline>
    <dependencySets>
        <dependencySet>
            <useProjectArtifact>false</useProjectArtifact>
            <includes>
                <include>net.microservices.tutorial:registrationservice</include>
            </includes>
            <outputDirectory>.</outputDirectory>
            <outputFileNameMapping>registrationservice.jar</outputFileNameMapping>
        </dependencySet>
    </dependencySets>
    </inline>
</assembly>
...

but actually I do prefer to keep it outside – it could be easier to manage, eventually, without cluttering too much the pom itself.

docker image creation, start and stop

Now we have all the pieces in place to build and start our first image, but before proceeding any further remember to replace the (YOUR-DOCKER-REPO-NAME) property with the name of your Docker repository.
Calling

mvn install

from the console, in the project root, will:

  1.  recursively build all the modules in the project
  2. install all the modules
  3. create the reactiveservice image.

The first time that the image is built, the base image will be downloaded (if not already present in the local Docker instance). Soon after the build, the image is tested starting a container and invoking the command defined in the wait directive: if the command is successfull, the build is marked with success. Whenever a new version of the image is built, the plugin takes care of removing the old one.

Now, to start the container, let’s go in the docker directory and type:

mvn docker:start

This will create and start a new container with the image just built and the parameters defined in the run configuration. If you execute the command in debug mode (with the -X parameter) you will see the actual configuration sent (i.e. with all the placeholders replaced) and also the http requests issued to the Docker daemon (and, for free, all the image’ data transferred to it, in hexadecimal format!).
To verify that everything is working, just open the browser here and you should see the Eureka dashboard.

Finally, to stop the container execute

mvn docker:stop

This will stop the container and remove it.

Conclusion

Well, I think we have touched a lot of important topics.
To clone the project:

 git clone https://github.com/gitgabrio/container-microservices-tutorial-1.git

In the next parts we will add other containers, to see how to make Eureka clients talking between them and how to deploy them in different docker images. We will also add some microservices communicating with Akka so… stay tuned!!!

Any comment and suggestion will be greatly appreciated!!!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s