返回文章列表文章索引阅读全文
STARBLOG / 阅读手记
Kotlin

Using Kotlin with Spring Boot to Develop RESTful APIs

Hello, long time no see. I glanced at my posting history and realized I haven't published an article in almost a month. Busy->tired->lazy, so I never got around to writing. I recently finished my defense, and I can finally spare some time to share a bit of what I've been learning lately~ About Kotlin I first noticed this language last year or the year before, when Google endorsed Kotlin, and I even bought several Kotlin books for the team. Overall I've been fairly optimistic about this language's future, but I never actually used it. This time I'll dip my toes in with Kotlin and the Spring Boot framework, looking forward to Kotlin's elegance

曦远 (DealiAxy)约 34 分钟
目录12 个章节

Hello, long time no see. I glanced at my posting history and realized I haven't published an article in almost a month. Busy->tired->lazy, so I never got around to writing. I recently finished my defense, and I can finally spare some time to share a bit of what I've been learning lately~

About Kotlin

I first noticed this language last year or the year before, when Google endorsed Kotlin, and I even bought several Kotlin books for the team. Overall I've been fairly optimistic about this language's future, but I never actually used it. This time I'll dip my toes in with Kotlin and the Spring Boot framework, looking forward to what sparks Kotlin's elegant syntax and Spring's powerful ecosystem can strike together~

PS: This article is for readers who already have a basic grasp of Spring development and Gradle.

Creating the Project

Alright, time to get to the point. Listen up, everyone.

First we create a Gradle script and save it in an empty directory to initialize the project:

buildscript {
    ext.kotlin_version = '1.3.72' // Required for Kotlin integration
    ext.spring_version = '5.2.6.RELEASE'
    ext.spring_boot_version = '2.3.0.RELEASE'
    repositories {
        jcenter()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // Required for Kotlin integration
        classpath "org.jetbrains.kotlin:kotlin-allopen:$kotlin_version" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support
        classpath "org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version"
    }
}

apply plugin: 'kotlin' // Required for Kotlin integration
apply plugin: "kotlin-spring" // https://kotlinlang.org/docs/reference/compiler-plugins.html#spring-support
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

repositories {
    jcenter()
}

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    compile "org.springframework.boot:spring-boot-starter-web"
    compile "com.fasterxml.jackson.module:jackson-module-kotlin"
    testCompile('org.springframework.boot:spring-boot-starter-test')
}

After saving, run gradle build. Anyone with a basic Spring Boot background should know this step, so I won't waste space pasting the how-to.

Once the build finishes, an src folder is generated. We just create our package structure and code under src/main. The only difference from Java is that the java folder is replaced by a kotlin folder.

Writing the First Endpoint

The code in this section is based on the official Kotlin language examples~

First we create a simple model. Under the model package, create Greeting.kt and write a data class:

data class Greeting(val id: Long, val content: String)

OK, next write the controller logic. Under the controller package, create GreetingController.kt with the following code:

@RestController
class GreetingController {
    val counter = AtomicLong()
    @GetMapping("/greeting")
    fun greeting(@RequestParam(value = "name", defaultValue = "World") name: String) =
            Greeting(counter.incrementAndGet(), "Hello, $name")
}

Look at this code—doesn't it have that familiar Spring Boot flavor~

Next, run the gradlew bootRun task and it will start. The overall flow is no different from developing Spring Boot with Java; it's just more comfortable to write than verbose Java thanks to Kotlin's syntax.

Visit http://127.0.0.1:8080/greeting in the browser, and you'll see:

{
  "id": 1,
  "content": "Hello, World"
}

Introducing an ORM

That was just a warm-up. Next we'll get into real development. Web systems usually need to access a database. When using Java, many people would pick JPA or MyBatis-Plus and the like. But since we're using Kotlin, we should switch to an ORM built specifically for Kotlin. There are many such ORMs to choose from. On a blog's recommendation, I picked Ktorm, developed by a Chinese author—as the name suggests, it's Kotlin-specific.

Official docs: https://ktorm.liuwj.me/zh-cn/quick-start.html

If you haven't used it before, you can give this ORM a try. Aside from lacking database migration, it more or less meets my requirements. It now also supports data classes as entity classes, but when I wrote this I followed the official docs and used interfaces as entity classes, which is a small pitfall when passing parameters in the controller.

