Writing a simple RESTful service on kotlin and spring boot

image

Introduction


In anticipation of the release of the Kotlin language from beta, I want to share my impression of its use.

Kotlin is a great new language from JetBrains (IntelliJ Idea developers) for the JVM, Android and browser, which at first glance looks like improved java (or like simplified scala). But this is only at first glance, the language not only absorbed many interesting solutions from other languages, but also presents its original ones:

- optional from swift, nullsafe in kotlin
- case classes from scala, data class in kotlin
- replacement of implicit methods from scala , extension of functions
- delegates
- null safely
- smart cast
- and much more, more details can be found on the official website of kotlinlang .

For those who are familiar with java or scala, it will be interesting to compare kotlin & java , kotlin & scala .

The authors of the language strive to achieve two goals:
- to make the compilation speed comparable to java
- the language should be quite expressive, and as simple as possible
, therefore, it is worth mentioning that if you are currently happy with scala, with its “complexity” and time compilation, then most likely you will not need kotlin, for everyone else read on:

For those who are in the tank for the first time hearing about the language, below are a few examples from the official site:

Hello world
package hello
fun main(args: Array) {
   println("Hello World!")
}


Reading arguments
fun main(args: Array) {
   if (args.size() == 0) {
      println("Provide a name")
      return
   }
   println("Hello, ${args[0]}!")
}


hello world c oop
class Greeter(val name: String) { 
   fun greet() { 
      println("Hello, $name")
   }
}
fun main(args: Array) {
   Greeter(args[0]).greet()
}  


From my personal experience with kotlin, I especially want to note several advantages of the language:

- the first is of course the ease of interaction with java. All types and collections from java are converted to similar ones from kotlin, and vice versa. This is especially pleasing after all the "anarchy" that is happening in scala (yes there is scala.collection.JavaConversions._ and scala.collection.JavaConverters._, but still it does not compare with completely transparent type conversion);
- also excellent support from Intellij Idea studio cannot but rejoice, although the language is in Beta 4, already at the moment the plug-in for the studio allows you to work comfortably;
- and for fans of implicit methods from scala, kotlin presents a very convenient solution in the form of extension functions ;
- among other things, the language developers set as their goal to achieve compilation times comparable to java (hi scala), for which they only want to shake hands! This is especially pleasing after a long work in scala, when editing a single line in a fairly small file compiles at the same speed as a small java project;
- inline functions - great innovation. With their help it is possible, for example, to expand the current capabilities of the language, or in some situations to increase productivity;
- Convenient functions of the standard library.
- convenient lambdas, unlike the same java 8. Very similar to the implementation from scala.

Nevertheless, the language has its drawbacks:

- there is not enough pattern matching from scala, but in some situations it saves smart cast and Destructuring Declarations , in others it is necessary to get out by other means. The absence of pattern matching is generally clear, the developers are trying to get as close as possible to java compilation time, but its presence would significantly simplify the writing of some applications, so we are satisfied with what we have;
- try with resource is not very well implemented yet. But here the authors of the language promise to rectify the situation in the near future. In the meantime, you can either apply the existing solution or use the language extension:

try-with-resources
internal class ResourceHolder : AutoCloseable {
    val resources = ArrayList()
    fun  T.autoClose(): T {
        resources.add(this)
        return this
    }
    override fun close() {
        resources.reverse()
        resources.forEach {
            try {
                it.close()
            } catch (e: Throwable) {
                e.printStackTrace()
            }
        }
    }
}
inline internal fun  using(block: ResourceHolder.() -> R): R {
    val holder = ResourceHolder()
    try {
        return holder.block()
    } finally {
        holder.close()
    }
}


Usage example
fun copy(from: Path, to: Path) {
    using {
        val input = Files.newInputStream(from).autoClose()
        val output = Files.newOutputStream(to).autoClose()
        input.copyTo(output)
    }
}


- there are no async and yield yet, but according to the authors, after the release of 1.0, you can wait for their appearance in the very near future.

Update In the comments, the most interesting is the comparison with scala, and in particular the kotlin advantage:
Kotlin advantages over scala
- interaction with java and vice versa. On kotlin you can generally forget that you are calling something from java, everything is extremely transparent and convenient. The opposite is true. From java it is also convenient to work with kotlin. Here, not only the similarity of types helps, how much is kotlin’s focus on transparent work with java. The easiest way to demonstrate this is with the following examples:
Examples
1. The use of lambdas instead of anonymous classes (in scala the truth may also appear soon, but so far this is not possible).
Code in java:
ExecutorService executor = Executors.newFixedThreadPool(1);
    executor.execute(System.out::println);


