DDD(领域驱动设计)的分层架构主要是为了隔离不同职责,让代码结构更清晰,避免层与层之间的强耦合。下面用更“接地气”的方式聊聊常见的分层方式~
一、传统DDD四层架构(最常用)
就像一家公司分工,每层有自己的“岗位”,互相配合但不越界:
1. 表现层(Presentation Layer)
角色:像公司的“前台”或“客服”,只负责和用户打交道。
- 职责:
- 接收用户输入(比如网页表单、API请求),展示结果(返回JSON数据、渲染页面)。
- 不处理业务逻辑,只负责“传话筒”,把请求转给下一层,拿到结果再“包装”给用户。
- 例子:比如用户在电商APP下单,表现层收到订单信息,转给应用层处理,最后返回“下单成功”提示。
2. 应用层(Application Layer)
角色:类似公司的“部门主管”,不亲自干活,但协调各部门。
- 职责:
- 定义“用户能做什么”(比如“创建订单”“查询库存”),但不写具体逻辑。
- 调用领域层的服务或仓储,组合多个领域操作完成一个“用户任务”。
- 特点:逻辑简单,只做“流程控制”,比如“先查库存,再扣减,最后生成订单”。
- 例子:用户下单时,应用层调用领域层的“库存服务”检查是否有货,再调用“订单服务”生成订单。
3. 领域层(Domain Layer)
角色:相当于公司的“核心业务部门”(比如生产、研发),公司的“灵魂”所在。
- 职责:
- 包含领域模型(如订单、用户、库存等实体)、领域服务(复杂业务逻辑)、值对象(如金额、日期等不可变数据)。
- 实现真正的业务规则,比如“库存不足不能下单”“订单取消后自动退款”。
- 关键:这层是DDD的核心,不依赖任何外部组件(如数据库、HTTP框架),纯业务逻辑。
- 例子:“库存实体”里有“扣减库存”的方法,判断库存是否足够;“订单服务”里有“取消订单”的逻辑,关联库存和支付。
4. 基础设施层(Infrastructure Layer)
角色:类似公司的“后勤部门”,提供“基础设施”支持。
- 职责:
- 负责技术细节:比如操作数据库(仓储实现)、发送邮件/短信、调用第三方API(如支付接口)。
- 为其他层提供通用工具(如日志、配置中心)。
- 依赖关系:其他层可以依赖基础设施层(比如领域层需要通过仓储访问数据库),但领域层不能被其他层反向依赖。
- 例子:用MyBatis实现“订单仓储”,从数据库读取订单数据,供领域层使用。
二、简化版分层(中小型项目常用)
如果项目没那么复杂,可能合并某些层,比如:
- 合并应用层和领域层:把简单的业务逻辑直接写在应用层,领域层只保留实体和基础规则。
- 基础设施层按需拆分:比如把“数据库操作”和“工具类”分开,但整体还是归为一层。
三、分层的核心原则(避坑提醒)
- 单向依赖:上层只能依赖下层,不能反向。比如应用层依赖领域层和基础设施层,但领域层不能依赖应用层。
- 领域层独立:领域层不涉及任何技术细节,这样业务逻辑可复用(比如换数据库、换框架,领域层代码不用改)。
- 不要跨层调用:比如表现层不能跳过应用层直接调用领域层,否则分层就没意义了。
举个“买奶茶”的栗子🌰
- 表现层:用户在小程序点击“下单”,选择奶茶口味,提交订单。
- 应用层:收到下单请求后,调用“领域层的库存检查”→“领域层的订单生成”→“基础设施层的支付接口”。
- 领域层:
- 库存实体:判断“波霸珍珠剩余100份,是否足够用户下单的2份”。
- 订单实体:生成订单号、计算总价(原价+加料费用)。
- 基础设施层:把订单数据存到数据库,调用微信支付API完成扣款。
这样分层后,不管以后小程序换成APP(表现层变了),还是支付方式从微信换成支付宝(基础设施层变了),领域层的“库存扣减”“订单计算”逻辑都不用改,复用性拉满~
咱们用「开一家包子铺」来类比 DDD 四层架构,把每个环节对应到分层里,这样更直观好懂~
一、表现层:包子铺的「点餐窗口」
角色:直接和顾客打交道的「门面」,只负责「收单」和「给包子」,不关心怎么做包子。
- 场景:
- 顾客来买包子,说「要 2 个肉包、1 个菜包」(接收用户请求)。
- 店员把订单信息告诉后厨(传给应用层),同时给顾客一张取餐小票(返回交互结果)。
- 顾客吃完后评价「包子太咸」,店员记录反馈(展示结果或收集信息)。
- 关键点:只负责「人机交互」,不涉及「做包子」的核心逻辑。
二、应用层:包子铺的「大堂经理」
角色:不亲自做包子,但协调「点餐」到「出餐」的整个流程,相当于「流程总指挥」。
- 职责:
- 接到店员的订单后,先判断「现在是否能做这几种包子」(比如肉包需要现蒸,得问后厨有没有面和馅料)。
- 调用「领域层的馅料库存检查」→「领域层的包子制作」→「基础设施层的收银系统」。
- 流程控制例子: ``` 顾客下单 → 应用层:
- 检查馅料库存(领域层)是否足够做肉包和菜包;
- 若足够,通知后厨制作(调用领域层的制作逻辑);
- 制作完成后,调用收银系统收款(基础设施层);
- 通知店员给顾客包子(返回表现层)。
```
- 特点:不关心「怎么和面、调馅」(领域层细节),只关心「流程是否通顺」。
三、领域层:包子铺的「后厨核心」
角色:包子铺的「灵魂」,决定包子好不好吃、能不能做,包含所有「做包子的专业知识」。
- 组成部分:
- 领域实体:
- 包子配方(实体):比如肉包需要「面粉 500g、猪肉 300g、酱油 20ml」,菜包需要「面粉、青菜、香菇」等固定配比(业务规则)。
- 馅料库存(实体):记录当前有多少面粉、猪肉、青菜,扣减库存时必须满足「肉包制作至少需要 200g 猪肉」(业务逻辑)。
- 领域服务:
- 制作包子(服务):根据配方调和馅料、包包子、蒸包子(复杂逻辑),比如「肉包要蒸 15 分钟,菜包蒸 10 分钟」。
- 计算成本(服务):根据馅料用量和采购价,算出每个包子的成本(业务规则)。
- 值对象:
- 包子价格(不可变):肉包 3 元/个,菜包 2 元/个,一旦设定不能随意修改。
- 制作时间(不可变):记录包子开始蒸的时间和预计出锅时间。
- 关键点:不管用「传统蒸笼」还是「智能蒸箱」(基础设施层),「调馅、包包子」的逻辑始终不变,这就是领域层的独立性。
四、基础设施层:包子铺的「后勤保障」
角色:提供「硬件」和「外部支持」,让后厨和前台能正常运转。
- 职责:
- 数据存储:
- 用账本记录每天的「馅料采购量、消耗量、库存量」(类似数据库仓储),供后厨查询(领域层调用)。
- 记录顾客订单历史(比如「周三卖出 100 个肉包」),供老板分析(应用层可能需要)。
- 外部工具:
- 收银机(支付接口):扫码收款,对接微信/支付宝(第三方服务)。
- 和面机、蒸箱(技术组件):后厨用这些工具实现「揉面、蒸包子」的动作(领域层调用基础设施完成具体操作)。
- 通用服务:
- 闹钟(日志系统):提醒后厨「包子蒸好了,该出锅了」。
- 温度计(监控工具):检测蒸箱温度是否达标,记录异常(基础设施提供的通用能力)。
五、分层协作的完整流程(买包子全链路)
- 表现层:顾客说「买包子」,店员记录订单(肉包×2,菜包×1)。
- 应用层:
- 调用领域层「检查馅料库存」:发现猪肉剩 400g(够做 2 个肉包需要 300g),青菜剩 500g(够做 1 个菜包需要 200g)。
- 调用领域层「制作包子」:后厨按配方做 2 个肉包、1 个菜包,蒸好后装盘。
- 调用基础设施层「收银机」收款:总计 3×2 + 2×1 = 8 元,顾客扫码支付。
- 领域层:
- 制作肉包时,严格按「猪肉+酱油+葱花」的配方调馅,包好后蒸 15 分钟(领域逻辑)。
- 扣减库存:猪肉从 400g 减到 100g,青菜从 500g 减到 300g(实体状态变更)。
- 基础设施层:
- 收银机完成支付后,记录这笔订单到账本(数据库存储)。
- 蒸箱在蒸包子时,通过温度计监控温度,确保包子熟透(技术工具支持)。
类比总结
- 表现层:只负责「和顾客互动」,像店员的点单本和取餐窗口。
- 应用层:只负责「流程调度」,像大堂经理安排后厨和收银。
- 领域层:只负责「核心业务」,像后厨的配方和制作手艺,决定包子铺的竞争力。
- 基础设施层:只负责「技术支持」,像厨房设备和账本,是支撑业务运行的基础。
通过这种分层,哪怕包子铺从「街边小店」升级成「连锁品牌」(换了收银系统或供应链),只要后厨的「配方和手艺」(领域层)不变,核心业务就能稳定运行~
那么如何将DDD四层架构应用到实际项目开发中?
将DDD四层架构应用到实际项目开发中,需要结合具体业务场景逐步落地。以下是一个可操作的实践路径,结合示例说明:
一、前期准备:理解业务边界(核心)
- 识别领域模型
- 用「贫血模型」快速梳理业务概念(如电商的
Order
、Product
、Inventory
)。 - 示例:某在线教育系统,核心模型可能是
Course
(课程)、Student
(学生)、LearningRecord
(学习记录)。
- 划分限界上下文
- 将大系统拆分为小的「自治区域」,每个区域有独立的业务规则。
- 示例:
- 课程管理上下文:负责课程创建、编辑、上架。
- 学习记录上下文:记录学生学习进度、完成状态。
- 支付上下文:处理订单、支付、退款。
二、代码结构实现(以Java为例)
1. 表现层(Presentation)
- 职责:接收请求,返回响应,不包含业务逻辑。
- 技术实现:
- Controller层:处理HTTP请求,参数校验,返回DTO(数据传输对象)。
- DTO转换:将领域对象(Domain Model)转为前端友好的DTO。
示例代码:
@RestController @RequestMapping("/api/courses") public class CourseController { @Autowired private CourseApplicationService courseService; @PostMapping public ResponseEntity<CourseDTO> createCourse(@RequestBody CourseCreationRequest request) { // 参数校验(可使用@Valid注解) CourseDTO course = courseService.createCourse(request.toCommand()); return ResponseEntity.ok(course); } }
2. 应用层(Application)
- 职责:编排领域服务,处理事务,返回应用DTO。
- 技术实现:
- ApplicationService:定义应用接口,调用领域服务完成业务流程。
- 事务管理:使用
@Transactional
注解管理数据库事务。
示例代码:
@Service @Transactional public class CourseApplicationService { @Autowired private CourseDomainService courseDomainService; @Autowired private CourseRepository courseRepository; public CourseDTO createCourse(CourseCreateCommand command) { // 创建领域对象 Course course = courseDomainService.createCourse( command.getName(), command.getTeacherId(), command.getMaxStudents() ); // 持久化 courseRepository.save(course); // 转换为DTO返回 return CourseDTO.fromDomain(course); } }
3. 领域层(Domain)
- 职责:封装核心业务逻辑,不依赖外部组件。
- 技术实现:
- 实体(Entity):有唯一标识,状态可变(如
Course
)。 - 值对象(Value Object):不可变,描述领域中的概念(如
Money
、CourseId
)。 - 领域服务(Domain Service):处理跨实体的复杂业务逻辑。
- 仓储接口(Repository):定义数据访问契约,不涉及具体实现。
示例代码:
// 实体 @Getter public class Course { private CourseId id; private String name; private TeacherId teacherId; private int maxStudents; private List<StudentId> enrolledStudents; public void enrollStudent(StudentId studentId) { // 业务规则:课程人数不能超过最大值 if (enrolledStudents.size() >= maxStudents) { throw new BusinessException("课程已满,无法报名"); } this.enrolledStudents.add(studentId); } // 工厂方法(属于领域层) public static Course create(String name, TeacherId teacherId, int maxStudents) { // 领域规则:课程名称不能为空 if (StringUtils.isBlank(name)) { throw new BusinessException("课程名称不能为空"); } return new Course( CourseId.generate(), // 生成唯一ID name, teacherId, maxStudents, new ArrayList<>() ); } } // 领域服务 @Service public class CourseDomainService { public Course createCourse(String name, TeacherId teacherId, int maxStudents) { // 调用实体工厂方法 return Course.create(name, teacherId, maxStudents); } } // 仓储接口 public interface CourseRepository { void save(Course course); Optional<Course> findById(CourseId id); }
4. 基础设施层(Infrastructure)
- 职责:实现仓储接口,提供技术支持(数据库、消息队列等)。
- 技术实现:
- Repository实现:使用JPA/Hibernate等ORM框架实现数据访问。
- 工具类:提供通用功能(如邮件发送、文件存储)。
示例代码:
@Repository public class JpaCourseRepository implements CourseRepository { @Autowired private SpringDataCourseRepository jpaRepo; @Override public void save(Course course) { // 将领域对象转换为JPA实体 CourseJpaEntity entity = CourseJpaEntity.fromDomain(course); jpaRepo.save(entity); } @Override public Optional<Course> findById(CourseId id) { return jpaRepo.findById(id.getValue()) .map(CourseJpaEntity::toDomain); } }
三、关键落地技巧
- 从领域层开始设计
- 先不考虑数据库表结构,专注于业务规则建模。
- 示例:设计
Course
实体时,先定义enrollStudent()
方法的业务逻辑,再考虑如何持久化。
- 渐进式实现
- 第一阶段:用内存实现仓储(如
Map
),快速验证领域逻辑。
public class InMemoryCourseRepository implements CourseRepository { private Map<CourseId, Course> courses = new ConcurrentHashMap<>(); @Override public void save(Course course) { courses.put(course.getId(), course); } // 其他方法... }
- 第二阶段:替换为数据库实现,领域层代码无需改动。
- 避免贫血模型
- 错误做法:将业务逻辑放在Service层,Entity只是getter/setter(贫血模型)。
- 正确做法:将业务逻辑封装在Entity中,如
Course.enrollStudent()
。
- 处理跨边界调用
- 场景:课程报名成功后,通知学习记录上下文更新状态。
- 方案:使用领域事件(Domain Event)解耦。
// 在Course实体中发布事件 public void enrollStudent(StudentId studentId) { // 业务逻辑... DomainEventPublisher.publish( new StudentEnrolledEvent(this.id, studentId) ); }
四、测试策略
- 单元测试:重点测试领域层逻辑
@Test public void should_not_enroll_when_course_is_full() { // 给定一个已满的课程 Course course = Course.create("Java高级", teacherId, 1); course.enrollStudent(studentId1); // 当尝试添加第二个学生时 assertThrows(BusinessException.class, () -> { course.enrollStudent(studentId2); }); }
- 集成测试:验证层与层之间的协作
@SpringBootTest public class CourseApplicationServiceIntegrationTest { @Autowired private CourseApplicationService courseService; @Test public void should_create_course_successfully() { // 执行应用服务方法 CourseDTO course = courseService.createCourse( new CourseCreationRequest("DDD实战", teacherId, 30) ); // 验证结果 assertNotNull(course.getId()); assertEquals("DDD实战", course.getName()); } }
五、常见误区与避坑指南
- 过度分层
- 错误:为分层而分层,在简单场景强行套用四层架构。
- 建议:小项目可合并应用层和领域层,后期按需拆分。
- 领域层依赖基础设施
- 错误:在领域层直接使用
@Autowired
注入Repository。 - 正确:通过构造函数注入仓储接口,保持领域层独立性。
public class CourseService { private final CourseRepository courseRepository; public CourseService(CourseRepository courseRepository) { this.courseRepository = courseRepository; } }
- 忽视限界上下文
- 错误:用一个大的
User
实体贯穿整个系统。 - 正确:在不同上下文使用不同模型(如认证上下文中的
AuthUser
与订单上下文中的Customer
)。
六、推荐学习资源
- 《实现领域驱动设计》(Vaughn Vernon)
- 《领域驱动设计模式、原理与实践》(Scott Millett)
- 开源项目参考:
通过以上步骤,可将DDD四层架构逐步落地到实际项目中,从核心领域逻辑出发,再向外扩展技术实现,最终构建出高内聚、低耦合的系统。