7

御剑_lingen的个人空间

 3 years ago
source link: https://my.oschina.net/lingenliu/blog/4947685
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. 乘座交通工具去公司
  2. 早上开例会,安排任务

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

#这是伪代码,不要当真

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.xKotlin的结合。并对它们的表现非常满意

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

  • 简洁优雅,而不失可维护性
  • 较为完整的生态
  • 性能上的绝对优势

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

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

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

如笔者在一个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](https://my.oschina.net/azibug)
    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.xKotlin协程的结合,提供了类似的解决方案,使得我们在异步编程中,仍然能以符号人类思维的方式来编码。

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

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

笔者仅举一例来说明

本代码摘自于笔者的myddd-java框架,基于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基本在每一方面都有自己的解决方案,这是非常明显的一个优势。

性能上的绝对优势

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

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

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

vert.x性能

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

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

myddd-vertx

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

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

基于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](https://my.oschina.net/u/1260961)
@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://taoofcode.cc 或关公众号:【微言码道】 - 用我们微小的力量传播编码之道


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK