Easy Boarding with Kotlin and PCF

In my last post, we built a simple microservice and got quick and easy lift off to the cloud with the combination of Kotlin, Spring Cloud and PCF. This is great but there’s no point taking off and not carrying any passengers. Thankfully, with PCF auto-reconfiguration, there’s no more queues in the departure lounge. So, to show this, I’m going to take the simple expenses-service that we built before and add capacity for it to cruise along carrying data.

Thanks to Spring Boot, adding an in-memory db using H2 is a breeze. Thanks to PCF, replacing this with a MySql db in the cloud is almost given to us for free!

All the source code for this is again just in one Kotlin file ExpensesServiceApplication.kt. For the purpose of this little demo service, this is fine. The complete ExpensesServiceApplication.kt file is included at the end of this post.

First of all we need a few changes to the pom.xml to add the spring data jpa and H2 dependencies.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
</dependency>

and the Kotlin JPA compiler plugin so the complete Kotlin plugin configuration looks like this:

<plugins>
    <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    <plugin>
        <groupId>org.jetbrains.kotlin</groupId>
        <artifactId>kotlin-maven-plugin</artifactId>
        <configuration>
            <args>
                <arg>-Xjsr305=strict</arg>
            </args>
                <compilerPlugins>
                    <plugin>spring</plugin>
                    <plugin>jpa</plugin>
               </compilerPlugins>
        </configuration>
       <dependencies>
            <dependency>
                <groupId>org.jetbrains.kotlin</groupId>
                <artifactId>kotlin-maven-noarg</artifactId>
                <version>${kotlin.version}</version>
            </dependency>
            <dependency>
                 <groupId>org.jetbrains.kotlin</groupId>
                 <artifactId>kotlin-maven-allopen</artifactId>
                 <version>${kotlin.version}</version>
            </dependency>
       </dependencies>
    </plugin>
</plugins>

With the H2 dependency in place, Spring Boot will detect this on start up during its classpath auto configuration scan. It will create the correct beans required to interact with H2. It also provides a H2 console so we’ll be able to run queries locally with ease. To enable this and to specify the path to the H2 console, we need the following in application.properties:

spring.h2.console.enabled=true
spring.h2.console.path=/h2

Spring Data really simplifies interacting with the db. I still like to de-couple db Entity objects from the core domain model. Its good for security and it keeps the core of the microservice simple without JPA @Entity and other annotations leaking everywhere. For the purpose of this demo service, I have all the code in one source file, but the JPA specific code could easily be extracted to another Maven module and plugged in. To enable this, let’s define an interface for our interactions with the whatever storage mechanism we want to use.

interface ExpenseRepository {
fun save(expense: Expense)
fun retrieveAll() : List<Expense>
}

For now, our repository will just support saving one expense and retrieving all expenses to keep things simple.

Now we can encapsulate all JPA related persistence code into one class. Again, if this microservice were to get any more complicated, this encapsulation would be better done using a separate Maven module.

@Repository
class ExpenseJpaRepository(@PersistenceContext private val entityManager: EntityManager) : ExpenseRepository {

enum class ExpenseType {
Meals, Travel, Fuel
}

@Entity
private class ExpenseEntity(
@Enumerated(EnumType.STRING)
val expenseType: ExpenseType,
val employeeId: UUID,
val submissionDate: ZonedDateTime,
val amount: BigDecimal,
val origin: String? = null,
val destination: String? = null,
@Enumerated(EnumType.STRING)
val mode: Mode? = null,
val distanceInKM: Int? = null) {

@Id
@GeneratedValue
var id: Long? = null
}

@Transactional
override fun save(expense: Expense) {
entityManager.persist(expense.toExpenseEntity())
}

override fun retrieveAll(): List<Expense> {
val criteria = entityManager.criteriaBuilder.createQuery(ExpenseEntity::class.java)
return entityManager.createQuery(criteria.select(criteria.from(ExpenseEntity::class.java)))
.resultList
.map { it.toExpense() }
}

private fun Expense.toExpenseEntity() : ExpenseEntity =
when(this) {
is Meals ->
ExpenseEntity(ExpenseType.Meals,
employeeId,
submissionDate,
amount)
is Travel ->
ExpenseEntity(ExpenseType.Travel,
employeeId,
submissionDate,
amount,
origin,
destination,
mode)
is Fuel ->
ExpenseEntity(ExpenseType.Fuel,
employeeId,
submissionDate,
amount,
distanceInKM = distanceInKM)
}

private fun ExpenseEntity.toExpense() : Expense {
fun <T> checkNonNull(fieldName: String, t: T?): T = t ?: throw RuntimeException("Non null field $fieldName has been stored and retrieved as null")

return when (this.expenseType) {
ExpenseType.Meals -> Meals(employeeId, submissionDate, amount)
ExpenseType.Travel -> Travel(employeeId, submissionDate, amount, checkNonNull("origin", origin), checkNonNull("destination", destination), checkNonNull("mode", mode))
ExpenseType.Fuel -> Fuel(employeeId, submissionDate, amount, checkNonNull("distanceInKm", distanceInKM))
}
}
}

One thing to note above is that the ExpenseEntity is a type specific to this ExpenseJpaRepository and it is kept very simple as just a representation of a flat row in the expense db table. There is a little bit of converting to and from the core Expense domain model and the ExpenseEntity but, thanks to Kotlin extension functions, this is very quick and easy to implement. I didn’t override equals and hashcode on ExpenseEntity as this can have complications around choosing properties and having to consider that instances can exist inside and outside of the JPA persistence context. It is well encapsulated and used purely to save and retrieve raw rows in the db. It’s never used for any business logic.

I used EntityManager directly in the above code. Spring Data makes CRUD operations far easier by providing interfaces such as CrudRepository. However using that here would have meant having to expose the ExpenseEntity type outside of the ExpenseJpaRepository. This wouldn’t matter if this were all separated into a separate module as the encapsulation would be at the module level.

So, great we have a couple of CRUD operations on the db. We need to provide a Restful gateway into these. The ExpenseController is very similar to how it was in my previous blog post. Now it is going to have an ExpenseRepository property which Spring Boot will kindly inject for us because we have annotated the ExpenseJpaRepository with @Repository above.

@RestController
@RequestMapping("/expenses")
class ExpenseController(val expenseRepository: ExpenseRepository) {

There’s also now two endpoints for saving a expense and retrieving all expenses.

@GetMapping
fun getExpenses(): List<ExpenseDTO> {
return expenseRepository.retrieveAll().map { it.toDto() }
}

@PostMapping
fun postExpenses(@RequestBody expenseDTO: ExpenseDTO) {
expenseDTO.toExpense().let(expenseRepository::save)
}

There’s a bit of converting to and from the core domain model to DTOs. Also, because we are serialising and deserialising to and from children of a sealed class, we can use some handy Jackson annotations to take care of this for us. So our DTO ADT (algebraic data type) now looks like this:

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes(
value = [
JsonSubTypes.Type(value = ExpenseDTO.MealsDTO::class, name = "meals"),
JsonSubTypes.Type(value = ExpenseDTO.TravelDTO::class, name = "travel"),
JsonSubTypes.Type(value = ExpenseDTO.FuelDTO::class, name = "fuel")
]
)

sealed class ExpenseDTO {
data class MealsDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : ExpenseDTO()
data class TravelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : ExpenseDTO()
data class FuelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : ExpenseDTO()
}

With all this in place (full source code is at the end of this blog post), we can crank up the microservice by running the main function in ExpensesServiceApplication.

With the expenses-service running, navigate to the H2 Console using http://localhost:8080/h2 . We get a login screen to log into H2. Use user “sa” and leave password blank. For the JDBC url we need, jdbc:h2:mem:testdb

After logging, in we can see our EXPENSE_ENTITY table.

We can execute sql queries or, to simply do a select *, we can just click into the table name and click Run. Either way, we will see that there is nothing in that table yet.

To get an expense in there, we can hit our POST endpoint with whatever you’re favourite Http Client is. I used POSTMAN to submit the following POST requests.

localhost:8080/expenses

{
“type”: “travel”,
“employeeId”: “80d4d699-019a-44db-b1ce-b5ad569e5daf”,
“submissionDate”: “2020-02-01T21:05:18.762655Z”,
“amount”: 343,
“origin”: “London”,
“destination”: “Cork”,
“mode”: “Air”
}

localhost:8080/expenses

{
“type”: “fuel”,
“employeeId”: “80d4d699-019a-44db-b1ce-b5ad569e5daf”,
“submissionDate”: “2020-02-01T21:05:18.762655Z”,
“amount”: 187,
“distanceInKM”: 870
}

localhost:8080/expenses

{
“type”: “meals”,
“employeeId”: “80d4d699-019a-44db-b1ce-b5ad569e5daf”,
“submissionDate”: “2020-01-18T21:05:18.762655Z”,
“amount”: 63
}

After submitting those, request we can now, query our H2 db and see the following results:

We can also now perform a GET request in POSTMAN or any http client:

localhost:8080/expenses

And see the following response:

[
{
"type": "travel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18.762655Z",
"amount": 343.00,
"origin": "London",
"destination": "Cork",
"mode": "Air"
},
{
"type": "fuel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18.762655Z",
"amount": 187.00,
"distanceInKM": 870
},
{
"type": "meals",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-01-18T21:05:18.762655Z",
"amount": 63.00
}
]

So, getting a microservice running locally with a H2 db is great for rapid development but it’s useless if its not in production. Luckily PCF makes it easy to get a MySql db up and running and to have our code connect to it without any code changes and barely any configuration. Once we log into the PCF console like we did in my previous blog post, we can click into “Marketplace.

Then we need to search for “mysql” – typing “mys” will do. Then choose “ClearDB MySQL Database” as below.

Then choose the free option.


Click to select this plan.

Now we can give the mysql service a name and we need to bind it to our expenses-service. This can be automated by creating Manifest.yml in our project. However, lets simply add it through the PCF console for now.

Then we can click to create the service and we’ll see the following:

So our db is up and running! Its also possible to get the db connection details so you can connect with a db tool of your choice.

All that remains now is to add a tiny bit of config to our microservice and to build and push it to PCF. In application.properties we need the following:


spring.jpa.hibernate.ddl-auto=update

Then in the root of our project, we can build the jar by running mvn clean install

To push to PCF, navigate to the target directory and run:

cf push expenses-service -p expenses-service-0.0.1-SNAPSHOT.jar

We then get out put similar to the following:


Pushing app expenses-service to org demo-1000 / space development as tom@tompriordev.com...
Getting app info...
Updating app with these attributes...
  name:                expenses-service
  path:                /Users/priort/d/expenses-service/target
  command:             JAVA_OPTS="-agentpath:$PWD/.java-buildpack/open_jdk_jre/bin/jvmkill-1.16.0_RELEASE=printHeapHistogram=1 -Djava.io.tmpdir=$TMPDIR -XX:ActiveProcessorCount=$(nproc) -Djava.ext.dirs=$PWD/.java-buildpack/container_security_provider:$PWD/.java-buildpack/open_jdk_jre/lib/ext -Djava.security.properties=$PWD/.java-buildpack/java_security/java.security $JAVA_OPTS" &amp;&amp; CALCULATED_MEMORY=$($PWD/.java-buildpack/open_jdk_jre/bin/java-buildpack-memory-calculator-3.13.0_RELEASE -totMemory=$MEMORY_LIMIT -loadedClasses=13612 -poolType=metaspace -stackThreads=250 -vmOptions="$JAVA_OPTS") &amp;&amp; echo JVM Memory Configuration: $CALCULATED_MEMORY &amp;&amp; JAVA_OPTS="$JAVA_OPTS $CALCULATED_MEMORY" &amp;&amp; MALLOC_ARENA_MAX=2 SERVER_PORT=$PORT eval exec $PWD/.java-buildpack/open_jdk_jre/bin/java $JAVA_OPTS -cp $PWD/. org.springframework.boot.loader.JarLauncher
  disk quota:          1G
  health check type:   port
  instances:           1
  memory:              1G
  stack:               cflinuxfs3
  services:
    mysql-instance
  routes:
    expenses-service.cfapps.io

Updating app expenses-service...

Most interestingly, we’ll see something like below in the output:

   -----&gt; Downloading Client Certificate Mapper 1.11.0_RELEASE from https://java-buildpack.cloudfoundry.org/client-certificate-mapper/client-certificate-mapper-1.11.0-RELEASE.jar (found in cache)
   -----&gt; Downloading Container Security Provider 1.16.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-security-provider/container-security-provider-1.16.0-RELEASE.jar (found in cache)
   -----&gt; Downloading Maria Db JDBC 2.5.1 from https://java-buildpack.cloudfoundry.org/mariadb-jdbc/mariadb-jdbc-2.5.1.jar (found in cache)
   -----&gt; Downloading Spring Auto Reconfiguration 2.11.0_RELEASE from https://java-buildpack.cloudfoundry.org/auto-reconfiguration/auto-reconfiguration-2.11.0-RELEASE.jar 

Because we have a mysql service bound to our expenses-service, PCF knows that it needs a JDBC driver for MySql (Maria DB). It also knows that it needs to do what is called “Auto Reconfiguration”. This means that it automatically configures the expenses-service to be able to connect to the MySql db we created earlier. This is seriously powerful and handy especially for quick prototyping and showing demos to customers! Fear not though, its also possible to add a lot more configuration in terms of db connection pools etc.

After the expenses-service has been deployed on PCF, we will see something like this:

     state     since                  cpu    memory         disk           details
#0   running   2020-02-02T11:56:24Z   0.0%   162.7M of 1G   150.9M of 1G   

Now we can submit HTTP requests as before except this time we can hit the endpoints of our expenses-service up in the cloud! We can submit POST requests as below. The URL will vary depending on the url given for your own deployment. The URL is configured as a "route" in PCF and can be found by going back through the output from the cf push we just ran and looking for something like:

routes:
    expenses-service.cfapps.io

Now, lets submit a POST request to create a travel expense: POST expenses-service.cfapps.io/expenses

{ 
    "type": "travel",
    "employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
    "submissionDate": "2020-02-01T21:05:18.762655Z",
    "amount": 343,
    "origin": "London",
    "destination": "Shannon",
    "mode": "Air" 
}

And a GET request gives us back all the expenses that we have already stored. I saved one earlier so that is also on the response below. GET expenses-service.cfapps.io/expenses

[
{
"type": "travel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18Z",
"amount": 343.00,
"origin": "London",
"destination": "Cork",
"mode": "Air"
},
{
"type": "travel",
"employeeId": "80d4d699-019a-44db-b1ce-b5ad569e5daf",
"submissionDate": "2020-02-01T21:05:18Z",
"amount": 343.00,
"origin": "London",
"destination": "Shannon",
"mode": "Air"
}
]

So, there we have it – the combination of Kotlin, Spring Cloud and PCF simplifying what it takes to build a microservice, bind a db to it and have the whole lot running in the cloud with minimal fuss!

Complete ExpensesServiceApplication.kt

package com.somecompany.financials.expensesservice

import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.data.repository.CrudRepository
import org.springframework.stereotype.Repository
import org.springframework.web.bind.annotation.*
import java.lang.RuntimeException
import java.math.BigDecimal
import java.time.*
import java.util.*
import java.util.concurrent.ConcurrentLinkedQueue
import javax.persistence.*
import javax.transaction.Transactional

@SpringBootApplication
class ExpensesServiceApplication

fun main(args: Array<String>) {
runApplication<ExpensesServiceApplication>(*args)
}

sealed class Expense
data class Meals(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : Expense()
data class Travel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : Expense()
data class Fuel(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : Expense()

enum class Mode() {
Air, Rail, Sea, Taxi, RentedCar
}


@RestController
@RequestMapping("/expenses")
class ExpenseController(val expenseRepository: ExpenseRepository) {

private val expenses = ConcurrentLinkedQueue(
setOf( Meals( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2020, 1, 15, 16, 30), ZoneOffset.UTC),
23.toBigDecimal()),
Travel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 12, 14, 13, 12), ZoneOffset.UTC),
12.toBigDecimal(),
"Cork",
"London",
Mode.Air),
Fuel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 11, 19, 16, 30), ZoneOffset.UTC),
45.toBigDecimal(),
45),
Travel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 12, 1, 9, 37), ZoneOffset.UTC),
12.toBigDecimal(),
"Limerick",
"Dublin",
Mode.Rail),
Fuel( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 2, 1, 16, 30), ZoneOffset.UTC),
12.toBigDecimal(),
45),
Meals( UUID.randomUUID(),
ZonedDateTime.of(LocalDateTime.of(2019, 9, 4, 21, 10), ZoneOffset.UTC),
14.toBigDecimal())))

