业务架构设计方法及实践

253 阅读21分钟

一、架构概念

1. 概念定义

软件架构(software architecture)是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计。软件架构是一个系统的草图。软件体系结构是构建计算机软件实践的基础。

感觉有点太抽象了,我理解的架构是对业务功能进行整体框架设计搭建,形成可理解并实用的系统整体模型。在这里的整体架构设计是指的根据现有业务的需求进行总体分析,对不同业务功能进行抽象、拆分和归类,也就是划分不同的领域,在根据业务要求,对不同业务间交互方式进行设计,交互方式是指调用方式和调用层级等。这样我们就可以对系统有整体全局的认知。

二、架构目的及作用

1. 架构目的

减少熵增带来的混乱,架构模式对业务领域进行划分,使业务间相互隔离,减少业务变化对整体或其他业务产生影响,对业务内的功能进行层级封装,规定层级功能职责和层间交互方式,以使功能变化对调用方影响减少。

2. 熵增影响即变化的影响

变化又可以分为三部分,一部分是业务的变化,另一部分是技术方案的变化,最后是交互方式的变化。

2.1 业务的变化

这个比较容易理解,业务的变更引起的变化,新业务的支持等都会对现有功能或业务产生影响.

2.2 技术的变化

这里指的技术方案变化不是指架构设计变化,是指技术选型的变化,例如存储方案的变化,连接db中间件变化。

2.3 交互方式的变化

交互方式的变化是指接口变更或参数变化,这种变化对于上层是有影响的,所有调用上层都需要感知变化。如何让调用方不感知或少感知,就是架构设计时需要做的事情了。

3. 架构作用

简单讲就是为了使功能内聚,减少耦合(高内聚,低耦合)。是不是特别熟悉这句话。 大家在做架构时需要注意以下几点:1. 架构为业务而生。2. 架构聚焦于解耦。3. 架构意味着沟通与协作。4. 架构需要为时间买单,即架构不可能永远不变,随着公司战略、技术和业务的更新迭代,架构也会随之变化。

三、架构设计方法

1. 自上而下

自上而下设计是指参考标准方案,裁剪/适配形特定解决方案的过程。很多业务域已有标准模型或解决方案(例如财务域,电商,供应链等),这些业务域采用自上而下方法是一个不错的选择。设计师经验丰富/知识面广适合采用该方法;当然如果设计师经验不足,也可主动调研后实践。在大部分业务自上而下方法使用不多,不详细展开。

2. 自下而上

自上而下设计是指从具体的业务细节出发,分析归纳最后得出解决方案的过程。自下而上是我们在日常业务中经常使用的方法,本文重点介绍如何做自下而上的设计。

2.1 自下而上做归纳

分析问题空间,通过归纳分类减少复杂度。分为两个过程:场景梳理 和 场景分类 场景梳理:罗列所有问题细节。例如:流程建模先罗列所有子流程;领域建模先罗列所有域内概念 场景分类:划分类型,合并同类项。找准分类是对问题空间信息的提炼,有些分类很容易;有些分类的识别需要一个迭代过程: 先根据经验或直觉选主要的划分维度,识别类型 将场景归入对应分类 遇到难以归纳的场景,则评估是否需调整分类

2.2 抽象分析出设计

所谓方案是解决方案空间里的解法,设计过程就是就是从问题空间过渡到解决方案空间。这是过程中的难点,也是最困难的点。设计结果视目标而定(有时是领域模型;有时是流程框架等),使用的方法也需结合问题而定。

2.3 自上而下验场景

设计方案要放在设计场景里进行"推演"。推演的过程很重要:既要推演已知的场景,评估是否满足现有需求;也要对可能性高的未来场景进行推演,评估未来的适应性。

四、架构设计步骤

1. 业务分析

1.1 业务分析需要做什么

理清企业战略目标、业务目标以及业务流程,根据以上背景,进行整体业务以及业务流程梳理。

企业战略目标分析决定了业务的方向。业务目标分析决定了架构的方向。业务流程分析决定了模块及角色定义的方向。在下面会详细介绍。

1.2 业务分析需要怎么做

首先是企业战略目标分析,了解企业要做什么,未来的规划是什么。最终指导的是业务的方向。根据未来的规划进行架构弹性设计,也就是架构设计时需要考虑对未来业务的支撑能力。

然后是业务目标分析,是与业务人员进行沟通,了解业务需要达到的效果是什么,具体的量化指标是什么。对业务目标了解后,可以更好的把握业务方向,在进行业务流程分析时,围绕着目标进行流程分析以及架构规划。

