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/7/24 12:45:23 阅读更多 →
Godot动画状态机实战:从Idle到Walk的流畅切换与状态管理

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

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

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

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

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

2026/7/24 12:45:23 阅读更多 →

最新新闻

基底模型与可解释性技术的产业实践与优化策略

基底模型与可解释性技术的产业实践与优化策略

1. 前沿技术研讨的价值与定位每次参加技术研讨会最让我兴奋的,就是能遇到那些真正在产业一线摸爬滚打的技术专家。上周参加的这场聚焦基底模型、可解释性和异常检测三大方向的闭门研讨,就让我记了满满二十页的笔记。不同于那些泛泛而谈的行业会议&#x…

2026/7/24 12:50:25 阅读更多 →
2026抖音去水印在线怎么操作?手机电脑通用实操教程

2026抖音去水印在线怎么操作?手机电脑通用实操教程

日常整理个人收藏素材、备份自己发布的抖音作品时,视频角落的水印会极大影响画面整洁度,很多用户都想找到简单高效的在线去水印方法。2026年抖音平台规则持续更新,不少老旧去水印工具、网页解析接口已经失效,同时各类工具的稳定性…

2026/7/24 12:50:25 阅读更多 →
AI评分系统:如何用技术提升评分一致性与公平性

AI评分系统:如何用技术提升评分一致性与公平性

1. 项目概述:当评分遇上AI去年参与某大型赛事评审系统改造时,评委们对同一作品的打分差异经常达到20分以上。这种主观性偏差让我开始思考:能否用AI建立一套客观的评分体系?经过半年实践,我们开发的智能评估系统将评分一…

2026/7/24 12:50:25 阅读更多 →
实在Agent技术解析:从动态目标分解到分布式决策架构

实在Agent技术解析:从动态目标分解到分布式决策架构

1. 项目概述:Agent技术如何重构生产力范式最近半年,我一直在跟踪测试各类智能体(Agent)系统的实际表现。从最初只能机械执行预设指令的聊天机器人,到如今能够自主规划任务链的智能体,技术迭代速度令人惊叹。…

2026/7/24 12:50:25 阅读更多 →
Q学习与PSO混合算法在无人机三维路径规划中的应用

Q学习与PSO混合算法在无人机三维路径规划中的应用

1. 项目背景与核心价值在无人机自主导航领域,三维路径规划一直是个极具挑战性的课题。传统算法在复杂环境中往往面临收敛速度慢、易陷入局部最优等问题。我们团队通过将Q学习算法与粒子群优化算法进行创新性融合,开发出一套适应性强、收敛速度快的混合路…

2026/7/24 12:50:24 阅读更多 →
LLaMA-Factory大模型微调实战:从入门到部署

LLaMA-Factory大模型微调实战:从入门到部署

1. 项目概述:LLaMA-Factory微调大模型实战指南 在AI大模型技术快速发展的当下,如何高效地对预训练模型进行微调已成为开发者面临的核心挑战。LLaMA-Factory作为一款开源的大模型微调框架,以其"零代码"的特性和全面的功能支持&#…

2026/7/24 12:49:24 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