In scala, you can’t do this anymore. But there are no problems with kotlin:
val executor = Executors.newFixedThreadPool(1)
    executor.execute { println() }


2. There is the same try-with-resources (yes, you can make an extension to the language, but this is not out of the box, which means you have to drag the solution from one project to another)
This is at least the first thing that comes to mind

- lack of implicit. An implicit per se is certainly a powerful solution, but without a studio it’s almost impossible to figure out someone else’s code. Especially if the authors are "very carried away." And to add methods, kotlin allows you to write extension, which in practice looks much more convenient than similar solutions in scala. Example:
Scala:
implicit class RichInt(val x: Int) extends AnyVal {
  def square: Int = x * x
}
object App  {
   def print(): Unit = {
      val two = 2
      println(s"The square of 2 is ${two.square}")
  }
}

Kotlin:
fun Int.richInt(): Int = this * this
object App {
    fun print(): Unit {
        val two = 2
        println("The square of 2 is ${two.richInt()}")
    }
}


- nullable types instead of Option.

- convenient functions of the standard library

- and of course, compilation speed. In practice, everything really compiles very quickly, especially when compared to scala.

- the resulting jar is less than the same in scala.

- and most importantly, JetBrains promise full backward compatibility (hello once again scala with libs under 2.10, 2.11, 2.12, etc.)


Let's move on to an example in which a small RESTful application on spring boot will be demonstrated , with assembly via gradle .

Studio setup


To work, you need to install the IntelliJ Idea Community (but you can use Eclipse, there is also a plugin for it), in which, after installation, update the kotlin plugin. You need to update it manually, through settings -> plugin , even if you had previously chosen to update the plug-in through a pop-up window (at least for now, while the language is in beta).

It is also better to set a local gradle, and register it in the settings in the studio (settings -> build, execution, deployment -> gradle -> user local gradle distribution. Then specify the path to gradle in gradle home).

Project setup


Create a gradle kotlin project (new project -> gradle -> kotlin) and change the contents of build.gradle to the following:

Build.gradle content
buildscript {
    ext.kotlin_version = '1.0.0-beta-4584'
    repositories {
        mavenCentral()
        maven { url "http://repo.spring.io/snapshot" }
        maven { url "http://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
    }
}
apply plugin: 'idea'
apply plugin: 'spring-boot'
apply plugin: 'kotlin'
jar {
    baseName = 'test-spring-kotlin-project'
    version = '0.1.0'
}
repositories {
    mavenCentral()
    maven { url "http://repo.spring.io/snapshot" }
    maven { url "http://repo.spring.io/milestone" }
    maven { url "http://10.10.10.67:8081/nexus/content/groups/public" }
}
dependencies {
    compile("org.springframework.boot:spring-boot-starter-web:1.3.0.RELEASE")
    compile("org.springframework:spring-jdbc:4.2.3.RELEASE")
    compile("com.fasterxml.jackson.module:jackson-module-kotlin:2.6.4")
    compile("org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version")
}


We create the application.properties file in the src / main / resources folder, in which we specify the port to start spring boot:

application.properties
server.port = 8080


Create the Application.kt file in the src / main / kotlin / test.kotlin.spring.project folder. It will have the basic settings for running spring boot:

Application.kt
package test.kotlin.spring.project
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.context.web.SpringBootServletInitializer
import org.springframework.context.annotation.Bean
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter
@SpringBootApplication
@EnableAutoConfiguration(exclude = arrayOf(DataSourceAutoConfiguration::class))
open class Application : SpringBootServletInitializer() {
    @Bean
    open fun mapperForKotlinTypes(): MappingJackson2HttpMessageConverter {
        return MappingJackson2HttpMessageConverter().apply { objectMapper = jacksonMapper }
    }
    override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder =
            application.sources(Application::class.java)
    companion object {
        val jacksonMapper = ObjectMapper().registerKotlinModule()
                .setSerializationInclusion(JsonInclude.Include.NON_ABSENT)
                .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES)
        @Throws(Exception::class)
        @JvmStatic fun main(args: Array) {
            println("starting application...")
            SpringApplication.run(Application::class.java, *args)
        }
    }
}



It will also be necessary to create a file with the settings of the rest service methods. There will be several methods:

- the method will return AckResponse to the first and last name data entered from the request.
- a method, an array of strings is input, from which the smallest string is selected by length, which is then divided by '_', sorted and assembled into a string already with the character ',' (demonstrates the capabilities of the language)

Create a ServiceController.kt file in the src / folder main / kotlin / test.kotlin.spring.project.