最后是业务流程分析,是对各个业务功能、业务数据流转、业务状态流转以及用户角色等功能的确认讨论过程,并形成分析文档(流程图,架构功能图、用户角色图)。在这里肯定是缺少不了与专业业务人员一起多次进行学习、了解、分析和讨论。在这里暂时给出几种方法,具体方法会在之后文章中详细讨论。在ddd中通用语言的概念,就是为了更好的解决架构师与业务人员之间沟通,避免产生理解偏差。在讨论业务过程中可以采用头脑风暴,访谈等方式进行业务梳理,这样做的好处是可以从不同人的角度了解业务的细节,避免产生信息误差,因为每个人对业务的理解不同,可能理解不全或理解角度不同。

完成业务分析后,我们会梳理几个类型的文档:架构功能图、用户角色图、业务流程图。

架构功能图:对现有业务进行功能梳理形成的现有架构功能模块图

用户角色图:现有业务中都有哪些角色,每个角色可能使用的功能有哪些

业务流程图:业务数据和状态流转图

2. 业务模型抽象

抽象就是把相关联的事物进行总结简化归类,使之成为更高层次的概念,这个概念下可以包含被抽象的事物。抽象后可以更容易发现业务的本质,从而在架构设计时设计出更具有弹性的模型。

2.1 业务抽象流程

  1. 抽象业务实体:在业务梳理后,我们需要识别出所有业务实体,并把业务实体进行归类抽象。比如虚拟商品和实体商品就可以抽象为商品、标品和标签等
  2. 抽象业务流程:识别出业务中主流程是什么,类似流程进行合并,抽象出共用核心流程。比如商品上单流程、商品审核流程等
  3. 抽象业务规则:从业务梳理过程中识别出业务规则,对业务规则进行抽象。比如审核规则、计费规则等
  4. 识别实体关系:从抽象的实体中识别出实体之间的依赖关系。依赖关系识别会指导架构设计调用关系。比如商品依赖品类,商品实体是调用方,品类则是被调用方 2.2

3. 业务领域划分

业务领域划分是指把相关联的概念进行统一收敛,收敛的原则是保持领域内业务功能内闭环,基本不直接依赖外部业务数据,但不是不依赖于外部功能。例如订单是肯定需要依赖商品数据,但订单领域并不会把商品划分到订单领域,订单领域会通过接口方式调用商品数据。

3.1 业务领域划分方法

使用限界上下文(bounded-context)进行领域划分,什么是限界上下文?

3.1.1.限界上下文(bounded-context)

我理解的限界上下文是根据现实中概念制定边界的标准,必须符合现实业务中人类行为或行业标准。比如客户在进行订单交易时需要浏览商品->下单->支付。限界上下文就可以对交易流程进行行为限定:商品、订单、支付。在根据领域划分后规范具体领域内的行为动作,比如浏览商品、查看商品详情。订单查询。支付查询等。

3.1.2 从大概念到小概念

当我们已经把业务进行了领域划分后,需要对每个领域内进行小领域划分。比如划分了商品领域,在商品领域中又会存在商品基本领域、商品标签、商品标品、商品分类等。

3.2 业务领域划分原则

  1. 依据该模型与边界内其他模型或角色关系的紧密程度。比如是否当该模型变化时,其他模型也需要进行变化。该数据是否通常由当前上下文中的角色在当前活动范围内使用。
  2. 服务边界内的业务能力职责应单一,不是完成同一业务能力的模型不放在同一个上下文中。
  3. 划分的子域和服务需满足正交原则。领域名字代表的自然语言上下文保持互相独立。
  4. 读写分离的原则。例如报表需有单独报表子域。核心子域的划分更多基于来自业务价值的产生方,而非不产生价值的报表系统。
  5. 模型在很多业务操作中同时被修改和更新。
  6. 组织中业务部门的划分也是一种参考,一个业务部门的存在往往有其独特的业务价值。

3.3 业务边界冲突划分原则

在进行领域划分过程中,如果产生划分领域冲突时,我们应该通过以下原则对领域划分进行考虑,首先要考虑成本,减少成本提高公司运营效率是第一原则,质量问题是第二原则。最后就是系统性能是第三原则。

  1. 成本分析划分:这里的成本包含了沟通成本、运营成本,沟通成本和运营成本是指在业务领域划分后,是否会产生额外的沟通交流成本。如果存在则需要对领域划分后的团队职责重新进行分配,团队职责单一,做到领域逻辑尽可能不暴露到领域外。
  2. 质量问题划分:是指在业务领域划分后,是否会产生质量风险。比如数据同步风险、流量过大风险等
  3. 性能分析划分:领域划分后是否会存在性能问题。比如链路过长、调用接口过多、数据量级同步过大等。

4. 业务领域组合

业务领域组合是指规定业务领域的调用层级关系,也就是哪些领域应该放到上层是调用方,哪些领域应该是下层是被调用方。调用原则是单一调用原则。上层领域可以调用下层领域,但下层不能调用上层。这样的好处是规避掉循环依赖,减少系统依赖复杂度。

