谈谈微服务架构中的领域驱动设计


【编者的话】本文是关于领域驱动设计与微服务架构结合的心得体会,通过对整个体系的思考和落地相关各个方面做的梳理,为大家提供了实践参考,从而帮助大家使用这套组合拳来应对复杂的大型企业软件开发。

微服务架构的领域驱动设计实践

01.png

图1:微服务之旅

微服务是一种应用架构风格,源于领域驱动设计架构和开发运维一体化,它具有明确的限界上下文,接口和依赖。每个微服务都是一个松耦合的遵循单一职责原则的服务,每个组件都是完整而小规模的应用,它关注实现某个单一业务。业务对于最终用户才是有意义的——而不是技术或者基础设施的微服务。每个都有清晰的接口和依赖(如对其它微服务和外部资源)所以微服务可以相对独立地运行,相应的团队可以做到独立开发。

微服务让开发者(不仅是计算机)更高效。因为它使开发者能在小型团队中能完成有意义的工作(例如:开发应用的价值功能)。小型团队让开发者(一般来说对所有人)更有生产力,因为他们开更少的会(以及沟通与协作的其他方面),用更多的时间用来开发程序。最终用户使用的是程序员开发的软件,而不是开发者们参加的会议、画的架构图或者他们发给经理的状态报告。越多时间花在编程,就对用户创造越多价值。(否则,如果开发者写的代码不能为用户带来价值,那就错了!这个团队就是无效的,或者他们需要思考一下职业规划!)

在探究微服务设计模式之前,首先,我们要分析和理解业务领域(业务能力概念);当下业务环境已经极其复杂,竞争使得对错误的容忍度很小,一点错误的决策就会导致灾难性的后果。减轻这个风险很关键,所以推荐采用一种能应对复杂领域的方法;领域驱动设计是一套关于解决复杂领域模型的软件开发方法;它的思路是围绕着业务模型来连接和实现业务核心概念。在领域专家与开发者之间的通用术语是:领域逻辑,子领域,限界上下文,上下文映射,领域模型和通用语言,用它们来协作完善应用模型和解决任何领域相关的主要矛盾。

微服务也不是没有缺点;缺少DevOps和自动化会导致你的微服务进程难以开展,带来的痛苦可能多于好处,这是后续博客再谈论的话题;目前我们先聚焦在微服务架构的收益,它为组织带来不仅业务能力和模块化的服务,还有:
  • 伸缩性
  • 可用性
  • 弹性
  • 独立,自治性
  • 去中心化治理
  • 故障隔离
  • 自动治理
  • 通过DevOps实现持续交付


用合适的方法构建微服务,更多是在讨论软件架构模式,提供一套结构化的方法帮助复杂的领域软件的设计决策。它关注如何让某个特殊业务领域的所有知识获得更好的理解。重要的一点是必须从解决业务理解及真实世界的建模出发。领域驱动设计是一个基于战略价值的框架,它关心将业务领域概念映射到软件概念。

微服务实现可以受益于以下的规范方法:
  • 领域分析
  • 定义定界上下文
  • 定义实体,聚合和服务
  • 识别微服务


领域分析

一个领域是指真实世界的某方面的解决方案(如:汽车,银行,抵押,贷款等), 领域是需要开发者实现的系统的需求和验收标准;领域可能以很高层次的形式隐含业务部分区别。为了微服务的成功实现,我们需要一个清晰的关注点隔离,应用一些边界划分方法,就像领域驱动设计建议的:
  • 团队通常工作于某一个业务领域
  • 此业务领域是这个团队的核心工作
  • 领域粒度取决于团队在组织中的位置


领域模型概念采集和处理,需要在这方面有深度的理解,所以最好的实现方式是采用“事件风暴”;事件风暴是一个基于工作坊的快速弄清领域中内容的方法。这个流程始于一个领域事件上下文,而后将事件视为一个模型里的基础元素。

子领域

一个领域可以被分解为子领域,每个为一个领域的特定一方面。典型的代表一些与用户有通用语言的组织结构。举例来讲,汽车业务可以分解成物流,研发,贸易,生产和市场;而研发领域自身又可以分解为设计,CAD(电脑辅助设计),测试,辅助系统,娱乐信息,生产计划,和引擎研发,以上是子领域与领域分解的示例。
  • 每个领域可以由子领域构成
  • 映射应用到领域与子领域是一个典型的企业架构方法
  • 每个子领域可以包含更细分的一级子领域
  • 典型的黑白盒建模
  • 各个子领域之间可以通信


02.png

图2:领域模型设计

领域模型

