null

Пишем простое REST API на Kotlin с помощью Vert.x

Что такое Vert.x?

Vert.x - это набор инструментов для разработки программного обеспечения с открытым исходным кодом от создателей Eclipse. Среди его достоинств: эффективное использование ресурсов, поддержка реактивного программирования, гибкость настройки, простота масштабирования, философия простоты кода, поддержка множества языков программирования, таких как: Java, Kotlin, Ruby, Groovy, Python и Javascript.
Vert.x работает на виртуальной машине JAVA (JVM). 
Официальный сайт проекта - https://vertx.io

И так начнем

Для сбора проекта будем использовать систему gradle.
Создадим проект gradle и добавим зависимости Vert.x в файл build.gradle:

buildscript {
    ext.kotlin_version = '1.6.10'
    ext.vertx_version = '4.2.4'

    repositories {
        mavenCentral()
    }

    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }

    group = 'SimpleRestApi'
    version = '1.0.0'
}

plugins {
    id 'org.jetbrains.kotlin.jvm' version "$kotlin_version"
    id 'java'
}

repositories {
    mavenCentral()
}

sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

dependencies {
    // Kotlin
    implementation 'org.jetbrains.kotlin:kotlin-stdlib'

    // Vertx Core
    implementation "io.vertx:vertx-core:$vertx_version"
    implementation "io.vertx:vertx-lang-kotlin:$vertx_version"

    // Vertx Web
    implementation "io.vertx:vertx-web:$vertx_version"

    // Vertx Rxjava
    implementation "io.vertx:vertx-rx-java3:$vertx_version"
    implementation "io.vertx:vertx-rx-java3-gen:$vertx_version"
}

jar {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    manifest {
        attributes 'Implementation-Title': group
        attributes 'Implementation-Version': archiveVersion
        attributes 'Main-Class': 'AppKt'
    }

    from {
        configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

task cleanAndJar {
    group = 'build'
    description = 'Clean and create jar'

    dependsOn clean
    dependsOn jar
}

Обратите внимание, что мы переопределяем задачу jar. Это действие необходимо, поскольку мы пишем на Kotlin и должны добавить наш скомпилированный Kotlin-класс в итоговый Jar.

Структура проекта

Теперь создадим файловую структуру нашего проекта и сделаем ее такой:

- src
| - main
|  | - java
|  | - kotlin
|  |  | - verticle
|  |  |  | - HttpServerVerticle.kt
|  |  |  | - MainVerticle.kt
|  |  | - App.kt

App.kt - это главная функция, которая будет запускать наш сервис.
HttpServerVerticle.kt - класс, в котором будет написан код REST API.
MainVerticle.kt - основной экземпляр Vert.x, который будет запускать класс HttpServerVerticle.

Пишем HTTP-сервис

Добавим в класс HttpServerVerticle.kt следующие строки кода:

package verticle

import io.vertx.core.Promise
import io.vertx.core.json.JsonObject
import io.vertx.rxjava3.core.AbstractVerticle
import io.vertx.rxjava3.ext.web.Router
import io.vertx.rxjava3.ext.web.RoutingContext
import io.vertx.rxjava3.ext.web.handler.BodyHandler

class HttpServerVerticle : AbstractVerticle() {
    private val users = JsonObject().put(
            "users",
            JsonObject().put(
                "tonys",
                JsonObject().apply {
                    put("user_id", "tonys")
                    put("user_name", "Tony Stark")
                    put("name_alias", "Iron Man")
                    put("company", "Stark Industries")
                }))

    override fun start(promise: Promise<Void>) {
        val router = Router.router(vertx).apply {
            get("/api/users").handler(this@HttpServerVerticle::getUsers)
            post("/api/users").handler(BodyHandler.create()).handler(this@HttpServerVerticle::setUser)
            put("/api/users").handler(BodyHandler.create()).handler(this@HttpServerVerticle::updateUser)
            delete("/api/users").handler(this@HttpServerVerticle::deleteUser)
        }

        vertx
            .createHttpServer()
            .requestHandler(router)
            .rxListen(8282)
            .subscribe(
                { promise.complete() },
                { failure -> promise.fail(failure.cause) })
    }

    private fun getUsers(context: RoutingContext) {
        context.response().statusCode = 200

        context.response().putHeader("Content-Type", "application/json")
        context.response().end(users.encode())
    }

    private fun setUser(context: RoutingContext) {
        val userId = context.request().getParam("user_id")
        val userName = context.request().getParam("user_name")
        val nameAlias = context.request().getParam("name_alias")
        val company = context.request().getParam("company")

        users.getJsonObject("users").put(
                userId,
                JsonObject().apply {
                    put("user_id", userId)
                    put("user_name", userName)
                    put("name_alias", nameAlias)
                    put("company", company)
                })

        val response = JsonObject().apply {
            put("success", true)
            put("action", "insert")
            put("current_rows", users)
        }

        context.response().statusCode = 200

        context.response().putHeader("Content-Type", "application/json")
        context.response().end(response.encode())
    }

    private fun updateUser(context: RoutingContext) {
        val userId = context.request().getParam("user_id")
        val userName = context.request().getParam("user_name")
        val nameAlias = context.request().getParam("name_alias")
        val company = context.request().getParam("company")

        users.apply {
            getJsonObject("users").getJsonObject(userId).apply {
                put("user_name", userName)
                put("name_alias", nameAlias)
                put("company", company)
            }
        }

        val response = JsonObject().apply {
            put("success", true)
            put("action", "update")
            put("current_rows", users)
        }

        context.response().statusCode = 200

        context.response().putHeader("Content-Type", "application/json")
        context.response().end(response.encode())
    }

    private fun deleteUser(context: RoutingContext) {
        val userId = context.request().getParam("user_id")

        users.getJsonObject("users").remove(userId)

        val response = JsonObject().apply {
            put("success", true)
            put("action", "delete")
            put("current_rows", users)
        }

        context.response().statusCode = 200

        context.response().putHeader("Content-Type", "application/json")
        context.response().end(response.encode())
    }
}

Обратите внимание на функцию start.

В начале мы с помощью объекта Router определяем все доступные для нашего эндпоинта маршруты (routes). Как вы могли заметить, они определены таким образом, что эндпоинт реализовывает полную CRUD функциональность.
Далее мы определяем работающий асинхронно экземпляр веб-службы Vert.x, который слушает порт 8282.

Остальной код - это собственно реализация API.

Пишем класс, который будет разворачивать наш API

Класс, который отвечает за запуск нашего Vert.x веб-сервиса - это MainVerticle.kt. 

Вот его код:
 

package verticle

import io.vertx.core.Promise
import io.vertx.rxjava3.core.AbstractVerticle
import io.vertx.rxjava3.core.RxHelper

class MainVerticle : AbstractVerticle() {
    override fun start(promise: Promise<Void>) {
        RxHelper.deployVerticle(vertx, HttpServerVerticle())
            .subscribe(
                { promise.complete() },
                promise::fail)
    }
}

Пишем главную функцию

Главная функция, которую мы расположим в файле App.kt и которая будет запускать MainVerticle выглядит так:

import io.vertx.core.Launcher
import verticle.MainVerticle

fun main() {
    Launcher.executeCommand("run", MainVerticle::class.java.name)
}

Собираем всё воедино и запускаем наш REST API-сервис

После того как Вы закончите писать код и затем соберете его, запустите gradle-задачу cleanAndJar.

Далее, имея на руках сгенерированный jar-файл, запустите его:

$ java -jar build/libs/SimpleRestApi-1.0.0.jar

Осталось протестировать то, что мы создали.

Для этих целей можно к примеру использовать клиент Postman.

Заключение

И так, теперь у Вас есть базовое представление о том, как простроить простой REST API-сервис на Kotlin с помощью Vert.x.
В следующих статьях мы продолжим знакомство с инструментом Vert.x и попробуем создать более сложные вариации REST API.

Выражаю благодарность автору Mei Rizal F, так как данная статья является моим отредактированным переводом его статьи "Build Simple REST API Using Vert.x in Kotlin".

Полный исходный код рассмотренного нами приложения, Вы можете найти на странице автора в GitHub по ссылке https://github.com/merizrizal/vertx-simple-rest-api