5. 业务领域交互定义

对业务领域进行划分后,我们就可以开始根据业务流程对领域交互进行定义,领域间的交互可以分为以下几种方式,可以根据业务特点自行选择。

  1. 接口调用(rpc/restful/http):同步调用、异步调用 采用直接调用接口方式时,尽可能的加入一个防腐层或接口层(项目),这样可以对接口进行统一管理,减少变化的影响范围
  2. mq通知:通过消息中间件进行数据或状态通知
  3. 数据同步:通过同步业务数据方式进行业务交互(采用此种交互方式主要是为性能考虑)

五、架构模型

下面介绍几种比较流行的架构模型。

1. 分层架构

image.png 分层架构大家应该都非常熟悉了,在此就不详细介绍,如果希望学习可以参考www.jianshu.com/p/29bfbbe16…zhuanlan.zhihu.com/p/40353581 文章。在这里介绍一下分层的原则和注意事项。

  • 分层架构,为什么不建议跳过分层,隔离层意味着在一个层的变化通常不会影响到其它层的组件
    1. 解决此问题可以采用依赖倒置,跨层调用的上层依赖接口编程,下层的业务变化上层是无感知的。当然如果是接口等交互变化是会有影响的。
  • 层级架构是可以进行跳层调用,但要遵循原则,比如对基础服务层调用
  • 分层架构模式的一个强大的功能是组件之间的关注点分离(separation of concerns)。 特定层中的组件仅处理与该层相关的逻辑
  • 分层依据:
    1. 分层的第一个依据是基于关注点为不同的调用目的划分层次
    2. 分层的第二个依据是面对变化
  • 分层原则
    1. 保证同一层的组件处于同一个抽象层次
    2. 抽象不应该依赖于细节,细节应该依赖于抽象,我们要依赖不变或稳定的元素(类、模块或层),采用依赖倒置原则,依赖于抽象。针对接口编程,而不是针对实现编程
    3. 分层架构有一个重要的原则:每层只能与位于其下方的层发生耦合

2. CQRS

cqrs简单讲就是进行读写分离。这样做的好处是读服务不会影响到写服务。

image.png

  • 与微服务的差别: 看起来就像是两个不同的微服务,那么我来说一说它们之间的一个细微区别。从物理实现层面来看,这两个数据模型可以作为两个独立的微服务,甚至可以用一个命令模型来支持多个查询模型。但是,微服务架构的一个关键构造是两个微服务通常代表两个独立的领域,而在 CQRS 中,无论运行时架构是怎样的,命令模型和查询模型都属于同一逻辑领域。如果查询模型对命令模型一无所知,就无法发挥作用。这里的耦合是预期的,不同于微服务之间的解耦行为。
  • 目标:根本目的是为了实现数据的多重表示,每一种表示都能够满足某些用户的需求。CQRS 可能会有多种查询模式,每个模式可能使用不同的物理实现。有些可能使用数据库,有些可能使用 Redis,等等
  • 优点:读写分离

3. Event Sourcing

mysql主从备份用到的binary log以及redis的aof持久化机制,都可以认为是“事件溯源”的实现。

  • 优点:

    • a.记录了数据完整变化过程,最详细的日志
    • b.可以将系统还原到任何一个时间点的状态
    • c.Domain Event非常有业务价值,BI分析事件能预测业务未来发展情况
    • d.可以有效解决线上的数据问题,线下重演一遍,就能知道哪里出问题
    • e.不再需要用到ORM,所以没有O/R阻抗失衡的问题,领域模型的设计可以更OO
    • f.将Command、Event串联起来,可以分析聚合根的整个变化过程,有助于排查分析问题
    • g.自动并发冲突检测、命令的幂等处理
  • 缺点:

    • a.事件数量巨大,如何存储
    • b.如果单个聚合根事件过多,则重演会造成性能问题
    • c.领域模型重构被制约,事件的修改必须兼容以前的结构
    • d.数据库订正不在有效
    • e.架构实践门槛高,没有成熟框架支撑基本无望
    • f.需要具备DDD领域建模的能力
    • g.事件驱动状态的修改,思维转变难
  • 日志系统应该叫溯源系统,如果只是简单的通过接入数据库操作记录,会丢失未被存储数据的动作。比如不进行打标数据,不进行补贴数据。很多时候是需要记录为什么不进行打标,具体不符合规则的原因是什么。

    通过对动作过程溯源记录(event sourcing)是有必要的,尤其是和金额相关(可以通过溯源进行恢复)。

4. 六边形架构

image.png

  • 六边形架构主要是在解决与外部交互方式
    • 输入、输出端口:即对外提供服务方式和调用其他服务方式
    • 领域模型:真实核心业务逻辑
  • 六边形架构的思想是将输入和输出都放在设计的边缘部分
  • 最终的思想还是针对接口编程,依赖于抽象
  • 六边形架构主要关注的是与领域层的交互,关注的是交互方式
  • 把领域层放在中心层,外部逻辑变化不会影响到业务逻辑变化

