业务复杂度

1,805 阅读6分钟

复杂度来自哪里

扩展性

因为我们大部分的系统都是从单一业务开始的。但是随着支持的业务越来越多,代码里面开始出现大量的if-else逻辑,这个时候代码开始有坏味道,没闻到的同学就这么继续往上堆,闻到的同学会重构一下,但因为系统没有统一的可扩展架构,重构的技法也各不相同,这种代码的不一致性也是一种理解上的复杂度。久而久之,系统就变得复杂难维护

面向过程

  • 一个是很多同学不了解SOLID原则,不懂设计模式,不会画UML图
  • 不会进行领域建模,DDD最大的好处是将业务语义显现化

分层不合理

  • 分层最大的好处就是分离关注点,让每一层只解决该层关注的问题,从而将复杂的问题简化,起到分而治之的作用。
  • 我们的原则是不可以没有分层,但是只分有必要的层。

随心所欲

  • 缺少规范和约束

复杂性对应之道

  • 扩展点设计
  • 面向对象
  • 领域建模
  • 分层设计
  • 设计规范

扩展点设计

  • 业务身份识别:多租户(TenantId) + 业务码(BizCode) + 扩展点(Extension)
  • 抽象扩展点机制:所有的扩展点(ExtensionPoint)必须通过接口申明,扩展实现(Extension)是通过Annotation的方式标注的,Extension里面使用BizCode和TenantId两个属性用来标识身份

面向对象

  • 单一职责原则(SRP)
  • 开闭原则(OCP)
  • 里氏替换原则(LSP)
  • 接口隔离原则(ISP)
  • 依赖倒置原则(DIP)

领域建模

  • 是一种知识丰富的设计(Knowledge Rich Design)从而增加代码的可理解性
  • 这里的领域核心不仅仅是业务里的“名词”,所有的业务活动和规则如同实体一样,都需要明确的表达出来。

好处

  • 面向对象
    • 封装:Account的相关操作都封装在Account Entity上,提高了内聚性和可重用性。
    • 多态:采用策略模式的OverdraftPolicy(多态的典型应用)提高了代码的可扩展性。
  • 业务语义显性化
    • 就是将隐式的业务逻辑从一推if-else里面抽取出来,用通用语言去命名、去写代码、去扩展,让其变成显示概念,比如“透支策略”这个重要的业务概念,按照事务脚本的写法,其含义完全淹没在代码逻辑中没有突显出来

领域事件

因为在现在的分布式环境下,没有一个业务系统是割裂的,而Messaging绝对是系统之间耦合度最低,最健壮,最容易扩展的一种通信机制。因此理论上它是分布式系统的必选项。

  • 命名:Domain Name + 动词的过去式 + Event 如 CustomerCreatedEvent
  • 内容: 丰富:Event的payload中尽量多多放data,这样consumer就可以自恰
  • Event Sourcing: 要有一个Event Store保存所有的Events

聚合根(Aggreagte)

把一组有相同生命周期、在业务上不可分隔的实体和值对象放在一起考虑,只有根实体可以对外暴露引用,也是一种内聚性的表现

领域服务

  • 有些领域中的动作,它们是一些动词,看上去却不属于任何对象。它们代表了领域中的一个重要的行为,所以不能忽略它们或者简单地把它们合并到某个实体或者值对象中。
  • 当这样的行为从领域中被识别出来时,最佳实践是将它声明成一个服务。
  • 这样的对象不再拥有内置的状态。它的作用仅仅是为领域提供相应的功能。Service往往是以一个活动来命名,而不是Entity来命名。例如开篇转账的例子,转账(transfer)

识别服务

  • 服务执行的操作代表了一个领域概念,这个领域概念无法自然地隶属于一个实体或者值对象。
  • 被执行的操作涉及到领域中的其他的对象。
  • 操作是无状态的。

服务划分

  • 操作是关于领域对象的,而且确实是与领域有关的、为领域的需要服务,那么它就应该属于领域层

边界上下文(Bounded Context)

  • 领域实体是有边界上下文的,比如Apple这个实体不同的上下文,表达的含义就完全不一样,在水果店它就是水果,在苹果专卖店它就是手机。
  • Bounded Context明确地限定了模型的应用范围,在Context中,要保证模型在逻辑上统一,而不用考虑它是不是适用于边界之外的情况。

上下文映射

  • Shared Kernal(共享内核)
  • Conformist(追随者)
  • Anti-Corruption(防腐层)

会员这个概念在ICBU网站是指网站上的Buyer,但是在CRM领域是指Customer,虽然很多的属性都是一样的,但是二者在不同的Context下其语义和概念是有差别的,我们需要用AC做一下转换

边界上下文和微服务 抛开以Docker为代表的底层容器化技术不看,微服务和我们之前的SOA么有本质区别。

模型重构

  • 模型统一:模型和业务不匹配时候,需要一个新的抽象
  • 模型演化:
    • 实体在演变:
      • 新功能新建接口,让其继承老接口,这样老的代码就可以保持不变了
      • 新功能,通过判断区分
    • 引入新的抽象:

业务可视化配置化

  • 业务逻辑流: 业务逻辑流是响应一次用户请求的业务处理过程,其本身就是业务逻辑,对其编排和可视化的意义并不是很大
  • 工作流:完成一项任务所需要不同节点的连接,节点主要分为自动节点和人工节点,其中每个人工节点都需要用户的参与,也就是响应一次用户的请求,比如审批流程中的经理审批节点,CRM销售过程的业务员的处理节点等等

分层设计

  • App层主要负责获取输入,组装context,做输入校验,发送消息给领域层做业务处理,监听确认消息,如果需要的话使用MetaQ进行消息通知;
  • Domain层主要是通过领域服务(Domain Service),领域对象(Domain Object)的交互,对上层提供业务逻辑的处理,然后调用下层Repository做持久化处理;
  • Infrastructure层主要包含Repository,Config和Common,
    • Repository负责数据的CRUD操作,这里我们借用了盒马的数据通道(Tunnel)的概念,通过Tunnel的抽象概念来屏蔽具体的数据来源,来源可以是MySQL,NoSql,Search,甚至是HSF等;
    • Config负责应用的配置;
    • Common是一写工具类;负责message通信的也应该放在这一层。

这里需要注意的是从其他系统获取的数据是有界上下文(Bounded Context)下的数据 为了弥合Bounded Context下的语义Gap

  • 用大领域(Big Domain)把两边的差异都合起来
  • 增加防腐层(Anticorruption Layer)做转换

规范设计

  • 放对位置
  • 贴好标签:命名规范
  • 方法名约定
  • 错误码约定
  • Domain Event约定
  • 测试约定

整体目标

  • 思想:高内聚,低耦合,可扩展,易理解
  • 架构:扩展点+元数据+CQRS+DDD