(PS: If you need this in production, don't go looking for trouble—this ORM has quite a few pitfalls.)

One more thing: I couldn't get the official Ktorm–Spring Boot integration project on GitHub to run. Not sure what the issue was.

On to the actual development~

First, add the Ktorm and spring-jdbc related dependencies in the Gradle script. Here's a pitfall I ran into: the official docs say to use Spring's JDBC, but when I did that I could never read the application config or inject a DataSource. After switching to Spring Boot's JDBC, it worked. Personal projects really do have some pitfalls after all.

First define a variable:

ext.ktorm_version = '2.7.2'

Next add the dependencies. I usually use SQLite in the development environment; those who need MySQL, please replace it yourselves:

compile "me.liuwj.ktorm:ktorm-core:$ktorm_version"
compile "me.liuwj.ktorm:ktorm-jackson:$ktorm_version"
compile "me.liuwj.ktorm:ktorm-support-sqlite:$ktorm_version"
compile "io.github.willena:sqlite-jdbc:3.30.1"

Write the configuration class:

@Configuration
class KtormConfiguration {
    @Autowired
    lateinit var dataSource: DataSource
    @Bean
    fun database(): Database {
        return Database.connectWithSpringSupport(
                dataSource = dataSource,
                dialect = SQLiteDialect()
        )
    }
    @Bean
    fun ktormModule(): Module {
        return KtormModule()
    }
}

Writing Entity Classes

Here we use the data structure from the official example:

import me.liuwj.ktorm.entity.Entity
interface Department : Entity<Department> {
    companion object : Entity.Factory<Department>()
    val id: Int
    var name: String
    var location: String
}

Then write another class to define the table structure and bind columns to the entity class:

import me.liuwj.ktorm.schema.Table
import me.liuwj.ktorm.schema.int
import me.liuwj.ktorm.schema.varchar
object Departments : Table<Department>("t_department") {
    val id by int("id").primaryKey().bindTo { it.id }
    val name by varchar("name").bindTo { it.name }
    val location by varchar("location").bindTo { it.location }
}

This is just an example; the following code may not actually use this entity class, but you can extrapolate from it.

Starting CRUD

Next up is the traditional craft of web developers—copy-pasting CRUD at lightning speed. Let's go~

Here's the code: standard RESTful endpoints using the four HTTP methods GET, POST, PUT, and DELETE. This organization is exactly the same as DjangoRestFramework—the APIs are concise and expressive.

It also demonstrates database operations with Ktorm; you can see it's quite convenient to use.

@RestController
@RequestMapping("/department")
class DepartmentController {
    @Autowired
    lateinit var database: Database

    @GetMapping("/")
    fun list(): Response<List<Department>> {
        return Response(true, "获取部门列表",
                database.sequenceOf(DepartmentDao).toList())
    }

    @GetMapping("/{id}")
    fun get(@RequestParam("id") id: Int): Response<Department> {
        val department = database.sequenceOf(DepartmentDao).find { it.id eq id }
                ?: return Response(false, "部门不存在", null)

        return Response(true, "获取部门信息", department)
    }

    @PostMapping("/")
    fun create(
            @RequestParam("name") name: String,
            @RequestParam("location") location: String
    ): Response<Department> {
        val department = Department()
        department.name = name
        department.location = location

        val result = database.sequenceOf(DepartmentDao).add(department)
        return if (result > 0)
            Response(true, "添加部门成功", department)
        else
            Response(false, "添加部门失败", null)
    }

    @PutMapping("/{id}")
    fun update(
            @RequestParam("id") id: Int,
            @RequestParam("name") name: String,
            @RequestParam("location") location: String
    ): Response<Department> {
        val department = database.sequenceOf(DepartmentDao).find { it.id eq id }
                ?: return Response(false, "部门不存在", null)

        department.name = name
        department.location = location
        val result = department.flushChanges()
        return if (result > 0)
            Response(true, "修改部门信息成功", department)
        else
            Response(false, "修改部门信息失败", null)
    }

    @DeleteMapping("/{id}")
    fun delete(@RequestParam("id") id: Int): Response<Nothing> {
        val department = database.sequenceOf(DepartmentDao).find { it.id eq id }
                ?: return Response(false, "部门不存在", null)
        val result = department.delete()

        return if (result > 0)
            Response(true, "删除部门成功", null)
        else
            Response(false, "删除部门失败", null)
    }
}

Integrating Swagger Docs

Once the APIs are written, the next step is of course sending them to the frontend for integration. Time to whip out Swagger~

Integration is very convenient. Step one, add the dependencies:

compile "io.springfox:springfox-swagger2:2.9.2"
compile "io.springfox:springfox-swagger-ui:2.9.2"

Step two, write the configuration:

@Configuration
@EnableSwagger2
class SwaggerConfig {

    @Bean
    fun api(): Docket {
        return Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo())
    }

    private fun apiInfo(): ApiInfo {
        return ApiInfo(
                "kotlin-springboot",
                "博客:http://blog.deali.cn",
                "api v1.0",
                "http://blog.deali.cn",
                Contact(
                        "DealiAxy",
                        "http://blog.deali.cn",
                        "admin@deali.cn"
                ),
                "GPL v2",
                "http://www.apache.org/",
                Collections.emptyList()
        )
    }
}

