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/10/20 5:22:04 阅读更多 →
Godot动画状态机实战:从Idle到Walk的流畅切换与状态管理

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

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

2026/10/19 7:24:16 阅读更多 →
Transformer架构在AI人才智能匹配中的实践与优化

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

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

2026/10/20 16:47:32 阅读更多 →

最新新闻

Spark Streaming vs Flink:流处理选型核心差异与实战决策指南

Spark Streaming vs Flink:流处理选型核心差异与实战决策指南

1. 流处理选型这件事,为什么值得花时间掰扯清楚做实时数据处理的人,迟早会撞上同一个岔路口:Spark Streaming 和 Flink 到底选哪个。这个问题在技术社区里被反复讨论,但大多数对比文章停留在"Flink 是纯流、Spark 是微批&quo…

2026/10/21 4:35:50 阅读更多 →
OpenClaw 2026.3.28 Windows 原生部署:角色初始化与排错全流程

OpenClaw 2026.3.28 Windows 原生部署:角色初始化与排错全流程

把OpenClaw 2026.3.28 跑进 Windows 原生环境,是我近期做得最折腾也最值的一件事。这里的“原生”,我指的是不套 WSL、不丢容器镜像,直接在 Windows 桌面系统的本地环境里装依赖、起服务、完成角色初始化并把角色跑起来。这篇文章把整个部署过…

2026/10/21 4:35:50 阅读更多 →
阿里EDAS示例工程上云实战:本地启动、部署配置与灰度避坑指南

阿里EDAS示例工程上云实战:本地启动、部署配置与灰度避坑指南

简介:这份资源是阿里EDAS(企业级分布式应用服务平台)的入门示例工程,面向希望学习微服务架构与云端应用部署的Java开发者,尤其适合刚接触EDAS、需要动手实践的中初级人员。压缩包共15个文件,以8个xml配置文…

2026/10/21 4:35:50 阅读更多 →
Redis哨兵实战:高可用架构部署、故障转移与踩坑经验

Redis哨兵实战:高可用架构部署、故障转移与踩坑经验

Redis 哨兵这套东西,我最早接触是在一个电商促销活动前夜。当时主从架构跑得好好的,结果主节点凌晨挂了,运维手动切了四十分钟,老板在群里连发十几条消息。从那以后我就把哨兵当成了高可用方案的标配。这篇文章不讲教科书定义&…

2026/10/21 4:35:50 阅读更多 →
C# WinForm Panel 边框颜色改不动?三种自绘方案一次讲清

C# WinForm Panel 边框颜色改不动?三种自绘方案一次讲清

简介:面向C# WinForm开发者的Panel控件美化方案,解决默认边框颜色与宽度不可直接调整的问题。资源通过继承Panel并重写OnPaint方法,以Graphics绘制自定义边框,提供BorderColor与BorderWidth两个扩展属性,示例代码清晰易…

2026/10/21 4:35:50 阅读更多 →
BMI160六轴传感器驱动开发实战:从寄存器配置到姿态解算的完整指南

BMI160六轴传感器驱动开发实战:从寄存器配置到姿态解算的完整指南

简介:面向STM32F030平台的博世BMI160自写驱动代码,通过I2C总线实现三轴陀螺仪的基础数据交互,适用于嵌入式开发者学习传感器驱动编写或快速搭建原型。压缩包共3个文件、仅6KB,包含BMI160.c源文件以及BMI160.h、BMI160_prm.h两个头…

2026/10/21 4:34:49 阅读更多 →

日新闻

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

统一内存+量化:锐龙AI Max PRO 400如何本地跑320B大模型

这段时间硬件圈和AI圈都在传一个事:AMD锐龙AI Max PRO 400系列把最高192GB统一内存搬进了PC形态的产品里,按宣传口径,本地跑320B参数的大家伙不再是纸面概念。我第一反应倒不是“内存真大”,而是这话背后的含金量——它把过去只能…

2026/10/21 0:00:11 阅读更多 →
AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

AI助手深度使用体验:对话质量、长文本处理与指令遵循如何留住用户

1. 从“封号重开”这个行为说起:用户到底在追求什么“封号重开”这四个字,放在任何产品语境里都带着一股决绝的味道。一个账号从零开始积累使用记录、调教偏好、沉淀工作流,背后是大量的时间和精力成本。正常情况下,没有人愿意主动…

2026/10/21 0:00:11 阅读更多 →
ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

ROS中YOLOv3工程落地:通信同步、坐标变换与性能调优

1. 为什么在ROS里硬刚YOLOv3不是“炫技”,而是工程落地的必经之路我第一次在某高校机器人实验室看到那台搭载Jetson TX2的移动底盘原地打转、反复识别错垃圾桶时,就意识到:单纯跑通darknet_ros demo和真正让YOLOv3在ROS里“干活”&#xff0c…

2026/10/21 0:00:11 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/21 4:27:17 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 9:50:45 阅读更多 →