写点什么

为什么我会选择 Vert.x 与 Kotlin

用户头像
御剑
关注
发布于: 2021 年 02 月 05 日

为什么要重新选择后端技术

过去的一年 2020 对笔者来说是非常有价值的一年,笔者在公司工作上大部分精力都花费在基于 TypeScript + React 的 Electron 桌面开发及前端开发以及WorkPlus Lite移动端开发等工作上。


而在后端方面,2020 年笔者在自己的一个业余项目上使用了 Spring Boot 技术,并整理抽象出了一个基于 DDD 领域驱动风格的开源框架mydddd-backend


笔者非常清楚,在后端技术方面,Spring 仍然是主流,凭借强大的生态及完整的解决方案,Spring 依然是大部分公司及团队的第一选择。这也是笔者在整理myddd-backend框架时为什么选择基于 Spring Boot 来实现的原因所在。因为笔者相信它能适合大多数团队。


进入 2021 年,笔者觉得需要重新关注下后端技术,以思考是否需要选择新的技术做为笔者后端技术解决方案,之所以有这种想法,也是基于以下几个原因


  • 在使用 Spring Boot 的过程中,仍然感觉它非常中规中矩,说不上哪不好,毕竟是一个完整的生态及解决方案。但始终感觉不到其的魅力所在。

  • 近些年兴起的一些新的编程理念与语言让笔者一直想尝试下,如响应式编程以及 Kotlin 这个号称Better Java的语言等。事实上,在 Google 的推动下,Kotlin 被人接受的程度越来越高,使用的程序员也越来越多了。

  • 传统 Java 语言及阻塞式编程并无问题,笔者认为它仍是大多数团队与公司的第一选择。但非阻塞式的异步编程的优点也非常突出,如果程序员及团队能掌控,确实可以尝试。


在这些原因的影响下,笔者也一直在思考是否要重新选择新的技术栈来尝试。


经过一些思考与了解及尝试后,笔者选择了 VertX Kotlin 的解决方案。在业余时间的一些尝试后,笔者对它是非常满意的,并认定它将是未来笔者在后端的主要技术栈。

为什么响应式编程没有成为主流?

如笔者上述所言,类似的响应式编程在性能上有极大的优势,但它一直未能成为主流。笔者也在思考这个现象。总结出部分原因如下:

原因一:思维差异+可维护性差

这些年,响应式编程的概念很火,但事实上一直未能成为主流。响应式编程有着非常好的性能优势,非阻塞式的实现机制比阻塞式的实现机制确实好很多,但它仍有一个非常难以解决的问题,那就是

响应式编程带来的异步编程思维并不符合人类的思维

人的思维是什么,我们理解一个事情的基本思维仍是面向对象及过程的,比如我们理解的上班是这样的

  1. 先起床

  2. 乘座交通工具去公司

  3. 早上开例会,安排任务

  4. 开始编码

如果就这件事,我们按照传统的面向对象及阻塞式的思维来编码,它是这样的

#这是伪代码,不要当真
class Coder{
public void work(){ this.getUp() this.driveToOfficePlace() this.joinMeeting() this.code() } }
复制代码

我们可以明显看到,这个编码与我们的思维是完全一致的,这就是我们所固有的习惯,阻塞式及同步的方式,是符合我们的思维的。

如果我们用一种响应式编程中的异步编程来实现,大致的代码可能是这样的

#这是伪代码,不要当真
class Coder{
public void work(){ this.getUp().onSuccess(()->{ this.driveToOfficePlace(()->{ this.joinMeeting(()->{ this.code() }) }) }) } }
复制代码

上面这个已经很简化了,事实上的业务不可能这么简洁,你可以想象当这个代码中充满各种代码细节时的场景。

大致上所有的异步编程都有这种风格,因为这种风格与我们人类思维上存在差异,所以有个非常著名的名字来称为它:回调地狱

当然,写 Java 的可能对这个不太清楚,但前些年,使用 NodeJs 的程序员对它可谓不所不知。事实上,移动端也一并存在类似的问题。