@GetMapping
fun getExpenses(): List<ExpenseDTO> {
return expenseRepository.retrieveAll().map { it.toDto() }
}

@PostMapping
fun postExpenses(@RequestBody expenseDTO: ExpenseDTO) {
expenseDTO.toExpense().let(expenseRepository::save)
}

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes(
value = [
JsonSubTypes.Type(value = ExpenseDTO.MealsDTO::class, name = "meals"),
JsonSubTypes.Type(value = ExpenseDTO.TravelDTO::class, name = "travel"),
JsonSubTypes.Type(value = ExpenseDTO.FuelDTO::class, name = "fuel")
]
)

sealed class ExpenseDTO {
data class MealsDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal) : ExpenseDTO()
data class TravelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val origin: String, val destination: String, val mode: Mode) : ExpenseDTO()
data class FuelDTO(val employeeId: UUID, val submissionDate: ZonedDateTime, val amount: BigDecimal, val distanceInKM: Int) : ExpenseDTO()
}

private fun Expense.toDto() =
when (this) {
is Meals -> ExpenseDTO.MealsDTO(employeeId, submissionDate, amount)
is Travel -> ExpenseDTO.TravelDTO(employeeId, submissionDate, amount, origin, destination, mode)
is Fuel -> ExpenseDTO.FuelDTO(employeeId, submissionDate, amount, distanceInKM)
}

private fun ExpenseDTO.toExpense() =
when (this) {
is ExpenseDTO.MealsDTO -> Meals(employeeId, submissionDate, amount)
is ExpenseDTO.FuelDTO -> Fuel(employeeId, submissionDate, amount, distanceInKM)
is ExpenseDTO.TravelDTO -> Travel(employeeId, submissionDate, amount, origin, destination, mode)
}
}

interface ExpenseRepository {
fun save(expense: Expense)
fun retrieveAll() : List<Expense>
}

@Repository
class ExpenseJpaRepository(@PersistenceContext private val entityManager: EntityManager) : ExpenseRepository {

enum class ExpenseType {
Meals, Travel, Fuel
}

@Entity
private class ExpenseEntity(
@Enumerated(EnumType.STRING)
val expenseType: ExpenseType,
val employeeId: UUID,
val submissionDate: ZonedDateTime,
val amount: BigDecimal,
val origin: String? = null,
val destination: String? = null,
@Enumerated(EnumType.STRING)
val mode: Mode? = null,
val distanceInKM: Int? = null) {

@Id
@GeneratedValue
var id: Long? = null
}

@Transactional
override fun save(expense: Expense) {
entityManager.persist(expense.toExpenseEntity())
}

override fun retrieveAll(): List<Expense> {
val criteria = entityManager.criteriaBuilder.createQuery(ExpenseEntity::class.java)
return entityManager.createQuery(criteria.select(criteria.from(ExpenseEntity::class.java)))
.resultList
.map { it.toExpense() }
}

private fun Expense.toExpenseEntity() : ExpenseEntity =
when(this) {
is Meals ->
ExpenseEntity(ExpenseType.Meals,
employeeId,
submissionDate,
amount)
is Travel ->
ExpenseEntity(ExpenseType.Travel,
employeeId,
submissionDate,
amount,
origin,
destination,
mode)
is Fuel ->
ExpenseEntity(ExpenseType.Fuel,
employeeId,
submissionDate,
amount,
distanceInKM = distanceInKM)
}

private fun ExpenseEntity.toExpense() : Expense {
fun <T> checkNonNull(fieldName: String, t: T?): T = t ?: throw RuntimeException("Non null field $fieldName has been stored and retrieved as null")

return when (this.expenseType) {
ExpenseType.Meals -> Meals(employeeId, submissionDate, amount)
ExpenseType.Travel -> Travel(employeeId, submissionDate, amount, checkNonNull("origin", origin), checkNonNull("destination", destination), checkNonNull("mode", mode))
ExpenseType.Fuel -> Fuel(employeeId, submissionDate, amount, checkNonNull("distanceInKm", distanceInKM))
}
}
}

One Reply to “Easy Boarding with Kotlin and PCF”

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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s