领域模型落地篇:微服务拆分与关联指南

原文标题:这些年背过的面试题——领域模型落地篇

原文作者:阿里云开发者

冷月清谈:

**拆分微服务**
  • 内高内聚:单一职责原则,每个微服务专注于自己的业务领域。
  • 间低耦合:开放封闭原则,微服务通过接口调用,实现业务领域分离。

关联微服务

  • 限界上下文:基于业务场景划分子域,每个子域都有独立的领域对象。
  • 领域事件通知:通过消息队列实现微服务间的领域事件通知,解耦各个子域。
  • 聚合和关联:通过贫血模型或充血模型设计业务领域层,实现微服务之间的聚合和关联。

微服务的落地

  • 需求变更独立性:微服务设计要确保需求变更可以由某个小团队独立完成,发挥微服务优势。
  • 数据隔离和接口复用:通过接口复用和数据隔离,降低微服务拆分变更的影响。
  • 本地调用和防腐层:使用本地调用和防腐层设计,隔离不同微服务接口变更的影响。
  • 数据库去中心化:根据数据量、读写模式等因素选择合适的数据库方案,实现数据库去中心化。

领域模型的意义

  • 理解真实世界:领域建模还原真实世界的业务场景,指导软件设计。
  • 小步快跑:每次需求变更都应基于领域建模完善设计,保持软件设计的良性循环。



怜星夜思:


1、如何理解微服务的高内聚和低耦合?
2、领域模型落地的关键是什么?
3、在微服务中,数据隔离和接口复用的目的是什么?

原文内容

阿里妹导读


本文是技术人面试系列领域模型落地篇,也是面试题系列的完结篇,感谢大家对本系列文章的支持~面试中关于领域模型落地都需要了解哪些内容?一文带你详细了解,欢迎收藏!

一、拆分微服务

微服务内高内聚,微服务间低耦合。
微服务内高内聚即单一职责原则
每个微服务中的代码变化都是同一类原因。因这类原因而需要变更的代码都在这个微服务中,与其他微服务无关,那么就可以将代码修改的范围缩小到这个微服务内。把这个微服务修改好了,独立修改、独立发布,该需求就实现了。这样,微服务的优势才能发挥出来。
微服务间低耦合开放封闭原则
就是说在微服务实现自身业务的过程中,如果需要执行的某些过程不是自己的职责,就应当将这些过程交给其他微服务去实现,你只需要对它的接口进行调用。这样,微服务之间的调用就实现了解耦。
领域建模就是将一个系统划分成了多个子域,每个子域都是一个独立的业务场景,每个子域的边界就是“限界上下文”。该业务场景会涉及许多领域对象,但分析建模始终需要围绕着业务场景的上下文进行。
领域事件通知机制最有效的方式就是通过消息队列,实现领域事件在微服务间的通知。
“核心通讯录”微服务只负责发送变更消息到消息队列,不管谁会接收并处理这些消息;
“门禁管理”微服务只负责接收照片变更消息,不管谁发送的这个消息。

二、关联微服务

  1. 按照限界上下文进行微服务的拆分,将领域模型划分到多个问题子域

  2. 基于充血模型与贫血模型设计各个微服务的业务领域层(Service、Entity、Value)

  3. 通过领域事件通知机制和微服务调用的推拉结合,将各个子域进行解耦关联

    • 核心:

  • 通讯录 | 短信 | 推送通知 | 支付 | 文件服务

  • 智慧通行

    解决物业多品牌、多系统应用造成的信息孤岛,数据混乱的问题

    • 人脸门禁 | 可视对讲 | 电梯梯控 | 停车系统 | 访客预约

  • 安全社区

    通过图像视频识别、传感数据采集,实现报警联动和风险预警

    • 视频监控 | 周界报警 | 高空抛物 | 跨域追踪

  • 全屋智能

    围绕业主需求,逐步引入社区医疗、社区养老、社区团购、社区家政等服务

    • 超级面板 | 无线门锁 | 烟感雾感

  • 增值服务

    实现跨品牌的产品体验,支持基于matrix引擎的智能生活场景裂变能力

    - 智能充电 | 云广播 | 出入提醒 | 定向投放
    