而且很明显,这种风格的代码在阅读与理解上相比更困难,这也是它可维护性较差的原因之一,也因此一并造成很多程序员写不好类似风格的代码,一方面思维上的不协调,而另一方面可维护性上也不是很好,而大多数公司和团队仍然有赖于大多数程序员的工作,这也是类似的编码模式一直未能成为主流的主要原因。

原因二:生态较差

如果我们说生态,那坦率的说,没有比 Java 语言生态更强大的语言了,这也是之所以这么多年,不喜欢 Java 的人非常多,但 Java 一直是后端的主力开发语言。 相比较而言,一些响应式的框架如果从生态上相比,就比 Java 差远了。类似 RXJava 等响应式编程语言,更多的是属于一个技术类库,其在生态上的不足也必然会阻碍一些程序员。

举例来说: 我如何用异步方式与数据库打交道?是否支持微服务?如何做 OAUTH2 权限?在 Java 的世界,你不需要为这些担忧,任何一个问题都有一大批成熟的解决方案。但在异步编程的世界,就相对差了很多。

为什么笔者会选择 Vert.x 与 Kotlin 的结合

但凡事并无绝对,基于对未来的一些考量,笔者还是希望能在这方面有所建树,所以近期关注并研究了一些技术。最终选择了 Vert.x Kotlin 的结合。并对它们的表现非常满意

在尝试后,笔者认为至少在以下几个方面,它们是笔者值得选择的理由

  • 简洁优雅,而不失可维护性

  • 较为完整的生态

  • 性能上的绝对优势

理由一:简洁优雅,而不失可维护性

其一:改善了回调地狱的现象

事实上,如笔者所述的前面的回调地狱问题,这个已经有较好的解决方案了。

如笔者在一个 Electron 桌面开发的代码中,是这样使用异步的

本代码摘自笔者的基于Electron开发的一个跨平台软件

    #TypeScript代码    public static async syncFavors(): Promise<Contact[]> {        //从网络获取星标联系人,这实质上是一个异步行为        const favors = await Contact.getNet().fetchFavorContacts();        if (favors) {            //存储到数据库中,这也是一个异步操作            await Contact.getRespository().batchSaveFavors(favors);        }        return favors;    }
复制代码

如上述代码所示,在 JS 的世界中,早已出现了 Promise 与 await 的来解决这个问题。将非阻塞回调转成同步风格但实质还是非阻塞。

虽然 Vert.x 本身未提供类似的功能,但 Kotlin 协程则提供了。基于它们的结合,就算是在异步编程中,你也可以如同前端 TS 一样,写出类似风格的代码

