跬步 On Coding

领域驱动设计与微服务

DRF的起手式

  • CURD Boy的通常的工作模式

image-20201010162649717

  • 先设计Serializer还是先设计Model?

问题是什么

image-20201010162706372

  • 框架捆绑
    • 写框架无关的代码
  • 复杂的逻辑会陷入职责不清晰, 交叉依赖
    • 抽象一层

面向对象

SOLID原则

- - -
SRP The Single Responsibility Principle 单一责任原则
OCP The Open Closed Principle 开放封闭原则
LSP The Liskov Substitution Principle 里氏替换原则
DIP The Dependency Inversion Principle 依赖倒置原则
ISP The Interface Segregation Principle 接口分离原则

clean Architecture

  • 实体: 模型, 比如权限中心的一条策略就是一个实体, 它具有唯一的ID
    • 模型不是分散的, 与存储无关, 避免交叉依赖
  • 用例: 业务的使用规则, 实例在某种场景下的使用方式, 一段实体的使用逻辑
    • 与api无关, 只与业务逻辑相关
  • 对外暴露接口与适配器
    • 隐藏用例与实体, 只对外暴露接口
  • 框架, 驱动
    • 对接接口, 实现适配器接口

  • 从业务出发, 以业务逻辑为核心构建系统
  • 灵活的使用外部依赖, 扩展方便

六边形架构

img

  • 以业务为核心, 外部依赖全部做成适配器, 方便替换

领域驱动设计

为解决复杂的现实问题的一种设计模式, 将数据和行为封装在一起,并与现实世界中的业务对象相映射。各类具备明确的职责划分,将领域逻辑分散到领域对象中。

  • 业务架构——根据业务需求设计业务模块及其关系
  • 系统架构——设计系统和子系统的模块
  • 技术架构——决定采用的技术及框架

DDD的核心诉求就是将业务架构映射到系统架构上,在响应业务变化调整业务架构时,也随之变化系统架构。

什么是领域

在互联网兴起之前, 传统的软件行业, 做的软件都是在解决现实中已经存在的问题, 软件的意义在于提示现实问题处理的效率, 比如电子商务, 现实中的商务模式只怎样运转的, 电子商务就是怎样运转的, 软件系统的意义在于大大提升了商务模式的运转的效率, 所以要设计一个好的软件系统, 先要有足够的领域知识, 以现实中出现的问题为基础, 构建领域的模型

已订单模型为例, 同样是订单, 对于购买客户展示的订单与对商户展示的订单信息就不一样, 操作也不一样, 这就需要对模型进行域的划分, 分别分为客户域与商户域

image-20201010170636576

领域语言

定义术语, 识别出领域内的用例主谓宾, 主语: 实体, 谓语: 用例, 宾语: 值对象, 比如在权限中心, 管理员对用户授权了xx权限

领域模型 – Entry与ValueObject

apwp 0104

实体

当一个对象由其标识(而不是属性)区分时,这种对象称为实体(Entity)。

在权限中心, 用户, 管理员, 策略, 都有唯一的标志, 可以分门别类划分到各自域的实体

值对象

当一个对象用于对事务进行描述而没有唯一标识时,它被称作值对象(Value Object)。

权限中心中, 策略中的拓扑, 属性, 这些没有唯一标志的值, 就是值对象

Repository

数据存储的adapter, 以Entry为参数, 保存实体信息到存储引擎, 从数据库中加载数据为实体对象

apwp 0201

好处:

  • 简单的接口, 解耦了对存储的依赖
  • 方便进行单元测试, 通过Facke Repository
  • 开考虑如何存储数据前, 专注于解决业务问题, 使得模型更加贴合实际问题

坏处:

  • 增加了额外的代码逻辑, 提高的维护的成本
  • 有一些逻辑可能ORM框架就已经支持了

Service

服务层, 领域模型的使用用例, 一些重要的领域行为或操作,可以归类为领域服务。它既不是实体,也不是值对象的范畴。

apwp 0402

好处:

  1. 提供一个单一的地方放所以的用例
  2. 通过服务层来把领域模型隐藏在api后面, 方便对领域层做重构
  3. 把定制http相关的协议改变成了定制服务的协议, 接口只是服务层的派生
  4. 有了存储层后, 可以方便的使用Fake存储来做测试

坏处:

  1. 如果你的app是一个纯粹的web服务, MVC就可很好的处理用例了, 不需要增加多余复杂度
  2. 服务层是另外的一个抽象
  3. 服务层承载了太多的逻辑, 会导致领域层成为贫血模式
  4. 在采用服务层前, 需要理清控制面上的逻辑编排
  5. 把控制面的逻辑下沉到领域模型上, 可以得到胖模型, 瘦控制

Unit of Work

service层中直接对接了Repository, 处理了太多事务相关的逻辑, 通过UOW来封装事务的处理

apwp 0602

好处:

  1. 对于原子操作有一个更好的抽象, 通过上下文管理器直观的看到哪些逻辑组合在一起
  2. 对事务的开始与结束有了明确的定义
  3. 提供了实例化存储类的地方
  4. 帮助处理事件与消息总线

坏处:

  1. 可能ORM已经有类似的封装
  2. 看起来简单, 但是还要处理回滚, 多线程, 嵌条事务等情况

Aggregate

Aggregate(聚合)是一组相关对象的集合,作为一个整体被外界访问,聚合根(Aggregate Root)是这个聚合的根节点。