ServiceController.kt
package test.kotlin.spring.project
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
data class AckResponse(val status: Boolean, val result: String, val message: String? = null)
@RestController
class ServiceController {
    @RequestMapping(
            path = arrayOf("/request"),
            method = arrayOf(RequestMethod.GET),
            produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))
    fun nameRequest(
            @RequestParam(value = "name") name: String,
            @RequestParam(value = "surname", required = false) surname: String?): AckResponse {
        return if (surname == null)
            AckResponse(status = true, result = "Hi $name", message = "surname is empty")
        else
            AckResponse(status = true, result = "Hi $surname,$name")
    }
    @RequestMapping(
            path = arrayOf("/sort_request"),
            method = arrayOf(RequestMethod.GET),
            produces = arrayOf(MediaType.APPLICATION_JSON_UTF8_VALUE))
    fun findMinimum(
            @RequestParam(value = "values") values: Array): AckResponse {
        println("values:")
        values.forEach { println(it) }
        val minValue = values.apply { sortBy { it.length } }
            .firstOrNull()
            ?.split("_")
            ?.sorted()
            ?.joinToString(",") ?: ""
        return AckResponse(status = true, result = minValue)
    }
}


Run and test operation


Launch the application from Application.kt. In case of a successful launch, the log will be something like:

