news 2026/8/24 2:23:20

MateCloud DDD四层架构实战:跟着一个请求看清每一层的边界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MateCloud DDD四层架构实战:跟着一个请求看清每一层的边界

MateCloud DDD四层架构实战:跟着一个请求看清每一层的边界

【免费下载链接】matecloud🔥MateCloud是一款基于Spring Cloud Alibaba的微服务架构。目前已经整合Spring Boot 4.0.7、 SpringCloud 2025、Spring Cloud Alibaba 2025、Spring Security Oauth2、Feign、Dubbo、JetCache、RocketMQ等,支持多租户的低代码平台,Saas平台开发套件项目地址: https://gitcode.com/GitHub_Trending/ma/matecloud

在用户模块加一个字段要动6个文件、800行的UserService、单测要先mock半个类——这些痛点的解药,就是DDD四层架构。MateCloud是一个基于Spring Cloud Alibaba的微服务项目(整合Spring Boot、Dubbo、RocketMQ等,面向多租户SaaS开发),把这套分层落成了真实源码:Trigger、Application、Domain、Infrastructure四层,各管各的,严禁越界。本文以它真实的用户模块为例,一步步拆解这次微服务分层架构实践。

🎯 一分钟看懂核心思想:每层只管自己的事

思路不复杂:把"业务规则"和"技术实现"分开,把"用例编排"和"入口协议"分开。每个服务模块下就四个目录:

  • Trigger层(trigger/):管外部入口——REST控制器、Dubbo RPC、事件监听,只做认证、参数校验、结果包装,严禁写业务规则;
  • Application层(application/):管用例编排——CQRS把读写拆成两条路,写命令服务管事务边界,严禁自己揣业务规则;
  • Domain层(domain/):业务核心——聚合根、实体、值对象、领域服务,所有业务不变量在这,严禁引用Spring、MyBatis注解;
  • Infrastructure层(infrastructure/):管技术实现——DAO、PO、仓储实现、事件发布实现,严禁让业务概念往上泄漏,只给领域层定义的接口做适配器。

关键在依赖方向:领域层不依赖任何人。仓储接口由领域层定义、基础设施层实现,这是依赖倒置,也是"可替换"的前提。admin(角色/菜单/字典)和tenant(租户)子模块用的是同一套结构,说明这是全项目约定,不是某个模块的偏好。

📍 跟着一个请求走:创建用户的完整链路

以"创建一个用户"(POST /api/v1/users)为例,从入口追到数据库。

① Trigger层:认证、校验、然后交给下一层

请求经网关转发到mate-system,落在 UserController。类级@SaCheckLogin查登录,方法级@SaCheckPermission(Perms.USER_ADD)查权限,@Valid完成参数校验,然后直接转交命令服务。如果在这里写"用户名查重""密码编码",后面新增RPC或导入入口时,这些规则就得再实现一遍——这正是它不碰业务的原因。

② Application层:事务边界与编排

写路径的核心是 UserCommandService 的 createUser,它演示了应用层的分工:

@Transactional(rollbackFor = Exception.class) public String createUser(RegisterUserCommand command) { tenantQuotaService.assertCanAddUser(TenantContext.getTenantId()); UserAggregate aggregate = userDomainService.createUser( command.getUsername(), command.getPassword(), command.getMobile(), command.getEmail(), command.getRealName()); userRepository.save(aggregate); eventPublisher.publish(new UserCreatedEvent( aggregate.getId(), command.getUsername(), command.getMobile())); return aggregate.getId(); }

先看多租户的套餐配额,再让领域服务创建聚合,经仓储接口持久化,最后发一个领域事件——监听器必须用@TransactionalEventListener(AFTER_COMMIT),回滚时不会有幽灵事件。关键:整个方法没有一条if/else业务规则,只有"先A后B、事务我来管"。

③ Domain层:业务规则与状态变更

UserDomainServiceImpl 是一个没有任何Spring注解的类,它做校验:用户名查重、手机号查重、密码经PasswordEncoderPort端口哈希(领域层不关心用哪种加密),然后UserAggregate.createNew生成聚合——分配UUID、状态置为ACTIVE,并记录一条CREATE操作流水。如果这些校验写在SQL或DAO里,规则就只存在于那一处,下一个人走导入或RPC"创建用户"时就直接绕过去了。

④ Infrastructure层:把聚合落进数据库

UserRepositoryImpl 接手领域层定义的仓储接口:转换器把实体转成UserPO,MyBatis-Plus的UserDao插入,再把聚合上累积的操作流水批量入库。表结构、mapper、逻辑删除(@TableLogic)这些技术细节全封在这层,哪天换持久化框架,动的只有这个目录。

