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

Intro

In the previous post we have built the scaffold of our microservice project, implementing the Eureka registration service. Now we will begin ti add some real functionality, beginning (surprisingly) with the data access. Only the crucial aspects of the implementation will be covered here, so you may download the full code from the repository:

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

persistence-service

For this tutorial we will create a simple entity, User, with the basic CRUD operations. We will use HsqlDb as in-memory database and JPA for ORM-mapping, with the utilities offered by Spring-Data. Let’s begin adding the module declaration to the parent pom:

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

This is the persistence-service pom:

<?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>persistenceservice</artifactId>

    <properties>
        <start-class>net.microservices.tutorial.persistenceservice.PersistenceServer</start-class>
        <finalName>registrationservice-${project.version}</finalName>
        <service.port>2222</service.port>
        <container.ip>localhost</container.ip>
        <rs.port>1111</rs.port>
        <rs.ip>localhost</rs.ip>
    </properties>

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

    <build>
        <finalName>${finalName}</finalName>
    </build>

</project>

Inside the resources directory we will put all the html templates used by Thymeleaf and the Spring configurations file: bootstrap.yml and application.yml:

persistenceservice/resources/bootstrap.yml:

# Spring properties
# Service registers under this name
spring:
  application:
      name: persistence-service

persistenceservice/resources/application.yml:

# Spring properties
spring:
  # Ignore Eureka dashboard FreeMarker templates
  freemarker:
      enabled: false
  # Allow Thymeleaf templates to be reloaded at runtime
  thymeleaf:
       cache: false
  # Trailing / mandatory
  # Template location for this application only
       prefix: classpath:/templates/

  # Database configuration
  # Spring Boot automatically creates a JPA EntityManagerFactory using Hibernate
  # but we need to override some defaults:
  #
  #   1. Stop Hibernate automatically creating a schema, we are doing it in
  #      schema.sql. Instead check the tables match their JPA mapped classes
  jpa:
    hibernate:
      ddl-auto: none
      naming_strategy: org.hibernate.cfg.ImprovedNamingStrategy
    database: HSQL
    show-sql: true

# Map the error path to error template (for Thymeleaf)
error:
  path: /error

# HTTP Server
# HTTP (Tomcat) port
server:
  port:  ${SERVICE_PORT:@service.port@}
eureka:
  client:
    serviceUrl:
      defaultZone: http://${RS_PORT_1111_TCP_ADDR:@rs.ip@}:${RS_PORT_1111_TCP_PORT:@rs.port@}/eureka/
  instance:
    preferIpAddress: true
    # DO NOT DO THIS IN PRODUCTION
    leaseRenewalIntervalInSeconds: 5

#Test db
testDatasource:
  url: jdbc:hsqldb:file:testdb
  username: SA
  password:
  driverClassName: org.hsqldb.jdbcDriver

# Disabling security for Actuator' REST services
security:
  basic:
    enabled: false
management:
  security:
    enabled: false

code

Since this tutorial is focused on the microservices architecture, I will not explain all the details of this configuration: fell free to ask for further explanation, if needed.
Please note:

  • port: ${SERVICE_PORT:@service.port@}: this is the listening port of the application; as for the registration-server, will be injected at compile time;
  • defaultZone:http://${RS_PORT_1111_TCP_ADDR:@rs.ip@}:${RS_PORT_1111_TCP_PORT:@rs.port@}/eureka/: this is the address of the registration service; RS_PORT_1111_TCP_ADDR and RS_PORT_1111_TCP_PORT will be dynamically injected by the fabric8 plugin during image building (more about them in the image configuration), otherwise rs.ip and rs.port (defined in the persistenceservice pom.xml)will be used
  • preferIpAddress: true: this is needed mostly for applications running inside docker containers; without this, the service will register itself with the container id, so it could not be attainable by the other services

persistenceservice/src/main/kotlin/net/microservices/tutorial/persistenceservice/PersistenceServer.kt

@file:JvmName("PersistenceServer")
package net.microservices.tutorial.persistenceservice


import net.microservices.tutorial.persistenceservice.configurations.PersistenceConfiguration
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.cloud.client.discovery.EnableDiscoveryClient
import org.springframework.context.annotation.Import

import java.util.logging.Logger

/**
 * Run as a micro-service, registering with the Discovery Server (Eureka).
 *
 *
 * Note that the configuration for this application is imported from
 * [PersistenceConfiguration]. This is a deliberate separation of concerns.
 */
@EnableAutoConfiguration
@EnableDiscoveryClient
@Import(PersistenceConfiguration::class)
open class PersistenceServer {

    protected var logger = Logger.getLogger(PersistenceServer::class.java.name)

    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(PersistenceServer::class.java, *args)
        }
    }
}

