平台架构分析与重构指南
一、 现在存在的问题
1. Service 层过于臃肿
- (1) 代码臃肿(Fat Service):所有的业务逻辑,如振动特征值计算、设备状态判定、告警等级推算、权限校验等等,全部塞在
Service里。随着功能增加,后续单个Service文件体积继续扩大。 - (2) 逻辑散落:原本属于设备本身的逻辑,却散落在
DeviceService、DataService等多个地方。 - (3) 难以维护:修改一个小的业务规则,由于
Service内部复杂的调用关系,往往会引起意想不到的连锁反应,导致维护困难。
2. 业务表达力缺失
- (1) 数据对象只是死的数据容器:在现有架构中,
Device对象通常只有get/set方法,没有任何业务行为。 - (2) 业务含义丢失:开发者在看代码时,看到的只是对数据库表的增删改查,而看不到真正的业务意图。
3. 技术实现与业务逻辑深度耦合
- (1) 数据库驱动设计:现有开发流程通常是先设计表,再写代码。这导致业务逻辑被数据库结构绑架。
- (2) 难以适配新技术:振动数据从 MySQL 扩展到时序数据库时,往往难以适应。在现有架构下,由于
Service层直接调用了特定的Mapper,想要更换底层存储或引入缓存,需要大规模重写业务代码,因为业务逻辑和持久化逻辑粘在一起了。
4. 应对复杂性的能力极弱
- (1) 设备差异化处理困难:不同类型的设备的振动分析逻辑、采样频率、告警阈值计算方法完全不同。
- (2) 硬编码风险:为了区分这些差异,
Service层会出现大量的if-else或switch-case。当接入多种设备时,代码将变得不可阅读且极易出错。 - (3) 扩展性差:如果后续想引入一套新的预测性维护算法,由于现有系统缺乏清晰的边界,新算法很难平滑地插进现有流程中。
5. 数据一致性挑战
- (1) 大事务问题:在
Service层中,为了保证一致性,往往会开启一个大事务,涉及设备状态更新、告警记录生成、推送通知等多个操作。 - (2) 性能瓶颈:在工业高频场景下,大事务会导致数据库锁竞争严重,系统并发能力受限,甚至因为一个小模块的延迟导致整个采集链路阻塞。
二、 架构更新带来的收益
1. 实现业务逻辑与技术实现的解耦
在传统架构中,业务代码往往被 SQL 或中间件 API 淹没。DDD 通过依赖倒置原则,将技术细节推向外层。
- 优点:可以先编写纯净的业务算法,而不必关心数据具体实现。
2. 建立统一语言,消除沟通鸿沟
DDD 强调程序开发人员与非编码人员的专业名词统一化。
- 优点:代码不再是冷冰冰的
updateStatus,而是analyzeVibration()、triggerAlarm()或decommissionDevice()。极大的降低了业务理解的门槛,减少了需求传递过程中的偏差。
3. 高度内聚的聚合根保障数据一致性
在物联网系统中,设备状态、配置和传感器采样频率之间存在复杂的约束。
- 优点:通过聚合根,所有对设备状态的修改都必须经过设备实体这个入口。确保了业务规则的强制执行。例如,只有在设备运行状态下才能调整采样频率,这一规则被锁死在聚合内部,避免了
Service层由于漏写判断逻辑而导致的数据非法。
4. 轻松应对设备多样性的扩展
工业现场设备千差万别,风机、泵、压缩机的分析维度各不相同。
- 优点:利用 DDD 的策略模式与限界上下文,您可以为不同类型的设备定义不同的领域服务。
5. 天然支持读写分离与高性能扩展
振动平台面临高频的波形数据采集上传与复杂看板分析的冲突。
- 优点:DDD 与 CQRS(命令查询职责分离) 模式高度契合。命令端只负责处理业务规则和状态变更,保证绝对安全。查询端针对前端的大屏看板、多维报表,可以绕过复杂的领域模型,直接通过轻量级的 DTO 甚至宽表进行检索,极大提升了系统的响应速度。
三、 架构介绍
传统的三层架构
A. 架构层次:
- 表示层 / 用户接口层 (Presentation Layer)
- 职责:负责与前端进行交互。接收用户的请求输入,进行简单的参数校验,然后调用下一层进行处理,最后将处理结果封装并格式化返回给前端。
- 常见组件: 控制器 (Controllers)。
- 业务逻辑层 (Service Layer)
- 职责:负责处理具体的业务规则、计算逻辑、流程编排以及事务管理。它承上启下,接收表示层传来的指令,并在需要读写数据时调用数据访问层。
- 常见组件: 服务类 (Service Classes)。
- 数据访问层 (Data Access Layer / DAL / Repository Layer)
- 职责: 专门负责与底层数据库或存储系统进行交互。封装了所有的 SQL 语句或数据库操作,向业务逻辑层提供简单的增删改查接口。
- 常见组件:DAO (Data Access Object)、Mapper、ORM 实体。
B. 依赖关系:
依赖是严格单向且自顶向下的: Web 层直接依赖 Service 层,Service 层直接依赖 DAO 层。
Figure 1 传统三层架构依赖关系
C. 所存在的问题:
1. 数据库驱动
- (1) 设计本末倒置:开发新功能时,开发者的第一反应往往是“如何建表?”然后再根据表结构生成实体类,最后在 Service 层写业务逻辑。这种思维是数据驱动而非业务驱动。
- (2) 贫血模型:实体类退化成了只有
getter和setter的数据载体,没有任何业务行为。 - (3) 面条式代码:所有的业务规则、状态流转、计算逻辑全部堆积在
Service层的方法里。随着业务发展,Service类会迅速膨胀,代码量激增,极难维护。
2. 业务逻辑与底层技术强耦合
- (1) 违背依赖倒置:
Service层直接依赖了 DAO 层。 - (2) 业务逻辑与技术强耦合: 如果想要更换数据库或者更换 ORM 框架,由于
Service层深层绑定了 DAO 层的具体实现或数据结构,核心业务代码往往也需要大面积重构。
3. 业务逻辑泄露与碎片化 由于缺乏严格的业务边界,业务逻辑常常散落在系统的各个角落:
- (1) 向上泄露到 Controller:开发者可能会在 Controller 层直接写一些复杂的参数校验、权限判断甚至核心业务分发逻辑。
- (2) 向下沉没到 DAO:开发者经常把复杂的业务规则写成几百行的超长 SQL 语句。这导致数据库承担了业务计算的职责,一旦业务规则变化,调试和修改 SQL 极其痛苦。
这是一份继续为您整理并排版好的 Markdown 格式文档,对领域驱动设计(DDD)的架构原理以及两个核心模块的重构实践进行了清晰的层级划分、加粗和代码高亮,保持了与上文一致的阅读体验:
四、 领域驱动设计 (DDD) 架构解析
A. DDD 四层架构
- 用户接口层 (Presentation Layer):负责向前端展示信息和解释用户指令。
- 应用层 (Application Layer):很薄的一层,不包含业务逻辑。它主要负责编排领域对象、处理事务、安全认证和发送领域事件。
- 领域层 (Domain Layer):系统的核心。包含所有的业务逻辑、领域模型、领域服务和仓储接口。它完全不依赖于具体的底层技术。
- 基础设施层 (Infrastructure Layer):提供技术支撑,包括数据库持久化、消息队列连接、第三方 API 调用等。
B. 依赖关系
为了保护领域层,现代的 DDD 四层架构应用了依赖倒置原则。在这个架构中,Domain 处于最中心位置,它不依赖于任何其他层。所有其他层的依赖箭头最终都指向领域层。
- (1) 领域层 (Domain Layer) —— 零依赖
- 依赖项:无。绝不引入任何基础设施层的代码或框架。
- 职责:它只包含纯粹的面向对象代码,以及接口定义。
- (2) 基础设施层 (Infrastructure Layer) —— 向内依赖 Domain 层
- 依赖项:Domain 层(有时也依赖 Application 层)。
- 职责:基础设施层负责实现领域层定义的接口。
- (3) 应用层 (Application Layer) —— 向内依赖 Domain 层
- 依赖项:Domain 层。
- 职责:应用层通过调用领域层中的领域对象和领域服务来编排业务用例。它同样是通过接口与基础设施层交互。
- (4) 用户接口层 (User Interface Layer) —— 向内依赖 Application 层
- 依赖项:Application 层(通常不直接跨层依赖 Domain 层)。
- 职责:接收用户请求,将其转换为应用层可以理解的指令,然后调用应用层的服务。
Figure 2 领域驱动依赖关系
C. 相比较与传统架构的优势
1. 业务与代码的高度一致性
- 通用语言:在传统开发中,业务人员说“业务话”,开发人员写“技术代码”,中间存在巨大的翻译损耗。DDD 强制要求团队建立一套通用语言。
- 代码即文档:领域模型中的类名、方法名直接反映了业务动作。这使得新加入的开发人员可以通过阅读核心领域层的代码,直接理解业务流程,降低了维护成本。
2. 天然契合微服务架构
- 限界上下文:传统架构容易演变成“大单体”和“大泥球”。DDD 在逻辑上划分出不同的限界上下文。
- 微服务拆分指南:一个限界上下文通常就是一个天然的微服务边界。为微服务的拆分提供了强有力的业务理论支撑,避免了“为了拆分而拆分”导致的服务间耦合灾难。
3. 告别贫血模型,实现代码高内聚
- 充血模型:针对传统架构中
Service层臃肿、Entity只是数据壳子的问题,DDD 将业务规则、状态变更逻辑全部内聚到实体和聚合根中。 - 保证业务规则的完整性:外部代码不能随意修改对象的状态,只能通过聚合根暴露的特定业务方法来进行。减少了脏数据的产生,保证了业务规则的严密性。
4. 核心业务逻辑与技术细节的解耦
- 依赖倒置与纯粹的领域层:在 DDD 中,领域层不依赖任何外部框架、数据库或中间件。它完全由纯净的面向对象代码组成。
- 更适应技术演进:如果未来需要把 MySQL 换成 MongoDB,或者把单体拆成微服务引入 Kafka,只需要修改最外层的基础设施层。核心的业务逻辑(领域层)一行代码都不用改。这极大地延长了软件的生命周期。
五、 架构重构落地实践
重构目标:模块按 DDD 分层拆解,引入领域对象、领域服务、应用服务、基础设施适配四层,使业务规则内聚于领域层,并保持外部接口不变。
一、 登录模块重构
A. 分层结构
- (1) 接口层:
/controller/LoginController.java- 核心组件:
LoginController - 职责定位:负责接收前端 HTTP 请求、基础的参数校验、路由分发以及返回统一的响应体。
- 设计特征:该层变薄。不再包含任何业务逻辑,仅负责将前端传入的
UserLoginDTO转发给下游,并将下游返回的UserLoginVO包装后返回给前端。
- 核心组件:
- (2) 应用层:
/application/UserApplicationService.java- 核心组件:
UserApplicationService - 职责定位:负责业务用例的编排与协调。它本身不包含核心业务规则,只负责串联各个领域组件和基础设施。
- 设计特征:在登录流程中负责调用领域服务进行业务校验,校验通过后组装并签发 JWT 令牌,最后构建返回
UserLoginVO;在注册流程中,负责调用领域实体的工厂方法创建新用户,并协调仓储进行持久化。
- 核心组件:
- (3) 领域层:
/domain/user/User.java,/domain/user/service/UserDomainService.java,/domain/user/UserRepository.java- 聚合根 (
User):摒弃贫血模型,转变为充血模型,封装了与用户身份相关的核心业务行为。 - 领域服务 (
UserDomainService):负责处理跨实体或不适合放入单一实体的业务逻辑;封装了完整的登录校验规则。校验失败时抛出明确的领域业务异常LoginFailedException、UserDisabledException。 - 领域仓储接口 (
UserRepository):利用依赖倒置原则,仅定义数据访问接口,不关心具体实现。
- 聚合根 (
- (4) 基础设施层:
/infrastructure/persistence/UserRepositoryImpl.java,UserDOMapper.java,UserConverter.java- 核心组件:
UserRepositoryImpl、UserDOMapper、UserDO、UserConverter - 职责定位:为领域层提供技术实现支持。
- 设计特征:引入 MyBatis-Plus 完成物理表的 CRUD。引入
UserConverter严格区分领域对象User与数据库对象UserDO,确保数据库表结构变更不会污染领域层。
- 核心组件:
Figure 3 登录模块重构后的结构目录
B. 重构步骤
- 新建领域层:在原模块下创建
domain/user/包,包含User聚合根(封装verifyPassword和isEnabled方法)、UserStatus值对象枚举、UserRepository仓储接口。 - 新建领域服务:在
domain/user/service/下创建UserDomainService,封装登录校验规则。 - 新建应用服务层:在
application/下创建UserApplicationService。登录负责调领域服务和发 Token;注册负责参数组装和调仓储。 - 新建基础设施层:在
infrastructure/下创建UserRepositoryImpl,内部依赖UserMapper,并使用UserConverter负责实体互转。 - 重构 Controller:
LoginController改为只依赖UserApplicationService。原UserService/UserServiceImpl保留用于用户管理功能,后续统一迁移。 - 新建 VO 与异常:新增
UserLoginVO、LoginFailedException、UserDisabledException,配合全局异常处理器。
C. 逻辑实现
- (1) 登录流程 (
POST /login)- 入口:
LoginController.login()->UserApplicationService.login() - 应用层委托领域层:
UserDomainService.login()- 按账号查用户(不存在抛
LoginFailedException) - 验密码
user.verifyPassword(...) - 验状态
user.isEnabled()
- 按账号查用户(不存在抛
- 校验通过后,应用层生成 JWT,返回
UserLoginVO。
- 入口:
- (2) 注册流程 (
POST /login/register)- 应用层调用聚合根工厂方法创建用户:
User.create(...) - 在领域对象内部完成密码 MD5、状态初始化、时间初始化。
- 通过仓储保存:
UserRepository.save(...)->UserRepositoryImpl.save(...) - 拦截重复键异常(Duplicate entry)并返回友好提示。
- 应用层调用聚合根工厂方法创建用户:
- (3) JWT 鉴权接入
- 拦截器从请求头取 JWT,解析成功后把账号状态放入
ThreadLocal(BaseContext)。 - 全局拦截,放行
/login、文档、静态资源等。
- 拦截器从请求头取 JWT,解析成功后把账号状态放入
D. 当前实现特点
- 登录业务规则已经从传统 Service 下沉到领域模型,职责更清晰。
- 接口层不再直接处理密码/状态规则,异常语义化。
- 持久化对象
UserDO与领域对象User通过 converter 解耦,避免数据库结构污染领域。 - 项目里仍保留旧风格的
UserService,处于新旧并存的渐进重构阶段。
二、 设备管理模块重构
A. 分层结构
- (1) 接口层:
system/controller/DeviceController.java- 设计特征:极度变薄。不包含事务控制和默认值处理,仅负责参数转发,并将结果包装为
DevicesResultVO返回。
- 设计特征:极度变薄。不包含事务控制和默认值处理,仅负责参数转发,并将结果包装为
- (2) 应用层:
system/application/DeviceApplicationService.java- 设计特征:负责设备用例编排(查询、新增、修改、删除及图片上传)。调用聚合根生成对象并协调仓储持久化;在查询时负责归一化查询参数
normalizeQuery。
- 设计特征:负责设备用例编排(查询、新增、修改、删除及图片上传)。调用聚合根生成对象并协调仓储持久化;在查询时负责归一化查询参数
- (3) 领域层:
system/domain/device/Device.java,DeviceRepository.java- 聚合根 (
Device):充血模型,封装状态合法性校验normalizeStatus,以及设备创建create和更新markUpdated的时间戳维护规则。 - 领域服务:暂无(业务校验完全内聚于聚合根内部)。
- 领域仓储接口 (
DeviceRepository):定义数据访问契约findAll、pageQuery、save。
- 聚合根 (
- (4) 基础设施层:
system/infrastructure/persistence/DeviceRepositoryImpl.java,DeviceConverter.java- 设计特征:引入 MyBatis-Plus(
DeviceMapper)。通过DeviceConverter区分聚合根Device与持久化实体DeviceEntity,保障领域层纯洁性。
- 设计特征:引入 MyBatis-Plus(
Figure 4 设备模块重构后的结构目录
B. 重构步骤
- 新建领域层:创建
domain/device/包,包含Device聚合根、DeviceStatus枚举、DeviceRepository接口、分页查询对象DeviceQuery和DevicePage。 - 新建应用服务层:创建
DeviceApplicationService,对接表现层,编排条件查询、设备保存、本地与云端 OSS 图片上传。 - 新建基础设施层:创建
DeviceRepositoryImpl实现仓储接口,并增加DeviceConverter处理实体转换。 - 新建异常机制:新增
DeviceValidationException和DeviceOperationException,并配套全局异常捕获器DeviceExceptionHandler。 - 重构 Controller:全面删除对原有贫血模型
DeviceService的调用。原DeviceServiceImpl增加@Deprecated注解标记废弃,平滑过渡。
C. 逻辑实现
- (1) 分页查询流程 (
POST /devices/PageConditional)- 应用层通过
normalizeQuery()处理空值默认化,生成领域查询对象DeviceQuery。 - 委托仓储层
DeviceRepository.pageQuery(query),底层使用 PageHelper 结合 Mapper 执行物理分页。 - 将结果转换为
DevicesPageResultDTO,在 Controller 组装返回。
- 应用层通过
- (2) 新增与修改流程 (
POST /devices,PUT /devices)- 新增:应用层调聚合根工厂
Device.create(...),内部完成状态校验防腐与时间初始化。 - 修改:应用层通过
Device.reconstitute(...)恢复对象,调用markUpdated()更新时间戳。 - 最终均通过仓储
save(...)存入数据库。
- 新增:应用层调聚合根工厂
- (3) 异常处理流程
- 底层校验失败(如非法状态、图片上传失败)直接抛出领域异常。
DeviceExceptionHandler全局接管,转为Result.error()输出,Controller 彻底告别try-catch。
D. 当前实现特点
- 业务规则内聚:状态校验收敛到
Device聚合根内部,解决数据被随意篡改的隐患。 - 强大的防腐隔离:利用
DeviceConverter彻底斩断了数据实体对核心业务层的侵入。 - 异常语义化与表现层减负:依靠自定义异常与
RestControllerAdvice结合,剥离了 Controller 层错综复杂的逻辑判断。 - 安全过渡架构:旧版 Service 使用
@Deprecated标识,接口向下兼容,保障了稳健的工程化改造。