前言在实践领域驱动设计DDD时你可能见过两种截然不同的代码组织方式一种是传统的按技术层划分文件夹另一种是按业务模块划分文件夹。两种写法的人都声称自己在做 DDD那到底哪种更合理本文来聊聊这个问题。一、两种风格长什么样风格 A按技术层划分src/ ├── controllers/ │ ├── OrderController.java │ ├── UserController.java │ └── ProductController.java ├── services/ │ ├── OrderService.java │ ├── UserService.java │ └── ProductService.java ├── repositories/ │ ├── OrderRepository.java │ ├── UserRepository.java │ └── ProductRepository.java └── entities/ ├── Order.java ├── User.java └── Product.java特点所有 Controller 放一起所有 Service 放一起所有 Repository 放一起。代码按技术职责归类。风格 B按领域模块划分src/ ├── order/ │ ├── entity/ │ ├── service/ │ ├── repository/ │ └── event/ ├── user/ │ ├── entity/ │ ├── service/ │ ├── repository/ │ └── event/ └── product/ ├── entity/ ├── service/ └── repository/特点每个业务领域独占一个文件夹内部再按技术层细分。代码按业务归属归类。二、两种都算 DDD 吗严格来说两种都可以算 DDD。DDD 的核心在于战略设计限界上下文、通用语言和战术设计聚合、实体、值对象、领域事件等并没有强制规定文件夹必须怎么组织。只要你的代码遵循了聚合隔离、分层架构等原则文件夹结构属于实现细节。但——按领域模块划分才是 DDD 社区推荐的做法。原因往下看。三、为什么按模块分更优维度按技术层分按领域模块分可读性找一个功能需要跨 3-4 个文件夹打开一个文件夹就能看到完整业务维护性修改一个功能改动散落各处改动集中在一个目录内扩展性新增模块的文件分散在各层新建一个文件夹即可拆分微服务需要大量重构抽离直接把文件夹拎出去聚合隔离容易跨聚合乱引用天然形成物理隔离边界团队协作多人改同一文件夹冲突频繁各团队各守一个模块目录核心原因与 DDD 的限界上下文天然契合DDD 强调聚合之间在代码上应当完全隔离聚合之间通过应用层协调。按模块划分文件夹让限界上下文在物理结构上可见。一个文件夹就是一个限界上下文或聚合边界清清楚楚。而按技术层划分时这个边界是隐性的全靠开发者自觉——时间一长跨模块调用必然泛滥。四、为什么还有人按技术层分几个常见原因MVC 惯性从 Spring MVC / Rails 等框架入门习惯了 controller-service-dao 三层结构迁移到 DDD 时直接照搬。项目规模小只有 2-3 个实体时按模块分反而显得过度设计按层分更简洁直观。“伪 DDD”口头上说在做 DDD实际思维还是面向数据库的 CRUD时间一长项目会退化成披着 DDD 外衣的 MVC。框架脚手架默认生成很多脚手架默认生成的就是按层分的结构开发者没有主动调整。五、推荐的项目结构结合 DDD 四层架构 按模块划分推荐如下结构src/ ├── interfaces/ # 用户接口层对外暴露 │ ├── rest/ │ │ ├── OrderController.java │ │ └── UserController.java │ └── dto/ │ ├── application/ # 应用层用例编排不含业务逻辑 │ ├── order/ │ │ └── OrderApplicationService.java │ └── user/ │ └── UserApplicationService.java │ ├── domain/ # 领域层核心业务逻辑 │ ├── order/ # 订单限界上下文 │ │ ├── entity/ │ │ │ ├── Order.java │ │ │ └── OrderItem.java │ │ ├── valueobject/ │ │ │ └── Money.java │ │ ├── event/ │ │ │ └── OrderCreatedEvent.java │ │ ├── repository/ │ │ │ └── OrderRepository.java # 接口定义 │ │ └── service/ │ │ └── OrderDomainService.java │ │ │ └── user/ # 用户限界上下文 │ ├── entity/ │ ├── valueobject/ │ ├── repository/ │ └── service/ │ └── infrastructure/ # 基础设施层技术实现 ├── persistence/ │ ├── OrderRepositoryImpl.java │ └── UserRepositoryImpl.java ├── messaging/ └── config/这个结构的好处领域层是绝对核心不依赖任何外部框架每个限界上下文一个文件夹边界清晰架构演进友好将来拆微服务时domain/order/整个目录迁移即可新人友好看目录结构就能理解业务划分六、什么时候用哪种场景建议项目只有 1-3 个实体且不会扩展按技术层分就够了别过度设计中大型项目多个业务领域按模块分必须的团队多人协作按模块分减少代码冲突预期未来要拆微服务按模块分提前做好物理隔离学习 DDD 初期练手直接用按模块分养成好习惯七、总结结论说明两种都算 DDDDDD 不强制规定文件夹结构但按模块分是推荐做法让限界上下文在代码物理结构上可见按技术层分的本质问题业务边界隐性化长期维护成本高一句话原则代码结构应该反映业务结构而不是技术结构如果打开你的项目目录一个不懂技术的产品经理都能大概看出这里是订单、这里是用户、这里是商品那你的代码组织就对了。如果你正在从传统 MVC 项目向 DDD 转型第一步不妨就从调整文件夹结构开始——把散落在各技术层的同一业务代码收拢到同一个模块目录下。这一步虽然简单但对团队理解领域边界的帮助是立竿见影的。