为艺术而技术

Kotlin与Vertx

September 13, 2019

做一个东西久了,就很容易形成思维定势,好在我所处的行业是所有行业中最不推崇墨守成规的。在Java Web开发行业中,Servlet可谓源源流长,系出名门,最新的版本已经到了4.0版本了。一个web容器如果说自己不支持Servlet都不好意思出门。可是谁能想到,当初Servlet也是从CGI手里夺得江山的,一旦封王,横行至今。天下大道,后浪推前浪,自从一帮不安份的人把JS的性能通过JIT提高了10倍之后,就有大神把JS直接推到了服务端,也就是大名鼎鼎的NodeJS,生生从Servlet的手中夺得一片天下。还好JVM这边也不是吃素的,赶紧的学习借鉴,你不是有Functional Programming吗,我也搞出来了Scala,Kotlin,你不是Reactive Programming吗,嘿嘿,我也跟着Reactive StreamsRxJava,就连JVM业界翘楚Spring也都赶紧表示自己也没有落下,推出了基于Reactorwebflux,一时间大家都取长补短,一派欣欣向荣之状。可天下总有那种好事之徒,动不动就掀桌子重干,业内叫“重新发明轮子”。这不,有人一不做二不休,干脆从头到位来个React,你不是要观察者模式吗?干脆,咱重启炉灶,直接搞了一个JVM版本(也有其他语言版本)的NodeJS,就这份豪气和干劲,佩服佩服。这就是今天这位大神Vert.x,从1.0干到3.0,业务越做越大,直接形成了一个巨大的生态圈(GraphQL, Web Client, Web API/Swagger, Data Access…),不客气的说,有点想当年Spring出来的气势,事实上,已经有人做了对比,Vert.x优势不小阿。特别是性能方面,这里这里都说Vertx的性能要好不少。究其根源,可能和不用那么多的Annotion以及Reflection有关。

稍微说下Reactive Programming这个东西,本来只不过是观察者模式的一个实现,为何能越做越大呢,说到底,还是思维方式的转变,工厂重要,还是产品重要? 当然还是产品重要,所以最终是工厂围着产品转,在IT业内这叫以数据为导向,一切都是要为数据服务,而数据流最讨厌的就是死板。想想以下城市的自来水管道和多米诺骨牌,本质上都是构建反应模式,然后数据驱动。而在今天这个Functional Programming加持下,这种模式如鱼得水,当然这里必须要表扬以下首开风气之先的NodeJS.

20190924 Update: <------------- Reactive Programming 在JVM里面主要是三大流派:RX, AKKA 和 Reactor(Spring Flux用的就是它),其中RX是主流,RX中处理back pressure的API已经被吸收进入JAVA 9(java.util.concurrent.Flow)了,也就是RXJava2,Vertx对RXJava2也有不错的支持,后来看了这篇文章 以及 这篇文章才发现实际上,由于Kotlin的coroutine的缘故,Kotlin版的Flow更加高效,所以今后的方向是Vertx + Kotlin Flow

当然,如果是继续学习RXJava2 以及 Vertx,由Clement Escoffier写的这两篇文章不可错过:

------------>

废话不多说,今天直接用Vert.x搞了个简配版的CRUD练练手,感受一下。

Gradle 配置

关键是如下几个:

plugins {
  id 'org.jetbrains.kotlin.jvm' version '1.3.20'
  id 'application'
  id 'com.github.johnrengelman.shadow' version '5.0.0'
}

dependencies {
  implementation "io.vertx:vertx-web:$vertxVersion"
  implementation "io.vertx:vertx-lang-kotlin:$vertxVersion"

  testImplementation "io.vertx:vertx-junit5:$vertxVersion"
  testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitJupiterEngineVersion"
  testImplementation "org.junit.jupiter:junit-jupiter-api:$junitJupiterEngineVersion"
}

shadowJar {
  classifier = 'fat'
  manifest {
    attributes 'Main-Verticle': mainVerticleName
  }
  mergeServiceFiles {
    include 'META-INF/services/io.vertx.core.spi.VerticleFactory'
  }
}

run {
  args = ['run', mainVerticleName, "--redeploy=$watchForChange", "--launcher-class=$mainClassName", "--on-redeploy=$doOnChange"]
}

可以看出,里面根本就没有Jetty容器的事,后台看了下加载包,才知道,默认用了Netty。也不错,好马,新鞍。

Http Rest

    var products = mutableMapOf<String, Any?>()
    fun addProduct(product: JsonObject) {
        products[product.getString("id")] = product
    }
    fun setUpInitialData() {
        addProduct(json {
            obj(
                "id" to "prod3568",
                "name" to "Egg Whisk",
                "price" to 3.99,
                "weight" to 150
            )
        })
    }
    fun handleGetProduct(routingContext: RoutingContext) {
        var productID = routingContext.request().getParam("productID")
        var response = routingContext.response()
        if (productID == null) {
            sendError(400, response)
        } else {
            var product = products[productID]
            if (product == null) {
                sendError(404, response)
            } else {
                response.putHeader("content-type", "application/json").end(product.toString())
            }
        }
    }
    fun sendError(statusCode: Int, response: HttpServerResponse) {
        response.setStatusCode(statusCode).end()
    }
    fun handleListProducts(routingContext: RoutingContext) {
        var arr = json {
            array()
        }
        for ((_, v) in products) {
            arr.add(v)
        }

        routingContext.response().putHeader("content-type", "application/json").end(arr.toString())
    }
    fun handleAddProduct(routingContext: RoutingContext) {
        var productID = routingContext.request().getParam("productID")
        var response = routingContext.response()
        if (productID == null) {
            sendError(400, response)
        } else {
            var product = routingContext.getBodyAsJson()
            if (product == null) {
                sendError(400, response)
            } else {
                products[productID] = product
                response.end()
            }
        }
    }
    override fun start() {

        setUpInitialData()

        var router = Router.router(vertx)

        router.route().handler(BodyHandler.create())
        router.get("/products/:productID").handler({ handleGetProduct(it) })
        router.put("/products/:productID").handler({ handleAddProduct(it) })
        router.get("/products").handler({ handleListProducts(it) })

        vertx.createHttpServer().requestHandler(router).listen(8080)
    }

看看代码就知道,相当简单粗暴,直接就是router.get().handler(…)就是这样,CRUD直接搞定,你要怎样action,自己随便定义就是。虽然现在代码还有点多,估计随着vert.x-web的升级,还可以表现更好。


Qingfei Yuan

Written by Qingfei Yuan who builds useful things.

© 2019 - 2020 yuanqingfei
Creative Commons License