领域模型是一个项目覆盖的业务区域;它有自己的术语,通用语言,需求和要解决的问题;它是这个小世界里的一个具体领域。这个领域可以是汽车,银行,抵押,贷款,储蓄,信用卡,小额贷款,内容管理,以及我们的新项目。一个领域有它的自然边界,不能包含所有知识。

限界上下文定义

在决定好领域的分解方案后,一个首要任务就是识别限界上下文。当你在明确定义限界上下文时,你经常会发现是否有模型的元素要膨胀到多个上下文。限界上下文是一种适用于领域模型的概念性的边界。整个的业务模型太大也太复杂,而难以理解也使得维护一个企业的完整的统一的模型变得不可能。我们需要标记模型间的边界和关系,那么就要准备开展战略设计,使用:
  • 子领域
  • 限界上下文
  • 领域模型
  • 通用语言
  • 上下文映射


限界上下文是一种战略设计,有着清晰的边界,里面包含已存在的领域。在这个边界内,通用语言的所有术语和习语都有着特定的含义,且模型能精确反映出这些含义。这个边界也使不同的服务和接口的交互有了正式有效的表达。

通用语言

通用语言是领域驱动设计的一个术语,是在开发者与用户之间建立一个通用的,严谨的语言的实践。这个语言应该基于软件开发用到的领域模型——因此这就需要严谨,因为软件不能处理含糊的东西。

限界上下文实现

限界上下文是一个清晰的边界围绕着领域模型,它确定领域的各个部分用以建模。领域模型主要封装通用语言和它的领域模型,但是它还包含建模存在着什么样的交互。限界上下文是一个软件构件,它被视为界定一个微服务功能集的神器。
  • 一个团队一个限界上下文
  • 每个限界上下文独立代码库
  • 领域模型 + 数据库表结构 + 用户界面 + 服务API
  • 细分大的领域成为小的上下文
  • 每个上下文可以有自己的通用语言和它自己的领域模型
  • 限界上下文可能共享领域的一些方面


上下文映射

译者注:可能译为“上下文地图”更容易理解,很多资料翻译为映射,理解起来比较抽象。

企业应用会有多个模型,并且每个模型有它自己的限界上下文。一种明智的做法是用上下文作为划分组织结构的依据。因为一个团队里的人沟通更容易,他们可以更好的集成模型和实现。当每个团队都在自己的模型上工作时,每个人对全貌有个认知都是有益的。

定义实体,聚合和服务

实体,值对象,聚合,领域服务与工厂和仓库是领域驱动设计的战术方法的基本要素,如下图:
03.jpg

图3:领域驱动设计战术设计

Eric Evans的书《领域驱动设计: 软件核心复杂性应对之道》描述了这些细节;我只在这里做个入门。

实体

设计实体这个领域对象,使它关注领域的特性,我们必须正确的确定它的特性以及我们如何获取它。它是:
  • 一个本质特性的名词
  • 可变的——随时间可变状态
  • 可与其它实体和值对象关联
  • 不能共享
  • 实体有历史记录可以被追踪


值对象

我们设计值对象这个领域概念,是当关注领域模型的某个元素的属性且它不是此模型的特性时,我们应该尽量用值对象建模而不是实体。确定一个概念是否是一个值,应该确定它是否具有以下特征:
  • 它测量量化或者描述领域的一部分
  • 它可以被设置为不可变——状态不可变
  • 它把一个概念建模,将相关属性组成一个整体的单元
  • 当测量值或者描述变更,它可被整体替换
  • 它可以与其他同类的值作比较
  • 提供给它的合作者以无害的行为
  • 没有唯一标识的一个名词
  • 不可变的——状态不可变
  • 可与其它实体关联
  • 可以共享
  • 没有生命周期,没有历史与之关联
  • 在数据库中不应该有它的表


聚合

聚合是一组关联的对象群,被当成一个单元根实体(也可看成事务)。聚合有清晰的边界(仅关心聚合内的对象的完整性和职责 ,它不关心外部对象)。聚合保护内部对象不受外部世界改变(外部对象只可以被聚合访问,不能改变聚合内对象的状态)。聚合的职责是保护它的实体和值对象的完整性。
  • 每个聚合有一个根实体
  • 关联的实体被根引用,但其他实体不行
  • 所有操作都是根完成


4个使聚合设计更简明的规则:

1. 在一致的边界内建模客观不变性

  • 不变性是业务规则,必须保持一致
  • 好的聚合可以做任何修改,而不变性必须一致
  • 应用的一个恰当的事务,只修改一个聚合
  • 适当的设计聚合:按业务要求可以任何方式修改, 而不可变性要在一个事务内保持完全一致。


2. 设计小的聚合

  • 如果设计了大聚合,可能会面对伸缩性能问题
  • 限制聚合到根实体,且有少量属性和/或值对象
  • 检查是否定义的不变性是真实的


3. 用标识引用其它聚合

  • 我们要抵御在一个事务里修改2个聚合的诱惑


4. 在边界外使用最终一致性

  • 与业务核实是否最终一致性可以接受。通常是可以的!任何要横跨聚合的规则,都不能期望它每时每刻保持最新。


服务

领域服务是充实了领域的专有任务的一系列无状态操作, 它执行有意义的业务处理,且能转换一个领域对象从一种组合到另一种,通常:
  • 服务是你应用的行为
  • 服务导致实体的状态变化
  • 有的操作的概念不能放在任何领域对象里
  • 无状态
  • 接口定义为领域模型之外的元素
  • 服务可以是任何层的一部分(应用,领域,基础设施)
  • 仍然被通用语言和领域专家的命名原则驱动


工厂

工厂模式有职责收集需要的信息来构建领域对象成为聚合根;工厂模式的最佳实践:
  • 创建实体和值对象
  • 只当实体的创建很复杂的时候使用


仓库

仓库封装了存储在数据库中的一个对象集合。
  • 实体集合
  • 关心实体的更新
  • 关心获取已经保存的实体
  • 一个仓库一个聚合根
  • 存储层的实现可以是文件,存储或者内存数据库等


识别微服务

第4步(识别微服务)由微服务架构,微服务分层,后端,前端,异步通信和微服务交互组成,它们可以帮助开发微服务应用。

微服务架构

我们为微服务应用定义了一系列的限界上下文。然后我们进一步审视其中一个限界上下文,调整边界,并识别一系列的实体,聚合根和领域服务,这样我们就可以定义清架构,将应用结构化为一系列松耦合,协作良好的服务。每个服务实现了一些有限的,相关的功能集。 应用可以由很多服务组成,如订单管理服务,客户管理服务等等。

服务交互使用同步协议如HTTP/REST或者异步协议如AMQP。各个服务可以独立开发和部署。每个服务都有独立的数据库,互相之间是解耦的。服务间的数据一致性靠使用Saga模式维护。

服务是很细碎的,客户端应用通常需要与多个服务交互来汇聚所需的数据。为了让服务端的修改不影响客户端调用,我们可以使用API网关。API网关是一个隐藏所有微服务的抽象层,暴露给客户端来调用。到达API网关的请求将会被代理或路由到相应的后端服务。网关也可以帮助我们有效的监控服务的使用状况。

微服务是这个架构中的一个组件:
  • 每个都是一个微型的应用
  • 每个都关注一个任务,一个业务能力(单一职责原则:每个微服务只实现一个领域限界上下文内的业务职责)。从软件的角度说,系统需要被拆解成多个组件,每个组件就是一个微服务。为了实现更小的内存占用和快速启动,微服务必须得是轻量的。)
  • 每个都可以独立的部署和更新
  • 松耦合
  • 每个都有设计精良的接口:REST APIs


04.png

图4:微服务架构

微服务架构能提供除了灵活性和弹性以外更多的好处,如:
  • 微服务架构是非常有前景的方法,可以以敏捷的方法设计和构建高伸缩性的应用。服务本身也比较直截了当,关注在把某方面的业务做好,所以它会比较容易测试而保障高质量。
  • 每个服务可以用合适的技术栈来构建,允许混合持久化技术以及其它类似的技术。你不会受项目的其它部分技术选型的牵制。
  • 在这个架构下开发者们可以独立交付,这对持续交付很友好,允许频繁发布的同时保持系统的其它部分稳定。
  • 万一一个服务宕掉,它只会影响直接依赖它的模块(如果有的话)。其它模块仍然正常。
  • 外部配置:把配置托管到外部的配置服务器,可以为每个环境维护成层级结构
  • 一致性:服务可以按同一样式编码,遵从统一的编码规范和命名规范……
  • 有弹性:服务应该处理各方的异常,如技术原因(连接和运行时),和业务原因(非法输入),而不能崩溃。模式如断路器和头信息块可以帮助隔离和包容错误。
  • 良好的治理:微服务应该通过JMX API或HTTP API上报它的使用统计,被访问次数,平均响应时间等。
  • 版本控制:微服务可能需要为不同的客户端支持多个版本,直到所有的客户端都迁移到了高版本。应该依据支持的新特性和缺陷修复,有一个清晰的策略……


微服务架构分层

业务服务一般是领域模型和整合层的职责,这点仍然延续(也是SOA的模式)。就是说这些层不会横跨整个应用,它们只在服务内做好封装。(类似O/R mapping就会贯穿整个应用被广泛复用。微服务更重视独立性多于复用性。并且你可能是在用NoSQL数据库,所以你不会需要O/R mapping代码。但是事实上,你的业务可能需要与遗留企业数据库对接,如果它是基于SQL的,还是需要O/R mapping代码 )。

应用模型通常作为门面层:一站式为客户端收集所有业务服务,汇聚起来像一个为客户端定制的应用。但是现在你要为每个客户类型构建一个应用模型,而不是一个单一应用模型贯穿整个应用。

展示层发生了什么呢?它被移入了客户端(原本就该聚合在一起!)。客户端可以是移动APP,它可能是一个展示了应用模型中的一些数据的视图;或者是一个WEB应用。WEB应用通常会包含很多视图层的代码来渲染HTML,且需要在HTTP会话中维持视图状态。现代的技术是用HTML5和CSS3的,网页浏览器使用下载到本地的静态文件来渲染和存储会话状态。
05.png

图5:微服务架构分层

用户体验适配层(BFF)

每个适配层都是外部前端的一个后端,一个典型的GUI开发团队都有一对后端和前端开发;在团队里使用相同或者兼容的语言。通常,相对于一个团队编写后端,其他人实现客户端,取而代之的是让同一个团队负责一对前后端。这对前后端为彼此的实现而设计,所以沟通是发生在一个团队内,不是在团队间。不同的客户技术各不相同,所以让团队专攻某一方面。
06.jpg

图6:前端的后端

异步通信

异步通信能让微服务更健壮:
  • 当提供者执行时,请求者不必阻塞
  • 不同的请求者可以同时处理响应
  • 消息系统掌控所有动作和结果


考虑异步集成。同步REST通常更简单,所以一开始先这么做。然后考虑也可能由于一些自然的需求(长时间运行,后台运行)或者让集成更可靠和健壮而从策略上将一些集成点转换为异步的。

怎么说异步执行更可靠?同步时,整个周期——请求者,提供者,消息——必须在整个执行周期保持正常。而异步,执行过程被拆分成3 - 4部分;如果某一个失败,系统可能重试。请求者是无状态的,收到响应的实例甚至不必是发送请求的实例,这就支持了水平扩容带来高可靠。
07.png

图7:异步通信

微服务交互

不同的微服务可以以不同的语言实现(现在或未来),所以不要锁定在某个语言的集成技术(如,Java socket,甚至CORBA/IIOP)。时下的标准是REST和JSON/REST,推荐使用。异步集成,遵循开放云友好的标准,像AMQP(一个开放的标准事务消息协议,比采用商业消息中间件明显降低成本。在传输关键业务,或在组织间传输实时数据和安全地在虚拟云计算事务环境中传输的理想协议)或者Kafka。不要冲动的使用像XML或者串行化的解决方案。那会导致协议数量大爆炸,每个API提供者与消息者都可能有不同的协议。

其它异步协议的例子有:
  • Message Hub
  • MQ Light
  • RabbitMQ


轻量协议:
  • REST 如 JSON 或者 HTTP
  • 消息队列,如Kafka


目标是用这些方法完全解耦:
  • 只要有可能就用消息队列
  • 使用服务注册中心或者服务发现
  • 负载均衡
  • 断路器模式


我们可以混合同步和异步。通常请求/响应可以既有同步也有异步,一个单一的调用可以是同步的,否则是异步。
08.jpg

图8:微服务交互

总结

我们写这篇文章的目标是分享我们如何合并领域驱动设计与微服务架构的;DDD是围绕解决领域模型的复杂性的软件开发方法;这个方法反复推敲如何将实现与核心领域概念连接为业务模型。通用术语在业务/领域专家和开发团队之间有领域逻辑,子领域,限界上下文,上下文映射,领域模型,和通用语言用来协同和优化应用模型和解决领域相关问题。我们的方法是结构化理解业务领域使用领域分析,定义限界上下文,定义实体,聚合根和服务,最终识别出微服务。微服务能提供传统架构不具备的独特优势,它提供扩展性,可用性,可靠性,并且每个微服务是松耦合单一职责的。

原文链接: Implementing Domain-Driven Design for Microservice Architecture (翻译:赵健军)

译者介绍
赵健军,天津美腾科技开发经理。自2003年一直从事软件开发运维管理工作,在监控软件、国内外业务应用与云计算、物联网方面都有一线的奋斗经历,近几年致力于工矿业物联网智能化平台研发、团队能力提升,在垂直行业信息化方面有很多落地体会,喜欢结构化思考,对秩序井然、效率提升有浓厚兴趣。

0 个评论

要回复文章请先登录注册