Kotlin dev - Spring Boot REST API with Kotlin

Let's talk about Kotlin in this article. I have been reading on Kotlin since last few weeks, so wanted to code in Kotlin to actually feel the difference in compared to java code.

Finally, developed a very simple REST API in Kotlin with Spring Boot + Spring Data + H2 in memory DB. 
Kotlin and Spring Boot play well together.

You will also notice in the Code Walk through section that, there is NO controller, NO service class in the project!! That's magic of spring's @RepositoryRestResource. I explained below in details.

I will try to write down my notes, on what I have learnt so far in Kotlin as on day 0.

I have no experience on Kotlin but what I read and looked into the Kotlin code on github projects, this is definitely to practice on.

An obvious question, you would ask is, Why Kotlin?

In short,
Why Kotlin


  • Kotlin compiles to bytecode, so it can perform just as well as Java.
  • More succinct than Java.
  • Kotlin's Data classes are clearly more concise than Java's value classes.
  • Classes are final by default which corresponds to Effective Java Item 17. You would need to explicitly put open if you want to make class inheritable.
  • Abstract classes are open by default.
  • One of Kotlin’s key features is null-safety - which cleanly deals with null values at compile time rather than bumping into the famous NullPointerException at runtime. 
  • Primary constructor vs. secondary constructors. If you need more than one constructor then only you would go for secondary otherwise most of the Kotlin class would have primary constructor.
  • Kotlin can also be used as a scripting language.
  • Kotlin and Java are inter-operable, so it's easy to do a trial of Kotlin on just a small part of your codebase.
  • Kotlin uses aggressive type inference to determine the types of values and expressions for which type has been left unstated. This reduces language verbosity relative to Java.
  • Kotlin is fully supported by Google for use with their Android operating system.

Two points below, reference from Wiki:

  • According to Jetbrains blog, Kotlin is used by Amazon Web Services, Pinterest, Coursera, Netflix, Uber, and others. Corda, a distributed ledger developed by a consortium of well-known banks (such as Goldman Sachs, Wells Fargo, J.P. Morgan, Deutsche Bank, UBS, HSBC, BNP Paribas, Société Générale), has over 90% Kotlin in its codebase.
  • According to Google, Kotlin has already been adopted by several major developers — Expedia, Flipboard, Pinterest, Square, and others — for their Android production apps.


Code walk through

Project code is on my Kotlin Github Repo , which is a simple REST API using Kotlin and Spring Boot

Clone - https://github.com/BeTheCodeWithYou/SpringBoot-Kotlin.git


  • Kotlin
  • Spring Boot
  • Spring Data
  • H2 in-memory DB
  • Gradle


Understanding build.gradle




org.jetbrains.kotlin:kotlin-gradle-plugin - 

compiles Kotlin sources and modules.

org.jetbrains.kotlin:kotlin-allopen

This is interesting part here. In Kotlin, by default all classes are final
Now, in order to make a class inheritable, you have to annotate with open keyword. 
And the problem is, lot of other libraries like Spring, test libraries ( Mockito etc ) requires classes and methods to be non-final. In Spring, such classes mainly to include @Configuration classes and @Bean methods.

Rule is simple, you need to annotate @Configuration and @Bean methods to mark them open but this approach is tedious and error-prone, hence Kotlin has come up with compiler plugin to automate this process through this dependency.

org.jetbrains.kotlin:kotlin-noarg  and 
apply plugin: 'kotlin-jpa'

In order to be able to use Kotlin immutable classes, we need to enable Kotlin JPA plugin. It will generate no-arg constructors for any class annotated with @Entity


apply plugin: 'kotlin'
To target the JVM, kotlin plugin needs to be applied.

apply plugin: 'kotlin-spring'
Required for Spring Kotlin integration

Compiler options

Spring nullability annotations provide null-safety for the whole Spring Framework API to Kotlin developers, with the advantage of dealing with null related issues at compile time and this feature can be enabled by adding the -Xjsr305 compiler flag with the strict options.

and also, configures Kotlin compiler to generate Java 8 bytecode.

compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-Xjsr305=strict"]
jvmTarget = "1.8"
}
}

