DDD 系列:项目分包和分层结构
前面几篇文章中我们陆续学习了实体、值对象、聚合根、应用服务、领域服务、仓储和领域事件。概念单独看都能理解真正开始写项目时却很容易卡住这些类到底放哪个包Controller 能不能直接调用仓储领域层能不能依赖 MyBatis基础设施层是不是只能放工具类这一篇我们就把它们放回一套完整的项目结构中。分层的目的不是让包变多而是让依赖方向和职责边界清楚。如果只是新建几个目录然后所有代码继续互相调用那只是把原来的混乱分散到了更多文件夹里。一、先确定四层职责常见的 DDD 项目可以分成用户接口层、应用层、领域层和基础设施层。层级主要职责常见内容用户接口层接收外部请求并转换协议Controller、RPC、消息消费者、Request、Response应用层编排一次完整用例ApplicationService、Command、Query、事务领域层表达核心业务模型和规则聚合、实体、值对象、领域服务、仓储接口、领域事件基础设施层实现技术能力和外部访问Repository 实现、Mapper、DO、MQ、HTTP Client、配置这四层不是简单的调用顺序更重要的是依赖方向。领域层位于业务核心不应该依赖 Spring MVC、MyBatis、消息队列客户端等具体技术。应用层依赖领域层完成用例基础设施层实现领域层声明的接口用户接口层再把外部协议转换成应用层能够理解的参数。二、按业务模块分包而不是按技术类型堆放很多项目最开始是下面这种结构com.example.mall ├─ controller ├─ service ├─ mapper ├─ entity └─ config项目小时很直观但模块多起来后service目录里可能有几百个类。想看订单业务要在五六个顶级目录之间来回跳。DDD 项目更适合先按业务边界拆分再在模块内部按职责分层com.example.mall ├─ order │ ├─ interfaces │ ├─ application │ ├─ domain │ └─ infrastructure ├─ inventory │ ├─ interfaces │ ├─ application │ ├─ domain │ └─ infrastructure └─ shared这样打开order就能看到订单上下文的完整实现订单和库存之间的边界也更明显。优先按业务能力聚合代码再用分层约束模块内部职责。这比全局建立一个巨大的domain、application、infrastructure目录更适合中大型项目。三、订单模块的完整目录示例下面是一套单体应用内的参考结构order ├─ interfaces │ ├─ web │ │ ├─ OrderController.java │ │ ├─ request │ │ └─ response │ └─ message │ └─ PaymentCompletedConsumer.java ├─ application │ ├─ command │ │ ├─ CreateOrderCommand.java │ │ └─ PayOrderCommand.java │ ├─ query │ │ └─ OrderPageQuery.java │ ├─ service │ │ ├─ OrderApplicationService.java │ │ └─ OrderQueryService.java │ └─ assembler │ └─ OrderApplicationAssembler.java ├─ domain │ ├─ model │ │ ├─ Order.java │ │ ├─ OrderItem.java │ │ ├─ OrderId.java │ │ ├─ Money.java │ │ └─ OrderStatus.java │ ├─ service │ │ └─ OrderPricingDomainService.java │ ├─ repository │ │ └─ OrderRepository.java │ └─ event │ └─ OrderCreatedEvent.java └─ infrastructure ├─ persistence │ ├─ repository │ │ └─ MyBatisOrderRepository.java │ ├─ mapper │ │ ├─ OrderMapper.java │ │ └─ OrderItemMapper.java │ ├─ dataobject │ │ ├─ OrderDO.java │ │ └─ OrderItemDO.java │ └─ converter │ └─ OrderDataConverter.java ├─ messaging │ └─ SpringDomainEventPublisher.java └─ client └─ PaymentGatewayClient.java目录不是固定答案可以按团队习惯简化。但每个类放在哪里应该能说清楚业务理由。四、请求怎么穿过这些层以支付订单为例请求链路可以写得很清楚。1. 用户接口层接收协议RestControllerRequestMapping(/orders)publicclassOrderController{privatefinalOrderApplicationServiceorderApplicationService;/** * 接收 HTTP 请求并转换成应用命令。 * * Controller 只处理协议参数和响应格式不直接修改订单状态也不调用 Mapper。 */PostMapping(/{orderNo}/pay)publicvoidpay(PathVariableStringorderNo,RequestBodyPayOrderRequestrequest){PayOrderCommandcommandnewPayOrderCommand(orderNo,request.paymentNo());orderApplicationService.pay(command);}}2. 应用层编排用例ServicepublicclassOrderApplicationService{privatefinalOrderRepositoryorderRepository;/** * 完成订单支付用例。 * * 应用层负责加载、调用和保存具体支付规则由订单聚合自身维护。 */Transactionalpublicvoidpay(PayOrderCommandcommand){OrderorderorderRepository.findByOrderNo(command.orderNo()).orElseThrow(()-newIllegalArgumentException(订单不存在));order.pay(command.paymentNo());orderRepository.save(order);}}3. 领域层执行规则publicclassOrder{/** * 支付订单。 * * 只有待支付订单才能支付外部代码不能绕过该方法直接设置状态。 */publicvoidpay(StringpaymentNo){if(status!OrderStatus.PENDING_PAYMENT){thrownewIllegalStateException(当前订单状态不允许支付);}this.paymentNopaymentNo;this.statusOrderStatus.PAID;}}4. 基础设施层完成存储仓储实现把领域对象转换成 DO再调用 Mapper。应用层和领域层不需要知道数据库表怎么设计。到这里调用方向是清楚的接口层调用应用层应用层调用领域模型和仓储接口基础设施层实现仓储接口。五、基础设施层为什么可以依赖领域层很多人第一次看这套结构会觉得奇怪应用层调用OrderRepository实现类却在基础设施层这不是反过来了吗其实这里使用的是依赖倒置。领域层声明“我需要一个订单仓储”基础设施层提供“我用 MyBatis 实现这个仓储”运行时由 Spring 把实现注入应用服务。业务核心定义接口技术细节实现接口。这样数据库框架依赖业务而不是业务依赖数据库框架。六、哪些内容不要放进 sharedshared、common很容易变成新的垃圾桶。适合共享的通常是非常稳定、没有特定业务归属的内容例如统一异常基类、分页结构、时钟接口。订单状态、会员等级、优惠规则等业务概念不应该因为“多个地方要用”就直接丢进公共包。跨上下文共享业务对象会让两个模块重新耦合。更稳妥的方式是通过业务编号、接口契约或集成事件协作。只有真正稳定且无业务归属的能力才进入共享模块。拿不准时先留在业务模块里。七、一定要拆成多个 Maven 模块吗不一定。分层首先是代码职责和依赖方向多 Maven 模块只是强化边界的一种手段。项目较小时可以先在一个 Spring Boot 模块中按包分层当团队、构建或复用边界确实需要隔离时再拆模块。常见的多模块方式如下mall-order ├─ mall-order-domain ├─ mall-order-application ├─ mall-order-infrastructure └─ mall-order-interfaces拆分后可以通过 Maven 依赖直接限制层级但也会增加构建、配置和依赖管理成本。不要为了目录看起来像 DDD就把一个小项目拆成十几个模块。八、怎么验证依赖方向先做最简单的静态检查domain下不应 import Controller、Mapper、DO 和具体 MQ 客户端application下不应直接 import Mapper 和数据库 DOinterfaces不应直接修改聚合内部字段infrastructure可以依赖领域接口但不应反过来被领域实现调用。项目稳定后可以使用 ArchUnit 把规则写成测试TestvoiddomainShouldNotDependOnInfrastructure(){noClasses().that().resideInAPackage(..domain..).should().dependOnClassesThat().resideInAPackage(..infrastructure..).check(importedClasses);}看到规则测试通过只能说明包依赖没有越界业务逻辑是否放对位置还要结合代码阅读和领域测试判断。九、总结这一篇把 DDD 项目的四层结构和分包方式串了一遍。先按业务边界组织模块再在模块内部区分接口、应用、领域和基础设施领域层保持业务纯粹技术实现通过依赖倒置接入。目录不需要照抄Maven 模块也不必一步拆满。只要职责清楚、依赖方向稳定结构就是为业务服务的。下一篇继续解决一个特别容易混乱的问题DTO、VO、DO 和 Entity 到底分别放在哪里又应该在哪一层完成转换。