Application Logs
starting application ...
  . ____ _ __ _ _
 / \\ / ___'_ __ _ _ (_) _ __ __ _ \ \ \ \
(() \ ___ | '_ |' _ | | '_ \ / _` | \ \ \ \
 \\ / ___) | | _) | | | | | || (_ | |)))))
  '| ____ | .__ | _ | | _ | _ | | _ \ __, | / / / /
 ========= | _ | =============== | ___ / = / _ / _ / _ /
 :: Spring Boot :: (v1.3.0.RELEASE)
2016-01-12 12: 47: 48.242 INFO 88 --- [main] tksproject.Application $ Companion: Starting Application.Companion on Lenovo-PC with PID 88 (D: \ IDA_Projects \ test \ build \ classes \ main started by admin in D: \ IDA_Projects \ test)
2016-01-12 12: 47: 48.247 INFO 88 --- [main] tksproject.Application $ Companion: No profiles are active
2016-01-12 12: 47: 48.413 INFO 88 --- [main] ationConfigEmbeddedWebApplicationContext: Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@dbf57b3: startup date [Tue Jan 12 12:47:48 MSK 2016]; root of context hierarchy
2016-01-12 12: 47: 50.522 INFO 88 --- [main] osbfsDefaultListableBeanFactory: Overriding bean definition for bean 'beanNameViewResolver' with a different definition: replacing [Root bean: class [null]; scope =; abstract = false; lazyInit = false; autowireMode = 3; dependencyCheck = 0; autowireCandidate = true; primary = false; factoryBeanName = org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration $ WhitelabelErrorViewConfiguration; factoryMethodName = beanNameViewResolver; initMethodName = null; destroyMethodName = (inferred); defined in class path resource [org / springframework / boot / autoconfigure / web / ErrorMvcAutoConfiguration $ WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope =; abstract = false; lazyInit = false; autowireMode = 3; dependencyCheck = 0; autowireCandidate = true; primary = false; factoryBeanName = org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration $ WebMvcAutoConfigurationAdapter; factoryMethodName = beanNameViewResolver; initMethodName = null; destroyMethodName = (inferred); defined in class path resource [org / springframework / boot / autoconfigure / web / WebMvcAutoConfiguration $ WebMvcAutoConfigurationAdapter.class]]
2016-01-12 12: 47: 51.066 INFO 88 --- [main] trationDelegate $ BeanPostProcessorChecker: Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionMan$oncerCan$hancerhan $$ ede1977c] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2016-01-12 12: 47: 51.902 INFO 88 --- [main] sbcetTomcatEmbeddedServletContainer: Tomcat initialized with port (s): 8080 (http)
2016-01-12 12: 47: 51.930 INFO 88 --- [main] o.apache.catalina.core.StandardService: Starting service Tomcat
2016-01-12 12: 47: 51.937 INFO 88 --- [main] org.apache.catalina.core.StandardEngine: Starting Servlet Engine: Apache Tomcat / 8.0.28
2016-01-12 12: 47: 52.095 INFO 88 --- [ost-startStop-1] oaccC [Tomcat]. [Localhost]. [/]: Initializing Spring embedded WebApplicationContext
2016-01-12 12: 47: 52.095 INFO 88 --- [ost-startStop-1] osweb.context.ContextLoader: Root WebApplicationContext: initialization completed in 3688 ms
2016-01-12 12: 47: 52.546 INFO 88 --- [ost-startStop-1] osbceServletRegistrationBean: Mapping servlet: 'dispatcherServlet' to [/]
2016-01-12 12: 47: 52.556 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'characterEncodingFilter' to: [/ *]
2016-01-12 12: 47: 52.557 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'hiddenHttpMethodFilter' to: [/ *]
2016-01-12 12: 47: 52.559 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'httpPutFormContentFilter' to: [/ *]
2016-01-12 12: 47: 52.559 INFO 88 --- [ost-startStop-1] osbcembedded.FilterRegistrationBean: Mapping filter: 'requestContextFilter' to: [/ *]
2016-01-12 12: 47: 52.985 INFO 88 --- [main] swsmmaRequestMappingHandlerAdapter: Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@dbf57b3: startup date [Tue Jan 12 12:47:48 MSK 2016]; root of context hierarchy
2016-01-12 12: 47: 53.089 INFO 88 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ request], methods = [GET], produces = [application / json; charset = UTF-8]}" onto public final test.kotlin.spring.project.AckResponse test.kotlin.spring.project.ServiceController.pullUpdate (java.lang.String, java.lang.String)
2016-01-12 12: 47: 53.094 INFO 88 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error (javax.servlet.http.HttpServletRequest)
2016-01-12 12: 47: 53.094 INFO 88 --- [main] swsmmaRequestMappingHandlerMapping: Mapped "{[/ error], produces = [text / html]}" onto public org.springframework.web.servlet.ModelAndView org. springframework.boot.autoconfigure.web.BasicErrorController.errorHtml (javax.servlet.http.HttpServletRequest)
2016-01-12 12: 47: 53.138 INFO 88 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/ webjars / **] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-12 12: 47: 53.139 INFO 88 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/ **] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2016-01-12 12: 47: 53.195 INFO 88 --- [main] oswshandler.SimpleUrlHandlerMapping: Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource. ResourceHttpRequestHandler]
2016-01-12 12: 47: 53.512 INFO 88 --- [main] osjeaAnnotationMBeanExporter: Registering beans for JMX exposure on startup
2016-01-12 12: 47: 53.612 INFO 88 --- [main] sbcetTomcatEmbeddedServletContainer: Tomcat started on port (s): 8080 (http)
2016-01-12 12: 47: 53.620 INFO 88 --- [main] tksproject.Application $ Companion: Started Application.Companion in 6.076 seconds (JVM running for 7.177)
2016-01-12 12: 47: 57.874 INFO 88 --- [nio-8080-exec-1] oaccC [Tomcat]. [Localhost]. [/]: Initializing Spring FrameworkServlet 'dispatcherServlet'
2016-01-12 12: 47: 57.874 INFO 88 --- [nio-8080-exec-1] osweb.servlet.DispatcherServlet: FrameworkServlet 'dispatcherServlet': initialization started
2016-01-12 12: 47: 57.897 INFO 88 --- [nio-8080-exec-1] osweb.servlet.DispatcherServlet: FrameworkServlet 'dispatcherServlet': initialization completed in 23 ms


After a successful launch, try to open the Request page with a name . The answer should look like this:
{
  status: true,
  result: "Hi Kris",
  message: "surname is empty"
}

And the request with the name and surname , then the answer will be slightly different:

{
  status: true,
  result: "Hi Eagle, Kris"
}

Call to check data sorting: Sorting . The result should be:

{
  status: true,
  result: "1,3,value,virst"
}

The same call, but with an empty array: Call
{
  status: true,
  result: ""
}

If necessary, you can assemble the entire project into one runnable jar with the command: gradle build. As a result, the project will be collected in one archive containing all the dependencies without unpacking. With this approach, the build time of the project is significantly increased compared to the same assemble, when the project is assembled in one archive with the unpacking of all the dependencies.

Conclusion


In conclusion, I want to note that kotlin turned out to be a very convenient language for working on any project that uses java as its replacement. The ecosystem of the language is not yet as extensive as the same scala, but now you can use it in the same big data, where there is a java api. In addition, it is very easy to interact with java from kotlin, so everything that is in java can also be used in kotlin. In addition, from the studio there is the possibility of easily converting java files to similar ones on kotlin (though it will be necessary to correct the file with a little hands after conversion). JetBrains did a wonderful job of creating the perfect language to replace java and scala. And I hope in the future the trend towards the use of kotlin will only grow.

Sources are available on github .

Thank you all for your attention.

Also popular now: