- PVSM.RU - https://www.pvsm.ru -
Kotlin — популярный инструмент у разработчиков на Android, но, как известно, это не единственное ему применение. Поэтому когда я решился написать простой веб-сервис, показалось разумным сделать это как раз на Kotlin.
Оказывается, Spring Framework — это не единственный вариант. Существует еще одна мощная асинхронная альтернатива — Vert.x, которая почему-то редко упоминается в контексте Kotlin. Об этом тандеме и поговорим в этой статье.
Начиная проект, хотелось невозможного: и прототип написать быстро, и хостить просто на каком-нибудь Heroku, и при надобности расширить прототип до полноценного проекта не переписывая с нуля.
Официальная документация и примеры от добрых блоггеров в один голос рекомендовали Spring Framework, ссылаясь на хорошую совместимость и даже родную поддержку для Kotlin в будущей версии. Но если так подумать, нужна ли какая-то особая совместимость? Язык и так дружит с Java, поэтому выбираешь любой фреймворк, импортируешь стандартную библиотеку и вперед.
Vert.x — это асинхронный событийно-ориентированный фреймворк для любых приложений, с модулем для веб. Архитектура схожа с Node.js, настолько, что проект даже начал свое существование в 2011 году под названием "Node.x", а уж потом создатель Тим Фокс посчитал это рисковым и вспомнил другой синоним к слову "node" ("node" и "vertex" — это "узел" в теории графов). В отличие от Node.js, который ограничен на JavaScript, Vert.x поддерживает еще и Java, Groovy, Ruby и Ceylon (в прошлом так же поддерживал Python, Scala и Clojure).
Меня заинтересовали следующие параметры Vert.x:
На этом завершу описание самого фреймворка, ибо на этом сайте уже была хорошая статья [2] про это. Моя задача показать, как можно использовать все эти удобства в Kotlin.
Допустим нам нужен веб-сервис, который будет возвращать список островов (например, Котлин [3]) и стран, в которых эти острова находятся, в формате JSON по модели REST.
GET /islands
GET /countries
GET /countries/:code
Соглашусь, не особенно полезный веб-сервис, но этого вполне достаточно для демонстрации фреймворка, избегая лишние подробности проекта и других библиотек, которые только отвлекают от основной темы.
Начнем с данных, которые веб-сервис будет возвращать. Модели нужны всего две: Island
и Country
.
data class Island(val name: String, val country: Country)
data class Country(val name: String, val code: String)
Благодаря дата классам в Kotlin, больше ни о чем волноваться не надо — методы equals()
, hashCode()
, геттеры и сеттеры все автоматически зашиты в эту простую конструкцию.
Дальше IslandDao
для доступа к данным: в реальном приложении здесь будут запросы в некую базу данных, а у нас простой статичный массив с заготовленными островами.
class IslandsDao {
companion object {
private val MOCK_ISLANDS by lazy {
listOf(
Island("Kotlin", Country("Russia", "RU")),
Island("Stewart Island", Country("New Zealand", "NZ")),
Island("Cockatoo Island", Country("Australia", "AU")),
Island("Tasmania", Country("Australia", "AU"))
)
}
}
fun fetchIslands() = MOCK_ISLANDS
fun fetchCountries(code: String? = null) =
MOCK_ISLANDS.map { it.country }
.distinct()
.filter { code == null || it.code.equals(code, true) }
.sortedBy { it.code }
}
Краткий обзор методов:
fetchIslands()
возвращает весь список островов с их странамиfetchCountries(code)
map
— вытаскивает страны из списка острововdistinct
— отметает повторные (Австралию)filter
— фильтрует по заданному коду (если таковой присутствует)sortedBy
— сортирует по кодамТакого минимального DAO достаточно, чтобы переходить к самому приложению.
Сердце Vert.x приложения — это сами вертиклы. У меня фантазия плохая, поэтому назовем его "MainVerticle".
class MainVerticle : AbstractVerticle()
Начнем с того, что создадим в нем поле для DAO, который уже написали выше.
private val dao = IslandsDao()
Теперь важная часть: маршрутизатор, который будет распределять запросы по типу и пути. Для начала разберем самый простой маршрут.
private val router = Router.router(vertx).apply {
get("/").handler { ctx ->
ctx.response().end("Welcome!")
}
}
Это рутовый GET маршрут, который возвращает обычный текст "Welcome!".
Но зачем нам текст? Нам бы лучше JSON сериализацию объектов. Для этого в утилях пишем расширение endWithJson(Any)
, которое заканчивает цепь запроса, только предварительно заполнив заголовок "Content-Type" с JSON форматом и сериализовав любой объект, который ему передали.
fun HttpServerResponse.endWithJson(obj: Any) {
putHeader("Content-Type", "application/json; charset=utf-8").end(Json.encodePrettily(obj))
}
Теперь можно добавить в маршрутизатор еще пару маршрутов, которые возьмут списки данных из DAO и вернут их в виде JSON.
get("/islands").handler { ctx ->
val islands = dao.fetchIslands()
ctx.response().endWithJson(islands)
}
get("/countries").handler { ctx ->
val countries = dao.fetchCountries()
ctx.response().endWithJson(countries)
}
Уже интереснее и полезнее, не так ли?
Из поставленной задачи, остался только маршрут для поиска стран по коду.
get("/countries/:code").handler { ctx ->
val code = ctx.request().getParam("code")
val countries = dao.fetchCountries(code)
if (countries.isEmpty()) {
ctx.fail(404)
} else {
ctx.response().endWithJson(countries.first())
}
}
Все почти так же, как и в предыдущих, только добавился параметр :code
к самому пути (который можно извлекать с помощью HttpServerRequest.getParam(String)
) и, вдобавок к успешному end()
, появился еще и fail()
с HTTP кодом ошибки на случай не найденной страны.
Итак, маршрутизатор готов. Осталось только собрать сам сервер. Звучит, признаться, намного грандиознее, чем на самом деле.
В абстрактном классе AbstractVerticle
есть метод start()
, который вызывается при запуске вертикла. Процедуру запуска веб-сервера помещаем как раз туда.
override fun start(startFuture: Future<Void>?) {
vertx.createHttpServer()
.requestHandler { router.accept(it) }
.listen(Integer.getInteger("http.port", 8080)) { result ->
if (result.succeeded()) {
startFuture?.complete()
} else {
startFuture?.fail(result.cause())
}
}
}
Код выше делает следующее:
На этом код самого приложения завершен, теперь магия конфигурации!
Внимание! В объяснении ниже, я притворюсь, что версия Kotlin 1.1 уже вышла в продакшн, чтобы не засорять код конфигурацией EAP репозитория (где живут не выпущенные версии языка). Ждать осталось уже не долго — на момент написания, 1.1 уже в стадии Release Candidate. В проекте на GitHub все, конечно же, в рабочем состоянии, без всяких упрощений.
Конфигурация будет жить в Gradle скрипте "build.gradle"
buildscript {
ext {
kotlin_version = '1.1.0'
vertx_version = '3.3.3'
}
repositories {
jcenter()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
Сначала buildscript
часть, где задаем версии и плагины (в данном случае только один).
plugins {
id 'java'
id 'application'
id 'com.github.johnrengelman.shadow' version '1.2.4'
}
apply plugin: 'kotlin'
Далее применяем заданные и встроенные плагины.
Первые два, "java" и "application", нужны как скелет Java приложения, на основе которого мы все строим.
Заданный выше "kotlin" — это все, что нужно с точки зрения настройки Kotlin приложения.
Плагин "shadow [4]" здесь используем для того, чтобы создаваемый JAR был "толстым" ("fat jar"), то есть, содержал в себе все используемые библиотеки. Это намного упрощает деплой, но для этого нам понадобится его еще и настроить.
shadowJar {
baseName = 'app'
classifier = 'shadow'
manifest {
attributes 'Main-Verticle': 'net.gouline.vertxexample.MainVerticle'
}
mergeServiceFiles {
include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
}
}
Первые два поля "baseName" и "classifier" указывают, как должен называться JAR на выходе (т.е. "app-shadow.jar"), чтобы деплой скрипту можно было легко его найти. Помимо этого настраиваем путь к вертиклу, написанному раннее, и к стандартному VerticleFactory
.
repositories {
jcenter()
}
dependencies {
compile "io.vertx:vertx-core:$vertx_version"
compile "io.vertx:vertx-web:$vertx_version"
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
}
Теперь применяем требуемые библиотеки, в данном случае нам хватит всего трех:
sourceCompatibility = '1.8'
mainClassName = 'io.vertx.core.Launcher'
Наконец, устанавливаем совместимость исходника на Java 8 (это минимум для Vert.x) и главный класс при запуске, которым будет встроенный Launcher
.
Все, конфигурация готова!
Сборка на локальном компьютере очень проста: gradle run
для запуска на localhost или gradle shadowJar
для экспорта JAR файла, который можно залить на веб-сервер.
Но, как я упомянул в самом начале, хотелось бы, чтобы все работало еще и на Heroku. Для этого достаточно создать "Procfile" следующего содержания:
web: java $JAVA_OPTS -Dhttp.port=$PORT -jar build/libs/app-shadow.jar
Эта строчка описывает, как следует запускать приложение: через java
, задавая номер порта (который решается самим Heroku) и, наконец, тот самый "app-shadow.jar", который мы прописали в "build.gradle".
Вот и все! Теперь это приложение можно целиком заливать в Git ремоут, как описывает Heroku документация [5], и радоваться результату.
Надеюсь, я убедил кого-то попробовать Kotlin, Vert.x или оба вместе. Документации (официальной и любительской) для обоих проектов предостаточно, так что разобраться, как написать более сложное приложение, не должно составить особого труда.
Хоть в документации Vert.x и нет раздела для Kotlin, он пользуется API для Java, поэтому функции одного языка достаточно тривиально переводятся в другой. Более того, при копировании примеров на Java в Kotlin класс, IntelliJ IDEA сам предложит конвертировать код автоматически.
Полную версию проекта можно найти в "vertx-kotlin-example" на GitHub, которую я поддерживаю со всеми обновлениями и некоторыми расширениями. Эта версия легко запускается после скачки и даже деплоится в Heroku.
Спасибо за внимание!
Автор: mgouline
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/245678
Ссылки в тексте:
[1] Netty: http://netty.io/
[2] статья: https://habrahabr.ru/post/276771/
[3] Котлин: https://ru.wikipedia.org/wiki/%D0%9A%D0%BE%D1%82%D0%BB%D0%B8%D0%BD
[4] shadow: https://github.com/johnrengelman/shadow
[5] Heroku документация: https://devcenter.heroku.com/articles/git
[6] vertx-kotlin-example: https://github.com/mgouline/vertx-kotlin-example
[7] vertx-examples: https://github.com/vert-x3/vertx-examples
[8] Vert.x Core Manual: http://vertx.io/docs/vertx-core/java/
[9] Vert.x-Web: http://vertx.io/docs/vertx-web/java/
[10] What's New in Kotlin 1.1: https://kotlinlang.org/docs/reference/whatsnew11.html
[11] Источник: https://habrahabr.ru/post/322406/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best
Нажмите здесь для печати.