5. Clean-Architecture/洋葱架构

image.png

  • 内部具有抽象性、外部具体实现性,**内部的圆层定义的是规则,外部的圆层定义的是实现机制
  • 依赖规则:只允许外部圆层的代码依赖内部圆层的代码,反之则禁止
  • 采用依赖倒置方式进行外层调用内层

6. DDD

image.png

  • 概念
    • 实体、聚合、值对象
    • 聚合根
    • 服务
    • 边界上下文
    • 通用语言
    • 领域划分
  • 原则
    • ddd是解决复杂业务系统的架构设计方式,并不适用于所有业务系统,在进行架构设计时需要考虑清楚ddd是否适用于现有业务。如果业务较简单,并不建议使用ddd,因为ddd带来的沟通成本、学习成本、维护成本都会增加
    • ddd中的概念及设计原则可以进行借鉴,比如边界上下文、领域划分、防腐层、依赖倒置等,可以解决业务间依赖,使业务隔离

7. Vertical Slice Architecture

image.png 垂直切片架构,把差异业务进行垂直切分,使用各业务相互隔离,并使用统一控制器进行控制调度

8. Event Driven Architecture (EDA)

image.png 事件驱动架构是一种用于设计应用的软件架构和模型。对于事件驱动系统而言,事件的捕获、通信、处理和持久保留是解决方案的核心结构。这和传统的请求驱动模型有很大不同。

  • 优点:
    • 松散耦合 —服务不需要相互依赖。 这应用了不同的因素,例如传输协议,可用性(服务在线)和正在发送的数据。 消费者仍将需要知道如何解释事件或消息,因此在这两个服务之间仍应使用严格的合同,但是合同的实现细节无关紧要。
    • 可扩展性 —由于服务不再耦合,服务1的吞吐量不再需要满足服务2的吞吐量。这可以帮助降低成本,因为服务不再需要24/7全天候在线,并且可以利用无服务器计算的无限扩展。
    • 异步性 -由于服务不再依赖于同步返回的结果,因此可以使用即发即弃模型,这可以大大加快流程。 这可能会有一个缺点,下面将对此进行概述。
    • 时间点恢复 -如果事件由队列支持或维护某种历史记录,则可以重播事件,甚至可以及时返回并恢复状态。
  • 缺点:
    • 涉及异步编程(要考虑远程通信、失去响应等情况),开发相对复杂
    • 难以支持原子性操作,因为事件通过会涉及多个处理器,很难回滚
    • 分布式和异步特性导致这个架构较难测试
    • 过度设计流程 -有时从一个服务到另一个服务的简单调用就足够了。 如果流程使用事件驱动的体系结构,则通常需要更多的基础结构来支持它,这将增加成本(因为它将需要一个排队系统)
    • 不一致 -由于流程现在依赖于最终的一致性,因此通常不支持ACID(原子性,一致性,隔离性,持久性)事务,因此重复处理或乱序事件的处理会使服务代码更加复杂,并且难以测试和调试所有情况。
  • 事件驱动场景:
    • 组件的解耦
    • 执行异步任务
    • 跟踪状态的变化
  • 多系统间交互(通知模式)- 事件驱动实例 批量更新、插入、删除 + mq => 保证数据正确性 背景:下游系统需要感知上游数据变化。 方案:
    1. 方案1:采用业务方变化后进行数据聚合,通过mq方式通知下游,这个方案的问题在于引起业务变化可能是不同业务模块。这样业务方就需要把所有变化模块进行修改聚合,另对于下游业务来说可能需要根据某一业务维度进行mq消费(比如商家维度),当然上游业务方也需要根据这个维度进行分区发送,这样就能保证发送和消费顺序了。如果不去保证消费和发送顺序就可能导致数据问题,比如操作同一数据的修改消息、删除消息在发送时,由不同consumer消费,消费的先后顺序就可能不同,这样就可能造成数据错误问题。
    2. 方案2:采用监控binlog,根据binlog信息作为通知信息,下游接收到通知消息后,进行业务接口调用,查询当前的业务数据,使用当前的业务数据。这样mq消息只是通知作用,通过接口方式进行业务逻辑判断处理。这方案有一个问题就是当存在一对多数据(比如订单对应的履约订单)时,下游接收到的数据会有重复性,这个问题如何解决,如果业务系统(上游)进行数据操作时使用批量操作(即要么全部成功,要么全部失败),这样下游系统就可以根据业务操作唯一标识(traceid)进行过滤处理(redis进行过滤操作) 总结:架构模型是采用了EDA, 所以存在数据一致性问题,只能保证最终一致性。

六、架构设计实例

七、参考