Kotlin is a popular tool for Android developers, but as you know, this is not its only use. So when I decided to write a simple web service, it seemed sensible to do it on Kotlin.
It turns out that the Spring Framework is not the only option. There is another powerful asynchronous alternative - Vert.x, which for some reason is rarely mentioned in the context of Kotlin. About this tandem and talk in this article.
Starting a project, I wanted the impossible: I could write a prototype quickly, and just host some Heroku, and if necessary, expand the prototype to a full-fledged project without rewriting it from scratch.
Official documentation and examples from good bloggers in one voice recommended the Spring Framework, citing good compatibility and even native support for Kotlin in a future version. But if you think so, do you need some special compatibility? The language is already friendly with Java, so you choose any framework, import the standard library and go ahead.
Vert.x is an asynchronous event-oriented framework for any application, with a module for the web. The architecture is similar to Node.js, so much so that the project even began its existence in 2011 under the name "Node.x", and only then the creator Tim Fox considered it risky and remembered another synonym for the word "node" ("node" and " vertex "is a" node "in graph theory). Unlike Node.js, which is limited to JavaScript, Vert.x also supports Java, Groovy, Ruby, and Ceylon (in the past, it also supported Python, Scala, and Clojure).
I was interested in the following Vert.x options:
This concludes the description of the framework itself, because this site already had a good article about it. My task is to show how you can use all these amenities in Kotlin.
Suppose we need a web service that will return a list of islands (for example, Kotlin ) and the countries in which these islands are located in JSON format by REST model.
GET /islands
GET /countries
GET /countries/:code
I agree, this is not a particularly useful web service, but this is quite enough to demonstrate the framework, avoiding unnecessary details of the project and other libraries that only distract from the main theme.
Let's start with the data that the web service will return. Models need only two: Island
and Country
.
data class Island(val name: String, val country: Country) data class Country(val name: String, val code: String)
Thanks to the date classes in Kotlin, you don’t have to worry about anything else - the methods equals()
, hashCode()
, getters and setters are all automatically embedded in this simple design.
Further IslandDao
for data access: in the real application there will be requests to a certain database, and we have a simple static array with harvested islands.
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 } }
Overview of methods:
fetchIslands()
returns the entire list of islands with their countriesfetchCountries(code)
map
- pulls countries from the list of islandsdistinct
- remarks (Australia)filter
- filters by a given code (if present)sortedBy
- sorts by codesThis minimum DAO is enough to go to the application itself.
The heart of the Vert.x application is the verticals themselves. My fantasy is bad, so let's call it "MainVerticle".
class MainVerticle : AbstractVerticle()
To begin with, we will create a field in it for the DAO, which we have already written above.
private val dao = IslandsDao()
Now the important part: a router that will distribute requests by type and path. To begin with we will disassemble the simplest route.
private val router = Router.router(vertx).apply { get("/").handler { ctx -> ctx.response().end("Welcome!") } }
This is a root GET route that returns the plain text "Welcome!".
But why do we need the text? We'd rather JSON serialize objects. To do this, write the endWithJson(Any)
extension in endWithJson(Any)
, which ends the request chain only by filling out the "Content-Type" header with the JSON format and serializing any object that is passed to it.
fun HttpServerResponse.endWithJson(obj: Any) { putHeader("Content-Type", "application/json; charset=utf-8").end(Json.encodePrettily(obj)) }
Now you can add a couple of routes to the router, which will take lists of data from the DAO and return them as 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) }
Already more interesting and useful, is not it?
From the task, there was only a route to search for countries by code.
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()) } }
Everything is almost the same as in the previous ones, only the parameter was added :code
to the path itself (which can be retrieved using HttpServerRequest.getParam(String)
) and, in addition to the successful end()
, it also appeared fail()
with the HTTP error code in case of not found country.
So, the router is ready. It remains only to collect the server itself. It sounds, admit, much grander than it actually is.
In the AbstractVerticle abstract class, there is a start()
method that is called when the vertical is started. The procedure for starting the web server is placed just there.
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()) } } }
The code above does the following:
This completes the code of the application itself, now the configuration is magic!
The configuration will live in the Gradle script "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" } }
First, the buildscript
part where we set versions and plugins (in this case only one).
plugins { id 'java' id 'application' id 'com.github.johnrengelman.shadow' version '1.2.4' } apply plugin: 'kotlin'
Next, we use the specified and embedded plugins.
The first two, "java" and "application", are needed as a skeleton of a Java application, on the basis of which we build everything.
The "kotlin" specified above is all that is needed in terms of setting up a Kotlin application.
The " shadow " plugin is used here so that the created JAR is "fat" ("fat jar"), that is, it contains all the libraries used. This makes it much easier to deploy, but for this we need to configure it as well.
shadowJar { baseName = 'app' classifier = 'shadow' manifest { attributes 'Main-Verticle': 'net.gouline.vertxexample.MainVerticle' } mergeServiceFiles { include 'META-INF/services/io.vertx.core.spi.VerticleFactory' } }
The first two fields "baseName" and "classifier" indicate how the output JAR should be called (i.e. "app-shadow.jar") so that the deployment script can be easily found. In addition, we configure the path to the vertical, written earlier, and to the standard 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" }
Now we apply the required libraries, in this case, we need only three of them:
sourceCompatibility = '1.8' mainClassName = 'io.vertx.core.Launcher'
Finally, we install Java 8 source compatibility (this is the minimum for Vert.x) and the main class at launch, which will be the built-in Launcher
.
That's all, the configuration is ready!
Building on a local computer is very simple: gradle run
to run on localhost or gradle shadowJar
to export a JAR file that can be uploaded to a web server.
But, as I mentioned at the very beginning, I would like everything to work on Heroku too. To do this, simply create a "Procfile" with the following content:
web: java $JAVA_OPTS -Dhttp.port=$PORT -jar build/libs/app-shadow.jar
This line describes how to run the application: via java
, specifying the port number (which is decided by Heroku itself) and, finally, the same “app-shadow.jar”, ​​which we have registered in “build.gradle”.
That's all! Now this application can be completely filled in with Git remout, as the Heroku documentation describes, and enjoy the result.
I hope I convinced someone to try Kotlin, Vert.x, or both. There is plenty of documentation (official and amateur) for both projects, so it’s easy to figure out how to write a more complex application.
Although the Vert.x documentation does not have a section for Kotlin, it uses the Java API, so the functions of one language are rather trivially transferred to another. Moreover, when copying examples in Java to the Kotlin class, IntelliJ IDEA itself will offer to convert the code automatically.
The full version of the project can be found in "vertx-kotlin-example" on GitHub, which I support with all the updates and some extensions. This version starts easily after the jump and even deploys to Heroku.
Thanks for attention!
Source: https://habr.com/ru/post/322406/
All Articles