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

Intro

In the previous post we have add a microservice that returns data slowly,the timeconsuming one. Now we will add the service that query with other twos to show data. We can pretend this is the “frontend” layer, maybe (in real application) full of javascript magics! 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-4.git

configuration-service

Let’s begin adding the module declaration to the parent pom:

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

This is the configuration-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>configurationservice</artifactId>

    <properties>
        <start-class>net.microservices.tutorial.configurationservice.ConfigurationServer</start-class>
        <finalName>configurationservice-${version}</finalName>
        <service.port>4444</service.port>
        <container.ip>localhost</container.ip>
        <rs.port>1111</rs.port>
        <rs.ip>localhost</rs.ip>
        <ps.port>2222</ps.port>
        <ps.ip>localhost</ps.ip>
        <ts.port>3333</ts.port>
        <ts.ip>localhost</ts.ip>
    </properties>
    <build>
        <finalName>${finalName}</finalName>
    </build>
    <dependencies>
        <dependency>
            <groupId>net.microservices.tutorial</groupId>
            <artifactId>servicecommon</artifactId>
            <version>0.1</version>
        </dependency>
    </dependencies>
</project>

code

In this module we will actually use the Eureka architecture to

  1. ask the registrar for the address of the first available instance of the target service
  2. send request to the found instance of the target service

net/microservices/tutorial/configurationservice/resources/application.yml:

...
   persistenceservice:
  url: http://PERSISTENCE-SERVICE
timeconsumingservice:
  url: http://TIMECONSUMING-SERVICE
...

This is the first piece of the implementation: we are defining two properties to map the name of the services we want to talk with. As you may recall, this is the same name (upper case) we have defined for the services with the directive spring.application.name in the bootstrap.yml files.This properties are injected in the configuration file (forgive the naming! ):
net/microservices/tutorial/configurationservice/configurations/ConfigurationConfiguration.kt:

...
     @Value("\${persistenceservice.url}")
    var persistenceServiceUrl :String = ""

    @Value("\${timeconsumingservice.url}")
    var timeConsumingserviceUrl :String = ""

    /**
     * A customized RestTemplate that has the ribbon load balancer build in.
     * @return
     */
    @LoadBalanced
    @Bean
    open fun restTemplate(): RestTemplate {
        return RestTemplate()
    }

    /**
     * A customized RestTemplate that has the ribbon load balancer build in.
     * @return
     */
    @LoadBalanced
    @Bean
    open fun asyncRestTemplate(): AsyncRestTemplate {
        return AsyncRestTemplate()
    }
...

Please note also the AsyncRestTemplate, more about it later.

The service class forward the requests coming from the controller to the target -remote – destinations:

net/microservices/tutorial/configurationservice/services/UsersService.kt:

...
    @Service
class UsersService(persistenceServiceUrl: String, timeConsumingserviceUrl: String) {

    @Autowired
    @LoadBalanced
    private var restTemplate: RestTemplate? = null

    @Autowired
    @LoadBalanced
    private var asyncRestTemplate: AsyncRestTemplate? = null

    private var persistenceServiceUrl: String

    private var timeConsumingserviceUrl: String

    private var logger = Logger.getLogger(UsersService::class.java.name)

    init {
        this.persistenceServiceUrl = if (persistenceServiceUrl.startsWith("http"))
            persistenceServiceUrl
        else
            "http://" + persistenceServiceUrl
        this.timeConsumingserviceUrl = if (timeConsumingserviceUrl.startsWith("http"))
            timeConsumingserviceUrl
        else
            "http://" + timeConsumingserviceUrl
    }

    /**
     * The RestTemplate works because it uses a custom request-factory that uses
     * Ribbon to look-up the service to use. This method simply exists to show
     * this.
     */
    @PostConstruct
    fun demoOnly() {
        // Can't do this in the constructor because the RestTemplate injection
        // happens afterwards.
        logger.warning("The RestTemplate request factory is " + restTemplate!!.requestFactory.javaClass)
    }

    @Throws(Exception::class)
    fun findAll(): List<UserDTO>? {
        logger.info("findAll() invoked")
        var users: Array<UserDTO>? = null
        try {
            users = restTemplate!!.getForObject(persistenceServiceUrl + "/persons/", Array<UserDTO>::class.java)
        } catch (e: HttpClientErrorException) { // 404
            // Nothing found
            return null
        }
        if (users == null || users.size == 0)
            return null
        else
            return Arrays.asList(*users)
    }
...

    @Throws(Exception::class)
    fun findAsyncByNumber(id: Int): UserDTO? {
        logger.info("findAsyncByNumber() invoked")
        var user: UserDTO? = null
        try {
            val method = HttpMethod.GET
            val responseType = genericClass<UserDTO>()
            //create request entity using HttpHeaders
            val headers = HttpHeaders()
            headers.contentType = MediaType.TEXT_PLAIN
            val requestEntity = HttpEntity<String>("params", headers)
            val future = asyncRestTemplate?.exchange(timeConsumingserviceUrl + "/deferredpersons/{id}", method, requestEntity, responseType, id)
            //waits for the result
            val entity = future?.get()
            //prints body source code for the given URL
            user = entity?.body
        } catch (e: Exception) {
            logger.log(Level.SEVERE, e.message, e)
        }
        return user
    }
    
    private inline fun <reified T : Any> genericClass(): Class<T> = T::class.java
...

Well, there is something going on here.
First, we inject the services “url” in the contructor and use this to set the two instance variables persistenceServiceUrl and timeConsumingserviceUrl.
Next, we define the two variables restTemplate and asyncRestTemplate as Autowired and LoadBalanced. This last annotation tells to the RestTemplate to use a LoadBalancerClient. With this in place, each service’ request is forwarded to the first available instance of the target type of destination.
The AsyncRestTemplate is used to talk to the timeconsuming service because it return a ListenableFuture, that is what we need to make this type of requests.

The controller is like the others, so I will skip the details

image creation

Let’s update the docker configuration.
docker/pom.xml:

...
 <properties>
      ...
        <!-- services properties -->
        <registration.service.port>1111</registration.service.port>
        <persistence.service.port>2222</persistence.service.port>
        <timeconsuming.service.port>3333</timeconsuming.service.port>
        <configuration.service.port>4444</configuration.service.port>
    </properties>
</properties>

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

This time, in the image definition we will add three links:

...
registrationservice:rs, persistenceservice:ps, timeconsumingservice:ts
...

With this definition, the fabric8 plugin will inject a bunch of RS_XXX, PS_XXX and TS_XXX variables. Actually, for our project only the RS_XXX ones are needed, because the configuration service never call directly the others, but instead “ask” the address to the registration service.

After installing the artifacts and starting docker, we should have the configuration service registered:

and the image running here

with the persistence service requests

and the timeconsuming ones (of course, actual data will vary)

Conclusion

Well, you will find the image definition (and all the other missing stuff) in the project:

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

In the next part we will begin to add Akka services 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