三、微服务的落地

通过合理的微服务设计,尽量让每次的需求变更都交给某个小团队独立完成,让需求变更落到某个微服务上进行变更。唯有这样,每次变更只需独立地修改这个微服务,独立打包、独立升级,新需求独立实现,才能发挥微服务的优势。
  • 数据隔离:数据库中用户信息表的读写只有通讯录微服务。当其他微服务需要读写用户信息时,就不能直接读取用户信息表,而是通过 API 接口去调用通讯录微服务。
  • 接口复用:因此,当多个团队向你提需求时,必须要对这些接口进行规划,通过复用尽可能少的接口满足他们的需求;当有新的接口提出时,要尽量通过现有接口解决问题。
  • 向前兼容:当调用方需要接口变更时怎么办?变更现有接口应当尽可能向前兼容,即接口的名称与参数都不变,只是在内部增加新的功能。宁愿增加一个新的接口也最好不要去变更原有的接口。
  • 本地调用:访客申请微服务的本地,增加一个查询用户Servicefeign 接口。这样,访客申请Service就像本地调用一样调用查询用户Service,再通过 feign 接口实现远程调用。这种防腐层的设计,可以隔离当前微服务以外的其他微服务拆分变更导致的接口的失效的影响。

  • 数据库去中心化:

    • 微服务中通讯录服务健康码服务分别对应的用户库与权限库,它们的共同特点是数据量小但频繁读取,可以选用小型的 MySQL 数据库并在前面架设 Redis 来提高查询性能;

    • 微服务中访客通行生活缴费分别对应的通行记录库、订单库,其特点是数据量大并且高并发写,选用一个数据库显然扛不住这样的压力,因此可以选用了 TiDB 这样的 NewSQL 数据库进行分布式存储,将数据压力分散到多个数据节点中,从而解决 I/O 瓶颈;

    • 微服务中数据分析通讯录查询这样的查询分析业务,则选用 NoSQL 数据库大数据平台,通过读写分离将生产库上的数据同步过来进行分布式存储,然后宽表一系列的预处理,应对海量历史数据的决策分析与秒级查询。( NoSQL 为空的字段是不占用空间的,因此字段再多都不影响查询性能)

四、领域模型的意义

贫血模型、充血模型、策略模式、装饰者模式只是DDD实现的方式,而DDD的真谛是领域建模

做事不能仅凭一腔热血,一定要符合自然规律。其实软件的设计开发过程也是这样。对业务理解不深刻全局架构设计往往是过度设计,这时候应该抓主要流程,开始领域建模。

  • 接着,每次添加新功能的时候,一方面要满足当前的需求,另一方面业务相关的领域建模设计刚刚满足需求,从而使设计最简化、代码最少。
  • 这样的设计过程叫小步快跑。采用小步快跑的设计方法,一开始不用思考那么多问题,从简单问题开始逐步深入。领域模型就像小树一样一点儿一点儿成长,最后完成所有的功能。

保持软件设计不退化的关键在于每次需求变更的设计,只有保证每次需求变更时做出正确的设计,才能保证软件以一种良性循环的方式不断维护下去。

有没有一种方法,让我们在第十次变更、第二十次变更、第三十次变更时,依然能够找到正确的设计呢?有,那就是领域驱动设计。

那么在每次需求变更时,将变更还原到真实世界中,看看真实世界是什么样子的,根据真实世界进行变更。

五、战略建模 

六、相关名词