读路径更短:UserQueryServiceImpl 经仓储接口取聚合,再用转换器 UserConvertor 返回UserInfoResponse——这个DTO定义在mate-api模块,其他服务的RPC调用共用。同一套领域代码还被 RpcUserServiceImpl 和Excel导入复用,importBatch 就是逐行走同一个 createUser 管线,保证两个入口规则一致。

🔎 分层要点速览

Trigger层:协议适配器,业务逻辑出门口即停

职责边界:HTTP/RPC/事件的最后一段路,只做认证、校验和Result包装。

@PostMapping @SaCheckPermission(Perms.USER_ADD) public Result<String> createUser(@Valid @RequestBody RegisterUserCommand command) { return Result.ok(userCommandService.createUser(command)); }

关键在"转手就走",控制器代码量几乎是恒定的,它变长就是逻辑泄漏的信号。常见误区:把查重这类规则放控制器里,后果是每个新入口都要复制一份。

Application层:CQRS拆读写,事务在这层

职责边界:写走CommandService(挂@Transactional),读走QueryService返回DTO。

@Override public UserInfoResponse findById(String userId) { UserAggregate aggregate = userRepository.findById(userId); return aggregate == null ? null : userConvertor.toResponse(aggregate); }

关键:读路径绝不碰"改状态"的方法,返回的是DTO而不是聚合根。常见误区:把状态机if/else塞进应用层,规则就和"这条编排"绑死了,别的用例没法复用。

Domain层:纯Java,一个框架注解都没有

职责边界:聚合、实体、值对象、领域服务,所有业务不变量在这层。

public void freeze() { checkActive(); this.status = UserStatus.FROZEN; } private void checkActive() { if (isFrozen()) throw UserException.of(UserErrorCode.USER_IS_FROZEN); if (isDomainDeleted()) throw UserException.of(UserErrorCode.USER_IS_DELETED); }

关键:"已删除的用户不能再被冻结"写在实体自己的方法里,谁来调都绕不过。常见误区:给领域类加@Service或MyBatis注解,领域层从此没法用纯JUnit测。

Infrastructure层:只为领域定义的接口做实现

职责边界:DAO、PO、仓储实现、事件发布实现,只有技术细节。

@Override @Transactional(rollbackFor = Exception.class) public void save(UserAggregate aggregate) { UserPO po = UserInfraConvertor.INSTANCE.toPO(aggregate.getUser()); userDao.insert(po); aggregate.getUser().setId(po.getId()); batchSaveStreams(aggregate.getAndClearOperateStreams()); }

关键:转换、表结构、逻辑删除都封在里面,领域层从头到尾没看见UserPO。常见误区:把仓储接口放在infra、让domain依赖它,依赖方向一反过来,"可替换"就是空话。

📊 传统写法 vs DDD四层:一笔账怎么算

维度传统写法(大Service)MateCloud的四层
改一个字段/规则Controller/Service/DAO/mapper多处散改领域模型+转换类,入口和其他层不动
单测业务规则要Spring容器+测试库,慢纯JUnit不起容器,见 UserTest、UserAggregateTest
换持久化技术业务代码与DAO缠绕只动infra层的Dao与仓储实现
新增入口(RPC/导入/事件)逻辑复制或跨层乱调复用同一个Command服务
多租户隔离每条SQL手工加租户条件命令层套餐配额断言+租户拦截器自动圈定查询范围

⚠️ 分层架构最常见的坑

  1. 业务逻辑写进Controller。后果:规则只存在于那一处,单测只能走HTTP集成。正确姿势:控制器只做认证、校验、转交,规则下沉领域层。
  2. 领域层引入框架注解。@Service@Transactional、MyBatis注解,出现一个,领域层就不"可移植"了。正确姿势:看 UserDomainServiceImpl——零注解,Bean由基础设施层的 DomainServiceConfiguration 注册。
  3. 应用层直调DAO绕过领域。后果:"冻结用户不能改资料"这类不变量只在部分入口生效,同一份数据不同入口行为不一致。正确姿势:写操作一律 领域服务 → 聚合 → 仓储。
  4. 聚合根直接返回给前端。后果:密码这类内部字段可能泄漏,表结构一变前端跟着遭殃。正确姿势:读路径统一经转换器出DTO。
  5. 什么都上全套四层。单表字典CRUD也切四层,样板代码比逻辑还多。我的建议:按领域复杂度决定建模深度,值得拆的才拆,简单域可以先用两层过渡。

🧩 落地清单:在你自己项目复刻的最小步骤

  1. 先选一个限界上下文:挑你最熟的模块(用户、订单、角色都行),划出trigger/application/domain/infrastructure四个目录,先不动代码,只搭骨架;
  2. 再抽领域模型:把聚合根、实体、值对象从Service里挪出来,"什么状态下能做什么"变成实体方法,只留纯POJO和接口;
  3. 然后做依赖倒置:仓储接口放domain、实现放infrastructure,领域服务的Bean用config类注册;
  4. 再拆读写:Command/Query分开,读走转换器出DTO,统一Result返回;
  5. 最后补入口:RPC、事件监听、导入都进trigger层,复用同一套命令服务。

设计动机和约定细节,项目里都有现成文档可以对照。

🏁 收尾:它解决了什么,没解决什么

回到开头的问题:字段变更不再散落六处文件,规则变更集中在领域模型,业务规则的单测不用容器就能写。但四层架构不解决所有事——限界上下文的边界怎么划、建模做到多深,仍然靠你的判断;前期建模成本比一个大Service更高,很小的CRUD项目硬上全套反而负担。这套架构是前期付费、后续每次需求变更都在回本。想深入设计动机,直接看 DDD架构RFC、编码规范 和 用户模块源码。

【免费下载链接】matecloud🔥MateCloud是一款基于Spring Cloud Alibaba的微服务架构。目前已经整合Spring Boot 4.0.7、 SpringCloud 2025、Spring Cloud Alibaba 2025、Spring Security Oauth2、Feign、Dubbo、JetCache、RocketMQ等,支持多租户的低代码平台,Saas平台开发套件项目地址: https://gitcode.com/GitHub_Trending/ma/matecloud

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/24 2:21:28

如何 5 分钟把 VS Code 字体换成 Commit Mono:安装与连字一步到位

如何 5 分钟把 VS Code 字体换成 Commit Mono&#xff1a;安装与连字一步到位 【免费下载链接】commit-mono Commit Mono is an anonymous and neutral programming typeface. 项目地址: https://gitcode.com/gh_mirrors/co/commit-mono 想让编辑器里的代码更好读&#…

作者头像 李华
网站建设 2026/8/24 2:20:53

3分钟把 NCM 音乐免费转成 MP3:ncmdump 拖一下就能用

3分钟把 NCM 音乐免费转成 MP3&#xff1a;ncmdump 拖一下就能用 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 你从网易云下载的 .ncm 文件&#xff0c;拷到别的播放器打不开&#xff0c;丢进剪辑软件也被拒。问题不在设备&#x…

作者头像 李华
网站建设 2026/8/24 2:20:31

如何系统刷 AtCoder 题目:3 步本地部署刷题平台

如何系统刷 AtCoder 题目&#xff1a;3 步本地部署刷题平台 【免费下载链接】AtCoderProblems Extend your AtCoder 项目地址: https://gitcode.com/gh_mirrors/at/AtCoderProblems 刷 AtCoder 时你多半卡在这种状态&#xff1a;题目散落在几百场比赛的归档里&#xff0…

作者头像 李华
网站建设 2026/8/24 2:20:05

构建可审计AI科学家:从假设演化协议到工程实践

1. 从“黑盒”到“白盒”&#xff1a;为什么我们需要可审计的AI科学家&#xff1f;最近和几个做科研的朋友聊天&#xff0c;大家不约而同地提到了一个痛点&#xff1a;用大语言模型&#xff08;LLM&#xff09;辅助科研&#xff0c;效率确实上去了&#xff0c;但心里总有点不踏…

作者头像 李华
网站建设 2026/8/24 2:20:00

6G显存玩转AI视频生成:ComfyUI稳定工作流搭建与4K输出实战

这类工具最值得先看的不是功能列表&#xff0c;而是能不能在普通环境里稳定跑起来。很多人一上来就冲着“4K”、“AI视频生成”这些词&#xff0c;结果发现要么启动报错&#xff0c;要么显存直接爆掉&#xff0c;最后连个像样的测试结果都看不到。如果你手头只有一块6G显存的显…

作者头像 李华
网站建设 2026/8/24 2:19:01

2026年测试工程师面试趋势与核心考点解析

1. 为什么2026年的测试面试题值得关注软件测试行业正在经历前所未有的技术迭代。从传统的功能测试到现在的AI驱动测试、混沌工程、左移测试等新范式&#xff0c;测试工程师的能力模型已经发生根本性变化。根据2025年ISTQB全球调研报告&#xff0c;超过67%的企业在招聘测试岗位时…

作者头像 李华