This is the application entry point. As for the registration service, there is a single annotation needed to transform the module in an Eureka client:

@EnableDiscoveryClient

With this in place, we are telling Spring to instantiate the class as a Eureka client, taking the properties from the *.yml files.

image creation

What is left now is to configure the image to be built inside the docker pom docker/pom.xml: first, as the dependency on the persistence service:

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

then add this just below the registration service’ one:
docker/pom.xml:

...
                        </image>
            <!-- Persistence service -->
                        <image>
                            <!-- Alias name which can used for linking containers during runtime -->
                            <alias>persistenceservice</alias>
                            <name>${docker.repo}/persistence-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}/../persistenceservice/src/main/fabric8/assembly.xml</descriptor>
                                </assembly>
                                <!-- Expose ports -->
                                <ports>
                                    <port>${persistence.service.port}</port>
                                </ports>
                                <!-- Default command for the build image -->
                                <cmd>java -Djava.security.egd=file:/dev/./urandom -jar /maven/persistenceservice.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>${persistence.service.port}:${persistence.service.port}</port>
                                </ports>
                                <env>
                                    <SERVICE_PORT>${persistence.service.port}</SERVICE_PORT>
                                </env>
                                <wait>
                                    <!-- Check for this URL to return a 200 return code .... -->
                                    <url>http://${docker.host.address}:${persistence.service.port}/</url>
                                     <!-- ... but at max 30 seconds -->
                                    <time>30000</time>
                                </wait>
                                <dependsOn>
                                    <container>
                                        registrationservice
                                    </container>
                                </dependsOn>
                                <links>
                                    <!-- Links can be referenced via alias (registrationservice) or name (${docker.repo}/registration-service:${project.version}) -->
                                    <!-- THIS SHOULD CREATE SOME RS_XXX variables -->
                                    <link>registrationservice:rs</link>
                                </links>
                                <log>
                                    <prefix>TC</prefix>
                                    <color>cyan</color>
                                </log>
                            </run>
                        </image>
                    </images>
...

This is almost identical to the registration service, but there are a couple of new tags:

  • dependsOn: this is to indicate the container dependency, i.e. we are telling to the plugin that the container for this image requires the registrationservice container
  • links: this is needed to inject in the persistence service container some properties of the registration service one

In the previous post I have explained how to inject properties inside container as environment variables, and that some of them are automatically generated and injected by the fabric8 plugin. In the documentation you will find more explanation; simply put, with  &lt;link&gt;registrationservice:rs&lt;/link&gt; we are telling to the plugin to use “rs” as prefix for some automatically generated properties, with the following convention (everything upper-case):

(prefix)_PORT=(protocol)://(container_ip):(exposed_port_number)
(prefix)_PORT_(port_number)_TCP=tcp://(container_ip):(port_number)
(prefix)_PORT_(port_number)_TCP_PROTO=tcp
(prefix)_PORT_(port_number)_TCP_PORT=(port_number)
(prefix)_PORT_(port_number)_TCP_ADDR=(container_ip)

For the current project, the port number is 1111 (registration.service.port), the prefix is rs, and let’s say that the registration container ip is 172.17.0.2. Following the above rules, we will have:


RS_PORT=tcp://172.17.0.2:1111
RS_PORT_1111_TCP=tcp://172.17.0.2:1111
RS_PORT_1111_TCP_PROTO=tcp
RS_PORT_1111_TCP_PORT=1111
RS_PORT_1111_TCP_ADDR=172.17.0.2

This properties will be injected as environment variables in the persistence service container. Now, if you look back at application.yml, you will find exactly these:

...
defaultZone: http://${RS_PORT_1111_TCP_ADDR}:${RS_PORT_1111_TCP_PORT}/eureka/
...

Now, calling from the root directory

 
mvn install 

We should be able to build all the modules and the two images.
Last, let’s start them all. Go to the docker directory and type

mvn docker:start

We should have:

the two  containers up and running


# docker ps
CONTAINER ID IMAGE                        COMMAND                CREATED            STATUS            PORTS                  NAMES
36909d6973b0 ***/persistence-service:0.1 "/bin/sh -c 'java -Dj"  About a minute ago Up About a minute 0.0.0.0:2222->2222/tcp distracted_varahamihira
3f8bebf677ca ***/registration-service:0.1 "/bin/sh -c 'java -Dj" 2 minutes ago      Up About a minute 0.0.0.0:1111->1111/tcp jovial_galileo


the registration service  here

the persistence service here

the persistence service registered inside the registration service

some REST for CRUD operation

Conclusion

Well, feel free to look in the code to see what REST services are implemented
To clone the project:

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

In the next part we will add another Eureka client container that will implement a long time request service so… stay tuned!!!

Any comment and suggestion will be greatly appreciated!!!

Advertisements

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