脚踏实地“敲代码”,同时,不忘偶尔仰望下星空。
本文根据阿里云原生团队资深技术专家的直播整理而成,从领域驱动设计(DDD)、Reactive、Service Mesh 和代码智能等几个方面展开,聊一聊他眼里的软件开发的发展趋势。
今年 4 月份的时候,InfoQ 发布了软件架构与设计的趋势报告。InfoQ 在技术趋势报告中将软件架构分为了 4 类,如下图所示,从左到右依次是创新者(Innovators)、早期采用者(Early Adopters)、早期大众(Early Majority)和晚期大众(Late Majority)。在报告中可以看出,很多技术如微服务(Microservice)、领域驱动设计(Domain-driven Design)等已经非常流行,并成为如今软件开发行业的主流了。对于早期大众和晚期大众部分的架构设计而言,很多部分与领域驱动设计相关,比如微服务、CQRS、事件驱动等。
在本次的分享中,也会介绍为什么领域驱动设计相关的技术会在InfoQ技术报告中占据如此重要的位置。并且也将会根据领域驱动设计展开到其他相关技术,比如 Service Mesh 等。
领域驱动设计(Domain-drivenDesign)
GitHub 上有一个 DDD 实践指南,从下图中可以看到领域驱动设计所解决的问题从架构设计一直到代码层级,所以对于整个软件研发周期来说,DDD 方法论还是比较流行的。而指导微服务划分的一个重要理论基础就是领域驱动设计。之前谈到领域驱动设计,大部分时间总会探讨如何识别限界上下文,如何使用战术或者战略方法等,而本次分享中则会介绍一个主流的趋势,那就是 DDD + Reactive 。
之前的时候,领域驱动设计有分层结构、事件驱动等概念,但是也存在一个很大的问题就是将单体应用划分成多个应用的时候,如何解决应用之间的通信问题。领域驱动设计中有限界上下文映射(BoundedContext Map)的概念,来解决两个限界上下文之间的通信。之前 DDD 谈到的解耦合方式主要是基于消息实现异步化的理论支撑,但是具体如何去做却没有具体的技术栈帮助大家实施。当然了,针对于每一种语言而言,具体实现可能不同,因为一些语言是纯异步化支持的,可能已经内置了协程支持,因此在不同的技术栈里面会选择不同的解决方案。本次分享中比较偏向于 Java 的技术栈,但是需要强调的是 Reactive 并不止针对 Java 。
Reactive 的好处在于使得两个限界上下文之间的通信变成一个技术栈,这样就可以使用Reactive 实施了。JDDD 是一个新的项目,其基于 Spring Data 实现。JDDD 也是一个开发包,其主要思想是让开发者通过开发包以代码的方式来呈现 DDD 要表达的思想。前面所提到的微服务则非常依赖限界上下文,借助它来确定边界并进行划分。此外,在 Could Native 和 FaaS 方案都会提到是面向事件驱动的,但是事件(Event)这一概念在领域驱动设计中很早的时候就已经提出来了,如果涉及到事件或者异步化相关的事情,就需要关注事件和领域驱动设计的关系了,当然目前来说,这一点已经非常成熟了。还有一点就是 DDD + CQRS , CQRS 简单理解就是读写分离,比如 MySQL 数据库使用 Master-Slave 机制使得读数据和写数据分开, Master 主要负责写入, Slave 主要负责读取,而 CQRS 也会在 DDD 中发挥很大的作用。目前来看,在整个架构设计里面,领域驱动设计是非常主流的,能够帮助我们解决一些设计上面的问题。
Reactive
DDD将应用划分成多个小的限界上下文,接下来需要解决如何通信的问题了。而Reactive就提供了不同应用之间进行通信的指导方案,当然了Reactive也提供了对应的技术解决方案,比如Async能够提供全异步化支持,当然这里的异步化只是说基于异步化技术。此外,还有Observable模式,可以说这是一个架构和设计的模式,这一模式应用很广泛,最早是由微软提出的,比如一个表格发生变化,其他表格也会根据这个表格发生相应的变化,这就是Observable观察者模式。针对Java程序员而言,可以发现Spring在主推SpringReactive,很多技术方案都是和Reactive相关的。此外,还有Message和EventDriven,FaaS和CouldNative在做通讯的时候都需要根据Message和EventDriven进行设计。
RSocket
在InfoQ的软件架构和设计趋势图中,在早期采用者阶段有一个FunctionProgramming,在早期大众阶段则有一个ReactiveProgramming,而在创新者部分则有一个RSocket&ReactiveStreams,大家对于这个技术可能了解比较少,因此在本部分简单介绍一下。如果大家关注Spring官方站点,那么可能了解Spring5.2版本已经内置了RSocket的支持,但是并没有很多相关文档来介绍相关技术。前面提到,Reactive是让DDD的通讯模型有了理论基础,所以这个RSocket协议就是为了实现Reactive理论基础设计一个通讯模型。
如果大家了解Reactive语义,就会知道有一个背压模式。当然,背压模式与断路保护模式类似,两者的机制都是为了解决限流问题,因为消费方或者服务提供方因为突发流量不能支撑导致应用雪崩或者宕机。传统模式最早使用消息队列,是消息提供方主动将消息推送给消息消费方,此时存在的问题就是消费方消费不了,但是消息提供方还会继续推送消息,使得消息消费方无法支撑导致系统不稳定。
后来出现了Kafka,其使用Pull模式,也就是需要多少条消息就去拉取多少条消息,因为Kafka支持消息堆积。但是使用Pull模式则需要设置时间间隔,此时出现的问题就是消息轮空怎么办?也就是当发送消息时发现消息没有就会轮空,此时就会不停地向Broker拉数据,这样就会造成网络浪费。所以在Reactive里面的背压是将原来的推送模型增加了开关,当消息到达一定数量之后就不要推送了。这种主动推送机制的好处在于性能特别高,不需要去拉取,也不需要保存消息位点。总之,背压机制能够设置所能够支持的最大拉取数量,如果在阈值的范围内能够做到性能的增高,同时也能保护消息的消费方。
在通讯方面,RSocket有四个模型,比如Request和Response、发布-订阅模型、日志采集等。在使用DDD实现应用划分之后,两个应用之间需要进行通信,而RSocket就提供了四个模型,基本涵盖了应用之间通信的场景,此外还提供了MetadataPush,比如所有的集群变化或者需要做限流都可以通过MetadataPush来实现。
RSocket还可以做对等通讯,传统的通讯模型都是Client-Server模式,但是在RSocket下通信都是对等的,没有Client和Server的分别,相互之间都可以进行消息发送。前面所提到的RSocket的设计模型和理念都是为了解决基于Reactive通信应用的各种问题。虽然RSocket这个技术方案并没有得到很好的推广,但是已经有很多技术社区提供支持了,比如Spring在5.2版本中就增加了对于RSocket的支持。因为推出的通信协议是Reactive的,但是背后需要涉及到数据库操作或者其他等如果不是异步化的,就需要进行处理,Spring在今年发布SpringBoot2.3的时候就完善了对于Reactive的支持。举例而言,从最前端的网关层通过Spring的WebFlask就能够保证是Reactive的。
中间件、通讯模型以及NoSQL产品都是支持异步化操作的,能够解决高并发问题,这些特性在很早的时候就已经添加到Spring里面了。此外,在开发的时候还有一点比较关键的就是数据库,每个Java程序员在访问数据库的时候首先要设置数据库连接池,需要考虑数据库设计方案以及考虑哪一个连接池性能最高等问题。这是因为数据库不属于异步化的,需要通过创建多个连接来解决并发的问题。因此,在Spring的R2DBC里面增加了对于数据库异步化的支持。目前Spring能够支持数据库的异步化了,但是还需要一段时间的等待,因为R2DBC和JDBC一样,也是一个规范+驱动,但是基于JDBC的上层框架非常多,比如Hibernate、MyBatis等,而基于R2DBC的框架并不多,目前只有一个SpringDataR2DBC。因此,国内很多的开发者首选还是MyBatis。如果存在实现异步化的意向,那么基本上是从最开始接入的网关层协议一直到NoSQL数据库,包括存储文件系统,全部都实现异步化,这些目前也比较成熟了。
ServiceMesh
再回到最开始InfoQ的软件架构与设计趋势,可以看到大多数与CloudNative相关,比如Serverless、ServiceMesh、HTTP/2和gRPC等,这些概念非常火爆,因此可以说ServiceMesh在目前的技术趋势中占据很高的地位。典型的ServiceMesh架构还是基于Istio+Envoy的Sidecar经典架构。后来出现了Dapr,它与Isito+Envoy架构的区别在于Envoy的Sidecar可以理解为代理,其目的在于将服务连接起来,Dapr也会有一个Sidecar,这个Sidecar不只是做代理那么简单,而是可以帮助开发者做非常多的事情,比如使用新的开发语言实现应用,但是Kafka或者NoSQL数据库并没有提供对应语言的SDK,或者即便提供了对应语言的SDK,但是这些SDK并不稳定,此时选择一些新的技术就会受到一些约束。举个例子,如果大家做一些大数据相关的工作,比如HBase、Hadoop等都是Java相关的SDK最稳定。而Dapr的Runtime可以很好地与外部系统交互,比如应用gRPC与Dapr的Sidecar通信,也可以和Kafka通信,将从Kafka中获取的数据传递给gRPC,并且Dapr的Runtime使得开发者不需要了解通信协议背后的细节。
这样的设计比Isito+Envoy架构要更好,因为如果只做代理,那么在客户端还需要做大量的数据处理工作,而如果放到Proxy上来实现,就会更加复杂,此时就会变成Dapr的Runtime。此外,阿里巴巴最近在做一些尝试就是RSocketBroker,其基于ReactiveMesh实现,其与Sidecar结构不同,因为大多数基于基于消息或者事件驱动的都只需要发送消息即可,而RSocketBroker提供了应用之间的一揽子通信协议,不需要再选择其他通信协议了。对于以上三种技术方案应该如何选择,大家应该根据自身的实际情况进行判断,因为技术选型没有绝对的正确,但是目前来说是Isito+Envoy架构收到了大多数人的欢迎,因为这与Kurbernates整合比较方便,并且目前已经提供了比较完善的基础设施。
FaaS
对于企业而言,可能核心系统只有几个,但是总会有一些长尾的应用。如果之前没有很好的FaaS方案,那么可能会做一个大而全的系统将这些小功能融合在一起,此时造成的问题就是修改代码的困难,造成很多的麻烦,也不利于技术革新。FaaS的好处在于可以将函数部署到边缘,比如CDN边缘或者Edge端。在通信部分,FaaS主要是Message+EventDriven的,而目前很多的通信协议会用到gRPC,因为FaaS本身要求响应速度比较快,因此对于通信协议具有一定的要求。gRPC的接口相对比较底层一些,在此之上还做成了ReactivegRPC能够更方便大家通过Reactive来操纵gRPC,但是在写代码的时候可能并不知道是底层是基于gRPC的。在技术趋势里面还有一个叫做AsyncAPI,其实在大家工作中,很多框架都支持从代码直接生成OpenAPI,比如SpringDoc等。
目前还有一个技术趋势是FaaSWebAssembly,其也是和FaaS结合的。众所周知,如果FaaS不是高频使用的话,会出现容器拉起时出现一定等待时间的问题,但是在某些场景下,对于等待时间存在比较严格的限制。那么此时如果使用传统容器的解决方案往往难以实现,而WebAssembly这种技术解决方案就能够充当函数的钩子,能够满足上述需求。同时,由于WebAssembly也是W3C新推出的规范,和之前的HTML、CSS、JS网页三剑客结合成为了网页四剑客。此外,在FaaS部分,还有最近推出的Deno可以很好地支持WebAssembly机制。而因为很多开发同学会关心Rust这一编程语言,而Deno对于Rust具有良好的支持,因此很多人会选择使用Deno作为FaaS的Runtime来支持FaaS的运行。
代码智能
目前还有一个技术趋势就是代码智能,通过前面所提到的技术架构,能够满足企业的架构选型,那么接下来就肯定会涉及到代码。对于代码而言,主要有两点,一个是代码生成,另一点是手写代码。代码生成主要是脚手架,能够快速将代码的框架生成,而不需要自己写配置项等,目前这项技术已经非常成熟了。这部分的工具主要有IntelliCode、Codota、Kite以及各种CloudIDE等。在写代码的时候会遇到的问题就是技术往往变化很快,每间隔4、5个月就会推出新的框架或者技术,而因为开发者关注的是开发效率,所以即使有CodeAI能力,但是在某种程度上还是会依赖CloudIDE特性。因为CloudIDE在跟进某些技术上面的速度比较快,它往往能够很快知道客户新的需求是什么。
CodeGeneration+IDE
目前有很多的代码生成工具,帮助大家解决框架生成问题。比如Scaffold,start.spring.io是可扩展的代码框架生成器。CodeGeneration是Java自带的Annotation机制,这一点可能大多数同学没有关注,但是在很多的场景下也会使用到,其生成的代码能够做到性能更高。此外,目前很多的IDE,比如JetBrains+VSCode都有集成的工具,帮助开发者快速生成代码框架结构。
6月12-13日,阿里巴巴研发效能峰会,由雷卷出品的架构设计与代码智能专场,将会有更多DDD、Reactive、ServiceMesh、代码智能相关的精彩分享。
不仅如此,本次峰会是阿里内部研发效能峰会首度对外,7大论坛、35个议题、39位阿里内外部的技术大咖、1300分钟的干货分享,将覆盖云原生、架构设计、代码智能、数字化转型等众多热门技术话题。