用Kotlin写Elasticsearch究竟有多爽?

前言

目前Elasticsearch有好几个客户端工具,最常见的可能是RestHighLevelClient,但在官网中表示,此工具也已经过时了,取而代之的是elasticsearch-java,下面是他的一个基本用法,可以看到的是,新工具利用java8的一些特性,让我们写起来更直观,更有层次感。

RestClient restClient = RestClient.builder(
    new HttpHost("localhost", 9200)).build();

ElasticsearchTransport transport = new RestClientTransport(
    restClient, new JacksonJsonpMapper());
ElasticsearchClient elasticsearchClient = new ElasticsearchClient(transport);

SearchResponse<TbBlog> search = elasticsearchClient.search(s -> s
        .index("products")
        .query(q -> q
            .term(t -> t
                .field("name")
                .value(v -> v.stringValue("bicycle"))
            )),
    TbBlog.class);

但是在Kotlin下,更有层次感的写法是这个样子,怎么样,是不是很喜欢。

es {
    size = 2
    query {
        match {
            fieldName = "blogTitle"
            fieldValue = "Java"
        }
    }
    onHits {
        for (hit in it.hits) {
            println(hit.sourceAsMap.get("blogTitle"))
        }
    }
}

上面这段代码核心搜索依然是使用了RestHighLevelClient,在语法上之所以能这样写,是用了Kotlin的特性,即高阶函数。

高阶函数

高阶函数的定义非常简单:一个函数如果参数类型是函数或者返回值类型是函数,那么这就是一个高阶函数。

如下面的createUser方法,参数是一个函数,这个函数的输入是一个User对象,并且无返回值,可以理解为,通过invoke方法调用并传入User实例对象,这个函数将会对这个User进行改变,最终的输出结果为张三

class User {
    var name: String = ""
}
fun createUser(config: (User) -> Unit) {
    val user = User().apply { config.invoke(this) }
    println(user.name)
}

fun main() {
    createUser {
        it.name = "张三"
    }
}

但还有一种写法,如下,区别是加了一个.()

fun createUser(config: (User).() -> Unit) {
    val user = User().apply { config.invoke(this) }
    println(user.name)

}

这种写法可以在大括号中使用this,表示传递过来的参数实例,而this则可以省略。

createUser {
    name = "张三"
}

这样就可以嵌套了,完整的查询如下。


abstract class QueryField {
    lateinit var fieldName: String
    lateinit var fieldValue: String
}

class EsTermConfig : QueryField() {
}

class EsMatchConfig : QueryField() {}

class EsQueryConfig {
    var term: EsTermConfig? = null
    var match: EsMatchConfig? = null
    fun term(esTermConfigFn: (EsTermConfig).() -> Unit) {
        EsTermConfig().run {
            esTermConfigFn.invoke(this)
            term = this
        }
    }

    fun match(esMatchConfigFn: (EsMatchConfig).() -> Unit) {
        EsMatchConfig().run {
            esMatchConfigFn.invoke(this)
            match = this
        }
    }
}

class EsConfig {
    var query: EsQueryConfig? = null
    var size = 20
    var from = 0

    lateinit var responseFn: (SearchResponse) -> Unit
    lateinit var hitsResponseFn: (SearchHits) -> Unit
    fun query(searchConfig: (EsQueryConfig.() -> Unit)) {
        EsQueryConfig().run {
            searchConfig.invoke(this)
            query = this
        }
    }

    fun onResult(result: (SearchResponse) -> Unit) {
        responseFn = result
    }

    fun onHits(result: (SearchHits) -> Unit) {
        hitsResponseFn = result
    }
}

fun es(esConfigFn: (EsConfig.() -> Unit)) {
    val esConfig = EsConfig()
    esConfigFn.invoke(esConfig)


    val builder = RestClient.builder(HttpHost("localhost", 9200))
    val basicCredentialsProvider = BasicCredentialsProvider()
    basicCredentialsProvider.setCredentials(
        AuthScope.ANY,
        UsernamePasswordCredentials("elastic", "cYjC2iTH4SUjXEWwGAkT")
    )
    builder.setHttpClientConfigCallback {
        it.setDefaultCredentialsProvider(
            basicCredentialsProvider
        )
    }
    val restHighLevelClient = RestHighLevelClient(builder)
    val searchRequest = SearchRequest()
    val searchSourceBuilder = SearchSourceBuilder()
    esConfig.query?.term?.run {
        searchSourceBuilder.query(QueryBuilders.termQuery(this.fieldName, this.fieldValue))
    }
    esConfig.query?.match?.run {
        searchSourceBuilder.query(QueryBuilders.matchQuery(this.fieldName, this.fieldValue))
    }

    searchSourceBuilder.from(esConfig.from)
    searchSourceBuilder.size(esConfig.size)

    searchRequest.source(searchSourceBuilder)
    val response = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT)

    esConfig.hitsResponseFn.invoke(response.hits)
    esConfig.responseFn.invoke(response)
}


fun main() {
    es {
        size = 2
        query {
            match {
                fieldName = "blogTitle"
                fieldValue = "Java"
            }
        }
        onResult {
        }
        onHits {
            for (hit in it.hits) {
                println(hit.sourceAsMap.get("blogTitle"))
            }
        }
    }

}

当然这只是一个示例,还没有完成聚合、mulit查询等功能,说不定在以后会单独写一个elasticsearch-kotlin-dsl得一个库。

猜你喜欢

转载自juejin.im/post/7125799522864726030