compile("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
Is the Java 8 variant of Kotlin standard library

compile('com.fasterxml.jackson.module:jackson-module-kotlin')
Adds support for serialization/deserialization of Kotlin classes and data classes.

compile("org.jetbrains.kotlin:kotlin-reflect")
Is Kotlin reflection library

Spring Boot Gradle plugin automatically uses the Kotlin version declared on the Kotlin Gradle plugin, hence the version is not defined explicitly on the dependencies section.

All remaining entries are self explanatory.

Kotlin Coding

Spring Boot Application

/src/main/kotlin/
  
  com.xp.springboot.kotlin.SpringBootKotlinRestApiApplication.kt

Notice that, missing semicolon.
You need open and close braces of class only if you have @Bean otherwise just a class with name is required.
runapplication is a top level function.





Creating Data Class

Then we create our model by using Kotlin data classes which are designed to hold data and automatically provide equals(), hashCode(), toString(), componentN() functions and copy().

Also, you could define multiple entities in the same data class.

var is like general variable and its known as a mutable variable in kotlin and can be assigned multiple times.
There is another type val, is like constant variable and its known as immutable in kotlin and can be initialized only single time and moreover, val is read-only and you are not allowed to explicitly write to val.



Creating a Repository

Yes, just the one liner to define repository interface with spring data curd repository.

The interesting spring annotation here is RepositoryRestResource.
This comes by adding spring-boot-starter-data-rest dependency.




If you would have noticed, there is NO Controller , NO Service and this project is exposing below REST endpoints with HATEOAS enabled.

GET - http://localhost:8080/parkrun/runners
POST - http://localhost:8080/parkrun/runners
GET - http://localhost:8080/parkrun/runners/2
DELETE - http://localhost:8080/parkrun/runners/1


It's the magic of @RepositoryRestResource.
Apply collectionResourceRel to define custom resource label else annotation will use default as per the model class name ( /parkRunners ).

At runtime, Spring Data REST will create an implementation of this interface automatically. Then it will use the @RepositoryRestResource annotation to direct Spring MVC to create RESTful endpoints at /parkRunners.


Running the Application

Once you have the jar ready after gradle clean bulid. Just run the app using

java -jar SpringBootKotlinRestAPI-0.0.1-SNAPSHOT.jar

and hit the below url

http://localhost:8080/parkrun

Response:

{
    "_links": {
        "runners": {
            "href": "http://localhost:8080/parkrun/runners"
        },
        "profile": {
            "href": "http://localhost:8080/parkrun/profile"
        }
    }

}

Hit the url http://localhost:8080/parkrun/runners on GET

Response:


{ "_embedded": { "runners": [ { "firstName": "NEERAJ", "lastName": "SIDHAYE", "gender": "M", "runningClub": "RUNWAY", "totalRuns": "170", "_links": { "self": { "href": "http://localhost:8080/parkrun/runners/1" }, "parkRunner": { "href": "http://localhost:8080/parkrun/runners/1" } } } ] }, "_links": { "self": { "href": "http://localhost:8080/parkrun/runners" }, "profile": { "href": "http://localhost:8080/parkrun/profile/runners" } } }


Hit the profile url and see what you get

http://localhost:8080/parkrun/profile/runners

{ "alps": { "version": "1.0", "descriptors": [ { "id": "parkRunner-representation", "href": "http://localhost:8080/parkrun/profile/runners", "descriptors": [ { "name": "firstName", "type": "SEMANTIC" }, { "name": "lastName", "type": "SEMANTIC" }, { "name": "gender", "type": "SEMANTIC" }, { "name": "runningClub", "type": "SEMANTIC" }, { "name": "totalRuns", "type": "SEMANTIC" } ] }, { "id": "get-runners", "name": "runners", "type": "SAFE", "rt": "#parkRunner-representation" }, { "id": "create-runners", "name": "runners", "type": "UNSAFE", "rt": "#parkRunner-representation" }, { "id": "patch-parkRunner", "name": "parkRunner", "type": "UNSAFE", "rt": "#parkRunner-representation" }, { "id": "get-parkRunner", "name": "parkRunner", "type": "SAFE", "rt": "#parkRunner-representation" }, { "id": "delete-parkRunner", "name": "parkRunner", "type": "IDEMPOTENT", "rt": "#parkRunner-representation" }, { "id": "update-parkRunner", "name": "parkRunner", "type": "IDEMPOTENT", "rt": "#parkRunner-representation" } ] } }


No comments: