Intro to SpringBoot3 with Kotlin: Part 3 — Services & Business Logic

Daniel Peach
4 min readOct 5, 2023

--

The Series

The Source Code

Introduction & Recap

In Part 2, we created a Controller. The Controller routes and handles our web traffic. However, as soon as your logic starts to get very complex, it can bloat the Controllers, making it difficult to read, test, and maintain. To help mitigate this, web servers generally have a Service layer.

Service Layer

The job of the Service layer is to house all the business logic of the application, and will be called by the Controllers, so that the Controllers themselves can be kept clean and only worry about handling HTTP traffic.

We will create a service that handles the logic of determining the language of the user, and returning the appropriate greeting. Then we will clean up our controller to use this service.

Definition

Create a new class in the main package with New > New Kotlin Class > GreetingService. Annotate the class with @Service.

This tells Spring to pick this class up as a service, and allow us to inject it later. We will see this in action when we need to use the service in the controller.

In this service, we will create a map of greetings. This map will use language codes as its keys and the greeting in that language as its value. We will then create a method that takes a language key and returns the appropriate greeting.

@Service
class GreetingService {

val greetings = mapOf(
"en" to "Hello World!",
"es" to "¡Hola Mundo!",
"hi" to "हैलो वर्ल्ड!",
"fr" to "Bonjour le monde!",
"zh" to "你好世界!",
)

fun getGreeting(languageKey: String): String? = greetings[languageKey]
}

This example is admittedly very contrived, and could easily fit in the controller itself, but the principal of seperating HTTP logic from business logic is very important in keeping your code base maintainable.

Your service should look like this:

package com.example.springboot3kotlin

import org.springframework.stereotype.Service

@Service
class GreetingService {

val greetings =
mapOf(
"en" to "Hello World!",
"es" to "¡Hola Mundo!",
"hi" to "हैलो वर्ल्ड!",
"fr" to "Bonjour le monde!",
"zh" to "你好世界!",
)

fun getGreeting(languageKey: String): String? = greetings[languageKey]
}

Usage

Now we can use our GreetingService! In our controller, add a constructor, and require a parameter of type GreetingService.

class GreetingController(private val greetingService: GreetingService)

When Spring sees this constructor, it will check the services we have annotated for one with a matching type. Once found, it will provide it to the constructor of the controller.

Now that we have an instance of the service in our controller, we can use it.

Let’s refactor the controller from multiple methods for different language, to one langauge that expects a language code path parameter. To do this, we will use a special path syntax and use the @PathVariable annotation.

@GetMapping("/{languageKey}")
fun getGreeting(@PathVariable languageKey: String): String

Now we will implement this method by checking the service for an appropriate greeting. If we don’t get one back, we will return a 404 response to the caller.

@GetMapping("/{languageKey}")
fun getGreeting(@PathVariable languageKey: String): String =
greetingService.getGreeting(languageKey)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "No greeting found for $languageKey")

Your controller should now look like this:

package com.example.springboot3kotlin

import org.springframework.http.HttpStatus
import org.springframework.stereotype.Controller
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.ResponseBody
import org.springframework.web.server.ResponseStatusException

@Controller
@ResponseBody
@RequestMapping("/greeting")
class GreetingController(private val greetingService: GreetingService) {
@GetMapping("/{languageKey}")
fun getGreeting(@PathVariable languageKey: String): String =
greetingService.getGreeting(languageKey)
?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "No greeting found for $languageKey")
}

With this in place we can run our application and see it in action.

curl -X GET http://localhost:8080/greeting/hi
हैलो वर्ल्ड!

And if we attempt a language key that does not exist, we get the 404 as expected!

curl -X GET http://localhost:8080/greeting/ex
... "status":404,"error":"Not Found" ... "message":"No greeting found for ex" ...

Try it yourself by calling the endpoint with any of the langauges we added to our list. Or add some more yourself!

Summary

Now we saw how to seperate business and http logic. In Part 4, we will wrap things up by using actual data entities and repositories to access the database!

--

--