Step three: add annotations to the APIs. This step isn't actually required, but once you add them you can clearly see on the API webpage what each endpoint is for.

Finally, open a browser to see the result: http://127.0.0.1:8080/swagger-ui.html. It looks roughly like this:

For this part of the configuration, you can refer to: https://www.ibm.com/developerworks/cn/java/j-using-swagger-in-a-spring-boot-project/index.html

Just mentally convert the Java code to Kotlin yourself~

Build and Deploy

Finally we need to deploy our finished project to a server. Using the following approach—an executable jar plus Docker—is much more convenient than Python~ (Go and C# would say that when it comes to deployment, everyone else here is trash hhhh)

Build an executable jar

Run the following command:

gradlew bootJar

This will generate a jar in the build/libs directory. Copy the db.sqlite3 database file from the project root into the same directory.

Then run:

java -jar xxx.jar

to start the service. You can upload this directly to the server and run it.

The next step is deploying with Docker.

Docker deployment

The Dockerfile is as follows:

FROM java:8

MAINTAINER DealiAxy <dealiaxy@gmail.com>
# VOLUME 指定了临时文件目录为/tmp。在主机 /var/lib/docker 目录下创建了一个临时文件,并链接到容器的/tmp
VOLUME /tmp
# 将jar包添加到容器中并更名为app.jar
ADD build/libs/spring-boot-restful.jar app.jar
# 添加数据库
ADD db.sqlite3 db.sqlite3
# 运行jar包
RUN bash -c 'touch /app.jar'

Plus my preferred docker-compose deployment

version: "3"
services:
  web:
    restart: always # 除正常工作外,容器会在任何时候重启,比如遭遇 bug、进程崩溃、docker 重启等情况。
    build: .
    environment:
      - ENVIRONMENT=docker
    command: java -Djava.security.egd=file:/dev/./urandom -jar /app.jar
    ports:
      - "8001:9123"

Final Summary

Kotlin feels to me like a mix of Java, TypeScript, and Python. Compared to C# I think it's still a bit behind, but it's already left Java several streets behind. Combined with Spring's unbeatable ecosystem, writing backend APIs and template-rendered websites is still fast and convenient. With this Kotlin learning experience, when I need a backend for some projects in the future I'll also consider practicing with this stack~

Finally, thanks to Kotlin for letting me avoid the Java cesspit while still using the Spring Boot ecosystem to develop APIs. Hahahaha I'm out

Welcome to Discuss

I've put together a series of technical articles and materials. Reply with linux, flutter, c#, netcore, android, java, python, etc. in the backend of the WeChat official account 「程序设计实验室」 to get related technical articles and materials. You can also leave a message in the official account backend if you have any questions~

_EOF_

Using Kotlin with Spring Boot to Develop RESTful APIs

本文作者:
程序设计实验室
版权声明:
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

相关文章

使用Kotlin搭配Springboot开发RESTFul接口(二)自定义配置、跨域、分页、ViewModel

前言 上一篇文章请看这里: https://zhuanlan.zhihu.com/p/146733027 上一篇文章介绍了Kotlin搭配Springboot的开发流程,从搭建项目、ORM、Controller到Swagger集成和docker部署都有,不过大师说我那篇文章太多代码了,吹水太少了,于是我决定再水一篇,介绍一下Web开发中常用的几种操作。 在做完这个简单的系统,Kotlin搭配Spr

2021-12-18 · Kotlin · 约 1 分钟

Asp-Net-Core学习笔记:3.使用SignalR实时通信框架开发聊天室

SignalR牛刀小试 在MVP杨老师的博客里看到这么个东西,我还以为是NetCore3才推出的新玩意,原来是已经有很多年的历史了,那看来还是比较成熟的一个技术了。 简介 SignalR是一个.NET Core/.NET Framework的开源实时框架,SignalR的可使用Web Socket, Server Sent Events 和 Long Polling作为底层传输方式。 Signal

2022-02-16 · Asp-Net-Core学习笔记 · 约 1 分钟

讨论

共 0 条

良言一句三冬暖,恶语伤人六月寒。

加载评论中…