有了Service与UOA后, 往往会导致Service的逻辑复杂, 而领域模型上的逻辑非常简单, 甚至于没有相关的方法, 这就是所谓的贫血模式/失血模式, 这时候就需要一种新的抽象来承载领域模型上的相关逻辑, Aggregate

以权限中心为例, 每次操作的Policy并不是一个, 而是一批, 这时候就可以定义PolicyAggregate, 来把批量的Policy操作的方法封装在聚合之中

apwp 0701

好处:

  1. 聚会可以定义出哪些模型是可以暴露, 那些不暴露
  2. 显示的定义界限上下文, 有助于提高ORM性能
  3. 由聚合来负责状态的变更, 更容易控制内部数据, 提供统一的出入口

坏处:

  1. 对于新开发者不友好, 需要多理解一个概念
  2. 严格定义聚合的单一职责, 对于思维的考研很大
  3. 不同聚合之间数据的最终一致可能很麻烦

Event Drive

领域事件是对领域内发生的活动进行的建模。

apwp 0801

好处:

  1. 消息总线解耦了不同逻辑之间的耦合, 通过响应消息实现多种逻辑的处理
  2. 时间响应的处理与核心的领域逻辑得到隔离
  3. 领域时间实际上是现实问题的一种抽象, 可以跟领域语言有机的结合到一起

坏处:

  1. 消息总线隔离各种逻辑, 不方便梳理整体逻辑
  2. 要小心消息的循环依赖

完全的事件驱动

apwp 0902

好处:

  1. handler与service变成了一个, 架构简化
  2. 事件对于api与handle的参数传递提供统一的结构

坏处:

  1. 对于web服务来说, 消息总线不是一个可以感知的过程, 异步逻辑不知道什么时候完成
  2. 事件对象与模型对象存在大量相同的字段, 修改时也要同时修改

引入命令模式:

好处:

  1. 区分出命令与事件, 可以清晰的定义出哪些是必须完成的, 哪些是额外的逻辑
  2. 命令名称相对于事件更明确

坏处:

  1. 命令与事件语义上的差异可能很小, 要小心区分
  2. 明确的处理失败与异常, 使得影响变小, 逻辑更加难理解, 需要增加更多监控

事件驱动的微服务:

apwp 1101

好处:

  1. 避免依赖大泥球
  2. 分离服务: 更新与新增单个服务更简单

坏处:

  1. 整理流程的信息很难直观看出来
  2. 需要理解最终一致性
  3. 消息的可靠性需要权衡: 至少一次还是最多一次

CQRS

apwp 1201

命令于查询分离

查询 命令
行为 简单 负责业务逻辑
缓存 可缓存 不可缓存
一致性 可能延迟 事务一致

微服务

宏服务的问题

  1. 代码量变大: 模块之间没有严格的界限, 想要理解系统整体困难, 随着代码的增加复杂度增加, 变更的成本变高, 降低迭代速度

  2. 资源利用低效: 整体服务占用大量的内存, 不管其中模块是否被大量的使用, 占用所有的数据库连接, 不管有没有使用

  3. 带来可扩展的问题: 即使能实现横向的扩展, 但是开发的过程中由于团队的人员变多, 也会带来版本迭代上的冲突, 除非有很好的职责划分, 不同团队之间的沟通, 会带来效率的降低

  4. 部署困难: 每次部署都需要保证所有模块的正确性, 其中某个模块出问题, 会导致整个系统的崩溃

  5. 技术的限制: 在选定一种技术后, 整个系统的技术就被限制了, 比如Python, 想要更改会非常困难

  6. bug影响: 一个模块的bug可能会影响到整个系统, 一个模块的资源占用过多, 也会影响到整个系统

微服务

  1. 模块之间松耦合

  2. 独立部署

  3. 每个微服务互相通信

  4. 为了提供整体的服务, 需要把微服务暴露成一个整体, 微服务隐藏在整体后面

优点:

  1. 微服务之间使用统一的通信协议通信, http-json, 更加通用一些, rpc更加高效

  2. 更好的资源利用率, 每个服务都是可控的, 按需分配资源

  3. 每个微服务都很小, 代码量少更简单, 重构更方便, 可以选择不同的技术栈

  4. 某些服务可以隐藏在微服务后面, 不对外提供, 更加安全

  5. 由于系统的独立, 一个服务的崩溃不会导致整体的崩溃

  6. 每个服务都是独立维护, 提高团队的开发效率

缺点:

  1. 迁移到微服务很困难, 需要付出大量的开发成本, 初期会带来很多混乱

  2. 微服务的会带来组织架构上的调整, 而一般公司的组织架构是很难改变的, 就会导致职责的分不清

  3. 学习曲线陡峭, 开发人员需要学习相关的开发模式, 需要开发运维相关的培训

  4. 调试更加困难

  5. 划分微服务本身很困难, 需要警惕多个独立服务合并到一个服务中, 这样就偏离的迁移微服务的初衷

  6. 违法对于团队配合会带来困难, 不同的团队会站在自己的角度思考, 沟通协调需要付出更多的成本

  7. 在整体功能与每个微服务的功能之间需要做平衡, 没有很好的配合, 团队之间会出现信息孤岛, 重复造轮子. 应该共享知识, 共享经验

  8. 敏捷开发的同时, 需要各个团队落地的文档就有要求了, 可以减少沟通, 但是增加额外的工作量

  9. 如果有对其它服务的依赖, 会增加请求的响应时间

总结

没有银弹, 没有完美的技术, 只有合适的技术.