相关新闻

光通信行业有哪些专业术语?

光通信行业有哪些专业术语?

光通信行业具备完善且专业的技术术语体系,各类缩写与专业名词繁多,是行业入门的主要认知壁垒。凭借长期行业积累,深知死记硬背的低效弊端,为帮助零基础从业者快速搭建行业认知,小编系统性地梳理了光通信里高频出现的核…

2026/11/9 16:29:49 阅读更多 →
Godot动画状态机实战:从Idle到Walk的流畅切换与状态管理

Godot动画状态机实战:从Idle到Walk的流畅切换与状态管理

如果你正在用 Godot 开发 2D 或 3D 游戏,角色动画状态管理可能是最让你头疼的问题之一。新手常见的做法是用一堆 if-else 判断角色状态,结果代码越写越乱,状态切换时动画卡顿、不同步,甚至出现角色"灵魂出窍"的诡异现…

2026/11/10 0:18:38 阅读更多 →
Transformer架构在AI人才智能匹配中的实践与优化

Transformer架构在AI人才智能匹配中的实践与优化

1. 项目背景与核心价值 在人力资源科技领域,AI驱动的智能人才匹配正在经历革命性变革。传统基于关键词筛选的简历匹配系统准确率普遍低于40%,而采用Transformer架构的智能匹配模型可将匹配精度提升至85%以上。这个项目正是针对高端技术岗位(特…

2026/11/12 6:44:09 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/11/13 5:13:25 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/11/13 5:13:25 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/11/13 5:13:25 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/11/13 22:08:41 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/11/12 18:22:25 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/11/13 5:13:25 阅读更多 →

日新闻

周新闻

月新闻