领域和子域(Domain/Subdomain)
上下文地图构建的领域中,对应模块,使用限界上下文划分领域,对应微服务
限界上下文(Bounded Context)
在一个领域/子域中,有概念上的领域边界,任何领域对象在该边界内部的有不依赖外部的确切含义。
领域对象
服务、实体与值对象是领域驱动设计的领域对象,可以通过贫血模型充血模型转换为程序设计
实体和值对象
通过一个唯一标识字段来区分真实世界中的每一个个体的领域对象,称为实体。真实世界中那些一成不变的、本质性的事物的领域对象,称为值对象。可变性是实体的特点,而不变性则是值对象的本质。
贫血模型与充血模型
POJO对象中只保存get/set方法,没有任何业务逻辑,这样的设计被称为贫血模型。
充血模型是封装和继承思想的体现,门禁设备实体中,包含特征值下发、广告下发、通行记录回调等方法,不同厂商的实体针对多态进行聚合,并通过工厂或仓库对外提供服务。在充血模型中, Service 只干一件非常简单的事,就是直接去调用对象中的工厂方法生成不同产品,其他的什么都不干。
聚合
聚合体现的是一种整体与部分的关系。正是因为有这样的关系,在操作整体的时候,整体就封装了对部分的操作。如何正确理解是否存在聚合的关系:就是当整体不存在时,部分就变得没有了意义。部分是整体的一个部分,与整体有相同的生命周期。
工厂
通过装配,创建领域对象,是领域对象生命周期的起点。譬如,系统要通过 ID 装载一个访客申请:
  1. 表单工厂分别调用表单信息DAO、表单明细 DAO 和用户DAO 去进行查询;
  2. 将得到的表单明细对象、用户对象进行装配,分别 set 到表单信息对象的表单明细与用户属性中;
  3. 最后,表单工厂将装配好的表单对象返回给表单仓库。
仓库
如果服务器是一个非常强大的服务器,那么我们不需要任何数据库。系统创建的所有领域对象都放在仓库中,当需要这些对象时,通过 ID 到仓库中去获取。
  • 当客户程序通过 ID 去获取某个领域对象时,仓库会通过这个 ID 先到缓存中进行查找:

  • 查找到了,则直接返回,不需要查询数据库;

  • 没有找到,则通知工厂,工厂调用 DAO 去数据库中查询,然后装配成领域对象返回给仓库。

  • 仓库在收到这个领域对象以后,在返回给客户程序的同时,将该对象放到缓存中。

【这些年背过的面试题】系列文章欢迎点击阅读原文查看合集!

学术一点的说法,高内聚就是微服务实现了单一职责原则,达到了模块化的设计目标。低耦合就是微服务之间的依赖关系弱,可以通过接口进行解耦,实现灵活组合和重用。

领域模型的落地是一个持续的过程,需要不断地演化和完善。随着业务变化和需求增加,领域模型也会随之调整,这是一个螺旋上升的过程。只有不断地完善和优化,才能让领域模型真正落地生根,为软件系统提供坚实的基础。

数据隔离是为了防止多个微服务同时访问同一个数据表或数据库,从而避免数据冲突和数据不一致。

高内聚就是每个微服务只负责一个特定的业务领域,比如通讯录微服务只负责管理用户信息。低耦合就是微服务之间通过接口调用,而不是直接依赖对方的代码,这样微服务可以独立开发和部署。

打个比方,数据隔离就像交通中的红绿灯,保证了微服务之间的数据访问是有序和安全的。而接口复用就像高速公路上的并行车道,让微服务之间的数据传输更加顺畅和高效。

俗话说得好,落地三分建,七分管。领域模型落在代码里之后,还要通过规范和约束来管理,确保代码始终符合领域模型,这样才能保证软件的正确性和可维护性。

接口复用是为了减少微服务之间重复的接口调用,降低开发和维护成本,同时提高系统的可扩展性和灵活性。

领域模型落地的关键是将业务场景抽象为领域对象和领域规则,通过代码实现这些对象和规则,从而构建一个贴合真实世界的软件系统。

用一个比喻来说,如果微服务是乐队中的乐器,高内聚就保证每种乐器只负责演奏自己的部分,低耦合就保证每种乐器可以单独练习和更换,不会影响其他乐器。