4+1 视图建模及架构设计工程实践
占春良:碧桂园服务技术专家,项目架构师,前阿里资深软件工程师,12年技术开发经验。
01 前言
架构设计建模的目的是通过统一的UML语言,完成业务的梳理,并对业务系统进行合理的组织(分层、分模块),以提高系统的可扩展性、可重用性、可移植性、易理解性和易测试性,从而达到一个高质量属性的软件系统。
架构设计的关键内容主要包含以下几个方面:
-
划分逻辑视图:确定领域;
-
选择架构风格:确定领域的架构风格,如单体还是分布式;
-
梳理领域之间依赖关系,并进行适当抽象(隔离依赖);
-
技术选型(可选)。
前置条件:在进行架构设计之前,我们需要先梳理清楚业务流程,并使用UML统一语言来呈现统一的组织价值目标。通过清晰的图标,我们可以更好地传达信息,一图胜千文。
02 架构建模简介
1、"4+1"视图组织建模
本文主要介绍“4+1”视图架构建模的过程,先看看“4+1”视图模型定义:
“4+1”视图是描述逻辑架构的重要工具,最早由Philippe Kruchten提出。目前“4+1”视图已成为主流的架构设计标准。本文中,我们将聚焦用例视图对业务流程的梳理(如何进行有效的组织业务建模)以及逻辑视图、开发视图的分层架构开发代码实践。
组织业务建模是关键的业务梳理环节,在“4+1”视图中,用例视图需要准确表达出组织的业务用例和系统用例。因此,在开始之前,我们先对用例图和UML的一些要点进行准备。
2、业务用例和系统用例的区分
2.1 业务用例
业务用例的定义是指业务执行者希望通过和所研究组织进行交互获得价值。在定义业务用例时,我们需要将视角放在组织外部和执行者身上,关注他们希望从组织中获得什么价值,而不是仅仅关注组织能提供什么(注:这个价值不是指组织能提供什么,而是指执行者想要什么)。
以小区物业的充电桩管理中心为例,其业务执行者主要是业主。尽管小区物业可以提供投诉等服务,但业主对充电桩管理中心最大的需求是充电服务,因此,“业主->充电”就是物业充电桩管理中心的业务用例,提供充电服务则是该组织的最大价值所在。
2.2 系统用例
系统用例的定义是指系统能够为执行者提供的、涉众可以接受的价值。为了解释这个概念,我们需要先明确:
1、系统
指封装了自身的数据和行为,并能独立对外提供服务的实体。一个系统可能包含多个子系统。
2、执行者
指在研究系统外,与该系统发生功能性交互的其他人或系统。
说明:
-
系统执行者一定是在系统外的,可以是人或者其他系统;
-
系统执行者必须与系统有交互;
-
系统执行者不一定是业务执行者。
3、涉众
主要指与要建设的业务系统相关的一切人和事,包括系统执行者在内。
以小区物业为例,当业主需要进行公区设备报修时,他们会通过微信告诉管家在小区什么地方、哪个设备有问题需要修理,然后管家通过企微对话框提交报修单。研究该场景的报修下单系统,我们可以得出:
系统执行者:管家。尽管业主是物业服务组织的业务执行者,但直接与报销下单系统进行交互的是管家,因此管家是该系统的系统执行者。
涉众:包括业主、管家、工程人员和项目经理等等,因为他们都是报修下单系统的利益关系者。
因此,从上述分析可以得出提交报修单和查看报修单满足系统用例的概念。以下是该系统用例图:
与业务用例不同,在研究系统用例时,我们需要将视角切换到系统本身,从系统的角度出发,考虑系统能够为执行者提供什么样的价值,并且该价值是涉众都可以接受的。
2.3 用例UML关系
系统用例图中关系主要有四种,分别是关联、包含、扩展、泛化,如下所示:
03 架构建模的工程实践
业务需求背景:为满足公司对小区物业电梯设备的同一线上管理需求,包括设备台账、维修管理以及设备工单执行作业标准等业务流程,我们计划进行电梯监管的系统建设。
1、用例视图:业务用例、系统用例
从需求背景中,我们可以看到电梯设备需要进行大量的信息的管理,包括维护、修理、设备记录以及统一的外部供应商如何管理维修电梯的标准。为了保障电梯的安全使用,我们需要将视角放在组织外部,关注执行者(安全监管员)的需求和价值。
通过分析,我们可以看出该系统的核心价值是确保电梯能够安全使用,防止出现重大故障隐患和安全事故。基于此,我们可以进行业务用例和系统用例的梳理:
2、流程视图
我们对电梯监管的每个系统用例(业务的静态)进行了拆解,并分析了每个场景的业务时序图(业务的动态)。在设计系统运行时,我们重点关注了实际业务流程中的系统交互。通过直观呈现完成系统用例的动态交互过程,我们可以清楚地看到涉及的用户角色和外部系统。以下是电梯工单维保的业务时序图示例:
3、逻辑视图
逻辑视图是面向系统逻辑分析和设计的,用于描述系统逻辑结构的视图。在电梯监管中系统中,我们采用领域模块为核心,基于六边形分层架构思想的项目模块分析模型。其中,domain-context领域模块可以拆分核心域、支撑域和通用域模块。这些模块的划分将在后续的工程编码环节中得到体现:
这里做一下补充:我们经常说的DDD(领域驱动设计)会聚焦在领域建模上,因为领域建模是领域驱动设计的核心。常见的领域建模方法包括事件风暴建模和四色建模法。
4、部署视图
部署视图主要面向系统部署,描述系统的交付、安装和部署的过程。它解决了系统安装部署的问题,展示了系统的交付、安装和部署关系。在我们系统中,所有的实现都在阿里云k8s进行Pod容器部。这里不做展开。
5、编码的环节-系统工程应用
在工程实践中,我们科研架构委员会采用了六边形架构。该架构抽象了核心的领域层,并使用适配器模式解耦对外的层次依赖。
基本思想:保证核心业务逻辑的稳定,领域层应作为最纯粹、最少对外依赖的层次,只包含业务知识和业务规则,不过多关心技术细节的实现(如数据持久化存储、第三方接口调用,推送消息等)。
根据我们以上“4+1”业务建模,可以进行子域的划分。根据子域对于业务重要性的不同,可以分为核心域、支撑域和通用域。在适配器模块,核心域是以电梯为主体的领域。
核心域:电梯域
支撑域:工单域
通用域:用户域
5.1 领域层
分层架构以领域层(domain)为核心来分层建设,领域层包含以下内容(工程结构模块说明):
点击查看代码
lift-supervision:服务,具体服务功能的代码标识
> > lift-supervision-adapter-mysql: mysql数据库的持久层模块
> > > lift-supervision-adapter-mysql-mapper: 持久层entity、mapper
> > lift-supervision-adapter-identity: 通用域,适配权限实现电梯通用模块
> > lift-supervision-adapter-order: 支撑域,适配工单实现电梯通用模块
> > lift-supervision-bootstrap: 服务启动模块
> > lift-supervision-domain-context: 电梯域核心业务模块
> > lift-supervision-schedulex: 适配器模块
> > lift-supervision-web: 服务功能模块
> > > lift-supervision-controller: resetful接口
5.2 核心域代码示例
实体和值对象的领域事件、观察者模式,监听电梯对象发布事件的,并执行相应的维保变更和人员计划业务逻辑:
点击查看代码
@Component
@Slf4j
public class ElevatorEventHandler {
@Autowired
private ElevatorRepository elevatorRepository;
@EventHandler(asynchronous=true, postAfterTransaction=true, isTransactionMessage= true,asyncConfig=@AsyncConfig(queueFullPolicy=QueueFullPolicy.DISCARD),transactionCheck= @TransactionCheck(checkTransactionStatusMethod = "checkElevatorCreateEvent"))
@Transactional(rollbackFor = Throwable.class)
public void handleElevatorCreateEvent(ElevatorCreateEvent elevatorCreateEvent) {
log.info("exec handleAccountCreateEvent,{}", elevatorCreateEvent);
// 电梯创建的相关维保计划变更处理
List<LiftBaseInfoEntity> liftBaseInfoEntities = selectData(param);
LiftMaintUnit liftMaintUnit = elevatorRepository.selectById(param.getMaintUnitId());
elevatorRepository.updateMaintUnit(liftBaseInfoEntities,liftMaintUnit);
LiftMaintUnit maintUnitQty = countQty(param.getMaintUnitId());
elevatorRepository.noticeToMainById(maintUnitQty);
elevatorRepository.sendMaintContract(liftBaseInfoEntities,liftMaintUnit);
}
// 事务状态检查
public boolean checkElevatorCreateEvent(ElevatorCreateEvent elevatorCreateEvent) {
return elevatorRepository.findByElevatorId(elevatorCreateEvent.getElevatorId()) != null;
}
}
5.3 仓储层代码示例
Repository适配dao实现数据持久化:
点击查看代码
@Repository
public class ElevatorRepository extends DaoAwareAggregateRepository<Elevator,Long> {
@Autowired
private ElevatorDao elevatorDao;
public ElevatorRepository() {
super(Elevator.class);
}
public Elevator findByElevatorId(String elevatorId) {
return fetchAllComponents(elevatorDao.findByElevatorId(elevatorId));
}
public List<Elevator> findByElevatorIds(List<String> elevatorIds) {
return fetchAllComponents(elevatorDao.findByElevatorIds(elevatorIds));
}
}
5.4 两个注意点
1、关注业务域的划分
业务域划分不清晰的系统往往更容易腐化,因为一旦业务域划分比较乱,分不清应该放在哪个域的模块中,再经过时间和人员迭代,系统会愈发的混乱。例如,在我们应用中,有一部分接口定义放在了通用域的服务中,但从业务域划分上来看,其并不够通用,因此将其放在通用域的服务是不合适的。我们需要单独定义专门的业务域的包来管理该业务专门的服务接口。
2、适度设计
在设计业务系统时,避免表达式的逻辑来实现业务。不要为追求Configuration而尝试把所有的业务逻辑以表达式的方式进行表述。如果业务逻辑落在数据库或者Diamond里变成片段化的QLExpress或者SPEL,那么在工程代码内完全看不懂系统处理了什么业务逻辑。这样会导致业务逻辑散一地,可读性不高,维护成本也会增加。我们需要回到设计的初衷,设计是为了解决问题的,而不是为单纯的秀设计。简单即好,适度设计。
04 总结与展望
面向业务进行架构设计建模,将业务知识沉淀到设计模型中。通过对系统的架构建模,可以进行整体的业务流程梳理和第三方系统的数据交互,确认系统建设的价值及目标。同时,在开展编码前对关键的技术模块、分层结构做好解耦的基础,指导后续业务代码的编程。
我们在熟悉建模的流程和理论方法后,期望能够做到一天甚至半天完成一个系统的业务流程的组织建模。接下来可以快速完成对核心实现模块的分层设计,进一步完成模块的领域事件、状态机、数据持久化等核心代码实现。这样可以框定后续的业务编码的边界,职责单一,只剩下业务逻辑代码的实现。