微服务基本特性
如何去定义微服务架构呢?首先从MartinFowler微服务这篇博客中就能看出一些端倪。MartinFowler将微服务的特性总结为9条。
- “组件化”与“多服务”
- 围绕“业务功能”组织团队
- “做产品”而不是“做项目”
- “智能端点”与“傻瓜管道”
- “去中心化”地治理技术
- “去中心化”地管理数据
- “基础设施”自动化
- “容错”设计
- “演进式”设计
简单来解释下每个特性所表达的含义, 详细可阅读原文。
“组件化”与“多服务”主要是对于组件以及服务做了定义,简单可理解为组件例如一个libraries,能被链接到一段程序。服务的定义为需要通过web请求或远程调用来进行通信。
围绕“业务功能”组织团队,想要表现的特性源于公司在做大型系统的架构时候往往会聚焦在技术层面上,比如有专门的网关开发、业务开发、数据库运维等等(本司也是)。但是根据康威定律的指导原则
任何设计(广义上的)系统的组织,都会产生这样一个设计,即该设计的结构与该组织的沟通结构相一致。——梅尔文•康威(Melvyn Conway), 1967年
微服务使用不同的方法来分解系统,即根据业务功能(business capability)来将系统分解为若干服务。这些服务针对该业务领域提供多层次、广泛的软件实现,包括用户界面、持久性存储以及任何对外的协作性操作。因此,团队是跨职能的。
“做产品”而不是“做项目”主要是源自亚马逊的“谁构建,谁运行”的理念,开发工作也可以遵循上述“产品”理念。
“智能端点”与“傻瓜管道”,主要是体现微服务使用一些简单的REST风格的协议,而不使用服务的协议编排通信。
“去中心化”地治理技术和“去中心化”地管理数据 顾名思义想要表明微服务的特性是服务是单独存在,单独建模的。对于该特性在业务落地建模时候,常常使用DDD去实现,关于DDD的学习,我非常推荐徐昊老师的《如何落地业务建模》课程。
“基础设施”自动化的想要表达的理念也非常明显,需要构建自动化的CI/CD pipeline,常常需要我们建立完善的DevOps体系。
- “容错”设计和 “演进式”设计 主要是表明微服务的设计要需要有技术手段来保证当其中一个服务故障时候,通过如重试、降级等技术实现容错,另外整个业务的迭代在范围内要可控变化,实现演进。
微服务定义架构
本文将限定于定义架构,而非技术实现,也就是说本文内容是讲解一种定义问题的方法,而非解决问题的方法。从微服务落地的现状来看,往往在对架构的定义和业务的分解上就出现了偏差,在之后的技术实现上,用上了分布式技术的屠龙之术,如链路追踪、限流、熔断等,反倒导致了整个开发流程和维护流程熵值急剧升高。
在上节简单介绍了微服务的基本特性,从以上的特性我们能看出来,我们要定义一个系统的微服务架构,关键有两点,即
- 定义微服务的方式围绕业务概念而非技术概念
- 服务的业务拆分有详细的限界来保证变化可控
这里我们需要引入DDD来解决这两个问题。通过子域进行分解;通过每个领域单独的领域模型来消除依赖项。也就是上帝类。通常来讲DDD的建模没有一个标准化的流程可以遵循,我们只能介绍一个大概的方法。以下我们将通过三个步骤,领域建模 -> 服务拆分 -> 定义服务API 来定义一个微服务项目的架构流程。
领域建模
进行领域建模需要三个步骤:
- 确定领域事件
- 定义聚合
- 定义限界上下文
这里我们简单用一段用户故事来做整个流程串联
我打算做一个外卖平台叫“饿了团”,主要的功能有,用户从我的平台下单,然后我平台接单之后,分配给餐馆商家,商家接单进行制作,然后再由平台分配骑手去进行配送给用户。骑手的位置会实时更新,用户可用通过平台查询到骑手的位置。
确定领域事件
在项目开始的起点,我们先需要根据用户故事和用户场景来识别和定义项目的基本操作。
定义系统的操作主要是根据用户故事中的动词,我们也可以用事件风暴(Event Storming)来定义领域驱动模型。
从上述的简单用户故事中,我们能得到收敛出一些领域事件:用户、商家、订单、外卖配送、骑手位置。
好了, 同时我们也能总结出一些系统操作:下单、接单、更新骑手的位置、骑手已取餐、骑手正在配送、骑手配送完成。
定义聚合
接下来我们就需要根据这些领域事件找出聚合根,然后为每个聚合根关联这些重要的系统操作。
确定聚合根,具体来讲是属于业务的一种sence,在复杂的业务场景中有不同的定义方式,这里我们给出一种聚合根的定义,即把 商家、用户、骑手作为聚合根。
聚合根 | 系统操作 | 依赖的实体 |
---|---|---|
用户 | 创建订单、查询订单 | 订单 |
商家 | 接受订单、准备订单、查询订单 | 订单 |
骑手 | 更新位置、取餐、配送、接受配送订单 | 外卖配送 、位置、订单 |
我们可以看到,创建订单操作被聚合用户子域,接受订单被聚合到了商家子域,但是考虑到订单的查询、以及订单的状态更新记录等操作,如果聚合到上述的三个聚合根中明显是不恰当的,所以这里我们增加订单子域来承担订单的查询与状态更新这些操作。
定义限界上下文
可以看到,我们把订单所承担的功能都拆分为“多个”订单实体,放在不同的子域内。所以这里的限界上下文对应每个子域,通常而言,限界上下文对应为一个或者一组服务。一个子域对应为每一个服务。
服务拆分
对于服务的拆分我们很难给出标准性的答案,这里可以给出一些指导原则。来源于Bob Martin的《面向对象设计的原则》 中的其中两项。其余九个原则,在设计类和包时非常有用。
指导原则
- 单一职责原则
改变一个类只改有一个理由。
我们设计微服务架构的时候也应该遵循SRP原则,设计小的、内聚的、仅包含一个职责的服务。
- 闭包原则
在包中包含的所有类应该是对同类的变化的一个集合,也就是说,对包做出的修改,需要调整的类都应该在这个包内。
这个原则的目标是,当业务规则发生变化时候,开发者只需要对一个交付包做修改,而不是大规模的修改。这样可以极大的改善应用程序的可维护性。
根据我们之上建模的例子,服务很容易拆分为四项
拆分服务 | 对应子域 |
---|---|
Consumer Service | 用户子域 |
Order Service | 订单子域 |
Restaurant Service | 商家子域 |
Delivery Service | 送餐子域 |
定义服务API
在这一步需要定义服务协作所需要的API,我们需要考虑到服务的前置条件和后置条件是否成立,比如
Consumer Service中我们需要验证用户的信息,确认其是否支付即获取付款信息。Restaurant Service中,需要验证送货地址和时间是否在餐厅的服务区域内。所以我们部分API的定义如下所示:
服务 | API |
---|---|
Consumer Service | verifyConsumerDetails() |
Order Service | findOrderDetailByConsumerId() |
Restaurant Service | verifyOrderDetails()、acceptOrder()、noteOrderReadyForPickup() |
Delivery Service | scheduleDelivery()、 notifyCourier()、updateLocation() |
这里只列出来很少一部分API的设计,更多的设计需要在真正的实践中探索。
总结
定义微服务架构的整体内容基本流程到这就介绍完毕了,我通过举了建立一个外卖平台的示例,从领域建模 -> 服务拆分 -> 定义服务API来说明了微服务架构将如何被定义,当然我的示例中只是包含了非常有限的场景,如果要扩展开,其中可能还需要定义的有消费者的账户、餐厅的优惠券、餐馆的关系、举报投诉催单等等更加服务的业务关系。可以看到,这个过程是需要很强的业务sence,勾勒出的架构也是非常抽象的。在微服务拆分阶段,除了一些指导原则之外,我们还会遇到很多难点。比如网络延迟、可用性、数据一致性等各类问题,这时候就需要具体的分布式技术来解决这些问题。在后续的文章的,我将继续展开。
最后,关于微服务架构,我还想说的是,架构决定了软件的各种非功能性因素,比如微服务架构提高了可维护性、可测试性、可部署性和可扩展性。但同样,在一些场景的性能方面有所妥协。同时微服务架构也增加了很多复杂性,比如在可观测性、链路追踪、安全性等方面我们需要更多的第三方组件去协助解决。