How to Create Microservices Using http4k

In this article, we will build a simple microservice based on Kotlin, the http4k toolkit , and JDBI.

Our Use Case

Let's consider a simple functionality: registering companies and generating a ranking system. Each company has a score based on their foundation date. Companies are classified by their score, so we have a simple solution, as described below:

article image       

The above diagram shows a simple service with it own database. We are assuming that the Ranking Web (UI) Application will interact with Company Service to show the company list. Let's take a look at some design points:

Getting Started

We will start from the repository, ranking-service. All boilerplate code is provided in this initial repository. Let's build the registration operation. First, when we create a company record, we need to set the company's score in our ranking system. The score calculation is based on its foundation date, so we have some rules for that:

Once the company is registered, we can perform an evaluation using the following:

Those values must be cumulative, and only allowed to go up, not down, based on votes. The company table should look like:

Our entity's code looks like the following:

Kotlin
 




x
13


1
import java.time.LocalDate
2
import java.time.LocalDateTime
3
 
          
4
data class Company(
5
    val id: String,
6
    val name: String,
7
    val site: String,
8
    val foundation: LocalDate,
9
    val score: Char,
10
    val likes: Int = 0,
11
    val dislikes: Int = 0,
12
    val createdAt: LocalDateTime = LocalDateTime.now()
13
)


Operations

Our service must provide three operations, as shown in the table below:

Register a New Company

Now we need to implement our company registration functionality. Let's implement our HTTP handler that represents the first operation shown in the table above. First, let's see the code below:

Kotlin
 




x


 
1
// creates application routes
2
fun routes(): RoutingHttpHandler = routes(
3
    "/" bind GET to index(),
4
    "/v1/companies" bind routes(
5
        "/" bind POST to registerHandler(service),
6
        "/" bind GET to rankingHandler(service),
7
        "/vote" bind PATCH to voteHandler(service)
8
    )
9
)
10
 
          
11
fun registerHandler(service: CompanyService): HttpHandler {
12
    val registerData = Body.auto<RegisterRequest>().toLens()
13
    return { request ->
14
        val data = registerData(request)
15
 
          
16
        when(val result = service.register(data)) {
17
            is Success -> Response(Status.CREATED).body(result.value)
18
            is Fail -> Response(Status.BAD_REQUEST).body(result.cause)
19
        }
20
    }
21
}


Inside the Router.kt file, we're defining a function, registerHandler, that receives a service as an argument. http4k defines a Handler like a function: (Request) → Response. Now, let's see the register function from the CompanyService class.

Kotlin
 




xxxxxxxxxx
1
21


 
1
fun register(data: RegisterRequest): Result<String> {
2
    val foundationCompanyDate = LocalDate.parse(data.foundation, DateTimeFormatter.ofPattern(DATE_FORMATTER))
3
  
4
    val companyScore = calculateScore(foundationCompanyDate)
5
    if (companyScore == '0') return Fail("This company is too much younger.")
6
 
          
7
    val currentCompany = repository.fetchBySite(data.site)
8
    if (currentCompany != null) return Fail("There is a company with same site.")
9
 
          
10
    val entity = Company(
11
        id = UUID.randomUUID().toString(),
12
        name = data.name,
13
        site = data.site,
14
        foundation = foundationCompanyDate,
15
        score = companyScore
16
    )
17
 
          
18
    return when(val result = repository.save(entity)) {
19
        is Success -> Success("Company has been created successfully.")
20
        is Fail -> Fail(cause = result.cause)
21
    }
22
}


The function above implements the business logic to register a new company. Basically, we validate the foundation date and check if another company with the same site exists. Our service depends on CompanyRepository, which implements a persistence contract with the database. Let's check the code for the repository's implementation:

Kotlin
 




xxxxxxxxxx
1
24


1
class CompanyRepositoryImpl : CompanyRepository {
2
 
          
3
    override fun save(value: Company): Result<Unit> = try {
4
        Database.runCommand {
5
            it.createUpdate(Database.getSql("db.insertCompany"))
6
                .bindBean(value)
7
                .execute()
8
        }
9
        Success(Unit)
10
    } catch(ex: Exception) {
11
        Fail("Cannot insert company", ex)
12
    }
13
 
          
14
  
15
    override fun fetchBySite(value: String): Company? {
16
        val entity = Database.runCommand {
17
            it.createQuery(Database.getSql("db.fetchBySite"))
18
                .bind("site", value)
19
                .mapTo<Company>()
20
                .findOne()
21
        }
22
 
          
23
        return if (entity.isPresent) entity.get() else null
24
    }
25
}


The implementation uses a helper class, Database, which abstracts the boilerplate code used by JDBI under the hood. The function getSql() loads external files which contain SQL statements. Our functionality for company registration has been completed with two persistence functions.

Company Ranking

Once our registration operation is complete, we can build the company listing functionality to display our Rankings page.

Kotlin
 




xxxxxxxxxx
1


 
1
fun rankingHandler(service: CompanyService): HttpHandler = {
2
    val rankingResponse = Body.auto<List<RankingResponse>>().toLens()
3
    rankingResponse(service.ranking(), Response(Status.OK))
4
}


As you can see in code below, our handler defines a body lens that will returns a list of based onRankingResponse. The lens concept is a functional paradigm used by http4k that can read or get a value from an object passed by an argument through the lens. The code for the service is really simple, as we can see below:

Kotlin
 




xxxxxxxxxx
1


1
fun ranking(): List<RankingResponse> = repository.fetchRankingList().map { company ->
2
    RankingResponse(company.id, company.name, company.site, company.score, company.likes, company.dislikes)}
3
}


The service basically transforms an entity into a Model object, which will be serialized in response. Now, let's see the repository function to fetch companies.

Kotlin
 




xxxxxxxxxx
1


1
override fun fetchRankingList(): List<Company> = Database.runCommand {
2
    it.createQuery(Database.getSql("db.fetchCompanies"))
3
        .mapTo(Company::class.java)
4
        .list()
5
}


Voting

Let's finish our simple service by implementing a Voting operation. Our vote handler must look like:

Kotlin
 




x


 
1
fun voteHandler(service: CompanyService): HttpHandler = { request ->
2
    val voteRequestBody = Body.auto<VoteRequest>().toLens()
3
    val voteRequest = voteRequestBody(request)
4
    service.vote(voteRequest)
5
    Response(Status.NO_CONTENT)
6
}


Now let's see the vote function from the CompanyService class. It has a simple logic that identifies which type of vote we are performing and updates the counter.

Kotlin
 




x


 
1
fun vote(data: VoteRequest) {
2
    when(data.type) {
3
        "like" -> repository.like(data.id)
4
        "dislike" -> repository.dislike(data.id)
5
        else -> logger.warn("Invalid vote type ${data.type}")
6
    }
7
}


The repository implementation is very straightforward, as we can see below:

Kotlin
 




xxxxxxxxxx
1
13


 
1
    override fun like(id: String) {
2
        Database.runCommandInTransaction {
3
            it.createUpdate("UPDATE companies SET likes = likes + 1 WHERE id = :id")
4
                .bind("id", id).execute()
5
        }
6
    }
7
 
          
8
 
          
9
    override fun dislike(id: String) {
10
        Database.runCommandInTransaction {
11
            it.createUpdate("UPDATE companies SET dislikes = dislikes + 1 WHERE id = :id")
12
                .bind("id", id).execute()
13
        }
14
    }


Wrapping Up

I hope this little tutorial has shown you the power and simplicity that http4k brings to us. Creating services is really simple and enjoyable using http4k and in the future we will explore more features. If you want a deep dive into more of http4k, take a look at the many examples and documentation from the http4k website. The complete code of this tutorial can be found at https://github.com/keuller/tutorial-ranking-service. See you next time!

 

 

 

 

Top