本代码摘自笔者的myddd-vertx框架,基于Vert.x与Kotlin的响应式领域驱动实现

    @Test    fun testExists(vertx:Vertx, testContext: VertxTestContext){        GlobalScope.launch {            try {                val user =  User(username = "lingen",age = 35)                //这是一个异步代码,但我们用await()来解决回调                val createdUser =  repository.save(user).await()                //这又是一个异步                var exists =repository.exists(User::class.java,createdUser.id).await()                testContext.verify {                    Assertions.assertTrue(exists)                }                testContext.completeNow()            }catch (e:Exception){                testContext.failNow(e)            }
} }
复制代码

可以看出,Vert.x Kotlin 协程的结合,提供了类似的解决方案,使得我们在异步编程中,仍然能以符号人类思维的方式来编码。

其二 Kotlin 本身足够简洁与优雅

几年前,Google 将 Kotlin 取代 Java 选定为 Android 开发的第一语言,这并不是空穴来风的决定。想必 Google 也是在认真考察并认可这门语言才决定的。 事实上也确实如此,Kotlin 号称Better Java,与其它 JVM 语言相比,它更简洁与优雅。

笔者仅举一例来说明

本代码摘自于笔者的myddd-backend框架,基于Java与Spring Boot的领域驱动实现

    private static DocumentRepository repository;
private static DocumentRepository getDocumentRepository(){ if(Objects.isNull(repository)){ Document.repository = InstanceFactory.getInstance(DocumentRepository.class); } return Document.repository; }
复制代码

而在 Vert.x 与 Kotlin 中,实现是这样的

本代码摘自笔者的myddd-vertx框架,基于Vert.x与Kotlin的响应式领域驱动实现

    companion object {        val repository:CommentRepository by lazy { InstanceFactory.getInstance(CommentRepository::class.java) }    }
复制代码

如上述代码所示,同一个获取仓储的方式,在 Kotlin 的代码中,比 Java 的实现好很多。

笔者就不举更多例了,Kotlin 相对 Java,确实是更加简洁与优雅,而又不失可维护性。

较为完整的生态

如笔者前述所言,类似的异步编程也好,响应式编程框架也好,在生态上都存在问题。表现为生态不够完善。 但这一点,在 Vert.x 反而是个优势。

之所以选择 Vert.x,也是因为笔者在看到它的生态之后,才决定更进一步了解它。

Vert.x 基本有自己的一整套生态,意味着你选择它,不用为项目中的任何一个维度的事情发愁,而且这些生态都是官方自己负责维护的,质量也比较有保证。

其在 Web,数据库单元测试权限微服务支持消息事件机制,集群等有完整的解决方案。



如上图所示,Vert.x 基本在每一方面都有自己的解决方案,这是非常明显的一个优势。

性能上的绝对优势

如果没有前两个优势,对笔者而言,这个优势并不足以成为可以考量的优势。因为,笔者始终将代码的可维护性放在第一重要位置来考量。

但如果有前两个优势,那这就成为另一个绝对优势

在国外的性能大对比中,Vert.x 始终处于前列。



而基于 Spring Boot 的实现,则弱于 Vert.x 数倍。

结论

所以,综上所述,如果能写出简洁优雅的代码,生态又足够完善,又在性能上足够有优势。为什么不选择它?

myddd-vertx

所以,笔者在正基于 Vert.x 与 Kotlin,按照领域驱动的理念,开发 myddd-vertx 框架。

这个框架已接近完成,后续也会如同 myddd-backend 一样,开源出来。

基于myddd-vertx 部分代码示例

仓储实现

class CommentRepositoryHibernate : EntityRepositoryHibernate(),CommentRepository {
override suspend fun createComment(comment: Comment): Future<Comment> { val future = PromiseImpl<Comment>() require(comment.id == 0L) comment.created = System.currentTimeMillis() var created = save(comment).await() created.rootCommentId = created.id created = save(created).await() future.onSuccess(created) return future }}
复制代码

领域层

@Entity@Table(name = "comment",    indexes = [Index(name = "index_comment_id",columnList = "comment_id"),        Index(name = "index_root_comment_id",columnList = "root_comment_id"),    ])class Comment : BaseEntity() {
/** * 关联文章 */ @Column(name = "comment_id") lateinit var commentId:String
/** * 关联评论根ID */ @Column(name = "root_comment_id") var rootCommentId:Long = 0
/** * 关联回复评论ID */ @Column(name = "parent_comment_id") var parentCommentId:Long = 0
@Column(name = "level") var level:Int = 0
/** * 昵称 */ var author:String? = null
/** * 邮箱 */ var email:String? = null
/** * 回复内容 */ lateinit var content:String
companion object { val repository:CommentRepository by lazy { InstanceFactory.getInstance(CommentRepository::class.java) } }
suspend fun createComment():Future<Comment> { return repository.createComment(this) }
suspend fun createReplyComment(parentComment: Comment):Future<Comment> { return repository.createReplyComment(parentComment,this) }

}
复制代码

单元测试

    @Test    fun testAddComment(vertx: Vertx, testContext: VertxTestContext){        GlobalScope.launch {            val comment = createComment()            val created = commentRepository.createComment(comment).await()            testContext.verify {                Assertions.assertTrue(created.id > 0)                Assertions.assertEquals(created.id,created.rootCommentId)                Assertions.assertEquals(created.level,0)            }
testContext.completeNow() } }
复制代码

以上,敬请期待!!!


---------------

更多优质文章,请访问笔者的个人网站 https://lingenliu.cc 或关公众号:【御剑轩】 - 致力于实践与传播优雅的编码之道




发布于: 2021 年 02 月 05 日阅读数: 38
用户头像

御剑

关注

WHATEVER IT TAKES 2018.07.15 加入

全栈式技术开发

评论

发布
暂无评论
为什么我会选择Vert.x与Kotlin