DDD落地实战:分层架构+领域/应用服务彻底理清

很多开发落地 DDD 时踩两大坑:一是Repository接口与实现分层乱放,领域代码耦合数据库技术;二是分不清ApplicationService应用服务、DomainService领域服务职责边界。本文结合工业最优架构规范 + 生活化案例,一站式讲清 DDD 四层架构规范与服务分层准则,可直接落地项目。

一、DDD 标准四层架构:Repository 接口在 Domain、实现落在 Infra

主流 DDD 标准划分为Interface 接口层、Application 应用层、Domain 领域层、Infra 基础设施层,仓储 Repository 遵循接口归领域、实现归基础设施核心规范,也是依赖倒置原则的经典落地。

1. 各分层职责与核心组件

分层

核心职责

包含组件

Interface 接口层

输入输出控制

请求接入

参数基础校验(长度,非空,格式,枚举)

结果封装

异常处理

Controller、DTO、VO、全局异常处理器

Application 应用层

流程细节

业务流程编排

事务管控

协调仓储与外部资源

应用 Service,只做流程不实现业务规则,几乎不写if判断

Domain 领域层

业务细节

承载业务规则校验

核心业务规则

领域模型

技术无关,与底层技术解耦

聚合根、Repository 接口、DomainService、领域枚举

Infra 基础设施层

技术细节

数据库

缓存

RPC

MQ

Repository 实现类、Mybatis Mapper、Redis 工具、RPC 客户端

动作放在哪一层示例
核心业务规则Domain 领域层计算价格、库存校验
多聚合根操作Domain 领域层(领域服务)多聚合根操作
流程编排、事务Application 应用层下单流程、事务控制
RPC 调用Infra 基础设施层Dubbo、Feign、HTTP
发送 MQInfra 基础设施层RocketMQ、Kafka
数据库操作Infra 基础设施层MyBatis、JPA
缓存操作Infra 基础设施层Redis

调用流向:

前端→Interface→Application(组装Repository和Domain)→Domain (Repository 接口)←Infra (Repository 实现)

四、代码示例(订单调用用户服务)

1. 定义接口(放在 Domain / Anti-Corruption 防腐层)

这一层只定义 “要什么”,不写实现。

// 防腐层/领域层接口public interface UserService {

    UserDTO getUser(Long userId);}

2. Application 应用层调用接口

完全不知道底下是 RPC / 本地 / 假数据

@Service
public class OrderApplicationService {



    // 注入接口

    private final UserService userService;



    public Order createOrder(Long userId, ...) {

        // 调用!完全无感

        UserDTO user = userService.getUser(userId);

        // ...

    }}

3. Infra 层实现 RPC(真正写 Dubbo/Feign)

这才是写 RPC 代码的地方!

// Infra 层
@Repository
public class UserServiceRpcImpl implements UserService {



    // RPC 客户端

    private final UserRpcClient userRpcClient;



    @Override

    public UserDTO getUser(Long userId) {

        // 真正 RPC 调用

        return userRpcClient.getUserById(userId);

    }}

2. Repository 分层细节说明

所属分层

核心作用

技术依赖

Order 聚合根

Domain

封装订单业务属性与行为、业务规则

无任何 DB/MyBatis 依赖

OrderRepository(接口)

Domain

定义聚合根存取契约

仅依赖聚合根,不绑定存储技术

OrderRepositoryImpl(实现)

Infra

基于 MySQL/MyBatis 完成数据存取

依赖 Repository 接口 + Mapper 数据库组件

OrderMapper

Infra

直接和数据库交互

依赖 Mybatis、JDBC

代码示例(仓储实现)

java
@Repository
public class OrderRepositoryImpl implements OrderRepository {
    @Override
    public Order findById(Long orderId){
        //查询订单主表
        Order order= orderMapper.selectById(orderId);
        //查询订单项子数据
        List<OrderItem> items = orderItemMapper.selectList(Wrappers.lambdaQuery(OrderItem.class).eq(OrderItem::getOrderId,orderId));
        //组装聚合根
        order.setOrderItems(items);
        return order;
    }
}

3. 该分层方案优缺点

优势

  1. 符合依赖倒置 DIP:切换存储(MySQL→Redis/ES)仅修改 Infra 实现,领域、应用层代码无需改动;
  2. 领域纯净解耦:聚合根无任何技术框架依赖,只聚焦业务逻辑;
  3. 单元测试便捷:测试应用 / 领域代码时 Mock 仓储接口即可,不用启动数据库;
  4. 技术收敛:所有 ORM、缓存、RPC 等技术代码统一收敛在 Infra,业务代码无技术污染;
  5. 架构易迭代:分库分表、更换 ORM、多数据源改造全部局限在基础设施层。

缺点

项目分包层级变多,小型 CRUD 项目略显冗余;极简小项目可折中:仓储实现和接口同包放入 Domain,省略独立 Infra 目录。

4. 架构选型对比

传统三层架构(Controller→Service→Mapper):DDD 项目不推荐
Repository 接口实现全部放在业务包,领域逻辑与数据库代码混杂,换存储需大面积修改业务代码,难以做单元测试。

极简 DDD:小型单体项目首选
Repository 接口仍在 Domain,实现类和 Mapper 统一放在 Domain 包,省去 Infra 分层,保留 DDD 核心思想降低开发成本。

5. 可选扩展分层(大型中台 / 复杂微服务)

普通业务四层架构即可,大型交易系统可额外新增三层:

  • Common 公共层:全局工具类、统一返回体、错误码;
  • Client SDK 层:RPC 对外调用接口与 DTO,单独打包供第三方依赖;
  • Query 视图层(CQRS 读写分离):复杂报表、多表查询单独拆分查询仓储。

五、一分钟分清 ApplicationService 与 DomainService,附生活化 + 业务案例

核心口诀:应用服务管流程编排与外部资源,领域服务管核心业务规则;应用层调用领域层,领域永远不反向依赖应用

案例 1:银行转账场景

需求:A 账户转账 100 元至 B 账户,扣款、加钱、记录流水、发送通知。

1. DomainService(领域服务):只处理核心业务规则

只负责资金增减、余额校验等不可变更的业务逻辑,无事务、不查库、不发消息、不调用第三方,仅修改聚合根状态。

java
public class TransferDomainService {
    //纯核心业务规则:余额校验+资金划转
    public void transfer(Account from, Account to, BigDecimal money) {
        //领域规则:余额不足抛异常
        if (from.getBalance().compareTo(money) < 0) {
            throw new BalanceNotEnoughException();
        }
        from.deduct(money); //扣款
        to.credit(money); //入账
    }
}

2. ApplicationService(应用服务):流程编排总指挥

负责查询聚合、开启事务、调用领域逻辑、持久化数据、发送短信 / 消息等外围操作,不编写业务规则。

java
@Service
@Transactional //事务定义在应用层
public class TransferApplicationService {
    private final TransferDomainService transferDomainService;
    private final AccountRepository accountRepository;
    private final NotifyService notifyService;

    public void transfer(Long fromId, Long toId, BigDecimal money) {
        //1. 仓储查询获取聚合
        Account from = accountRepository.findById(fromId);
        Account to = accountRepository.findById(toId);
        //2. 调用领域服务执行核心规则
        transferDomainService.transfer(from, to, money);
        //3. 持久化变更数据
        accountRepository.save(from);
        accountRepository.save(to);
    }
}

形象比喻

DomainService = 银行法定转账规则,ApplicationService = 大堂经理统筹全流程。

案例 2:订单创建(扣库存)实战落地

DomainService(领域层:跨聚合业务规则)

java
public class OrderCreateDomainService {
    public Order createOrder(Long userId, List<Item> items) {
        Stock stock = stockRepository.findById(...);
        Order order = new Order();
        stock.lockStock(items); //领域规则:锁定库存
        order.calculateTotalPrice(items); //领域规则:计算订单总价
        return order;
    }
}

ApplicationService(应用层:流程编排)

java
@Service
@Transactional
public class OrderApplicationService {
    public OrderVO createOrder(Long userId, List<Item> items) {
        //调用领域生成订单
        Order order = orderCreateDomainService.createOrder(userId, items);
        //落地库
        orderRepository.save(order);
        //发送MQ消息
        mqService.sendOrderCreatedMsg(order.getId());
        //DTO转换返回
        return converter.toVO(order);
    }
}

区分总结 3 条铁律

跨聚合根操作、核心业务校验、业务计算逻辑 → DomainService;

事务控制、DB 存取、MQ/RPC/ 短信调用、参数组装、VO 转换 → ApplicationService;

依赖顺序:Controller→ApplicationService→DomainService→聚合根,单向依赖。

六、落地总结

中大型项目:严格遵循四层架构,Repository 接口 Domain、实现 Infra,业务规则落 DomainService,流程编排落 ApplicationService;

小型 CRUD 项目:简化架构,去掉独立 Infra 分层,仓储实现和接口同包,保留领域与应用服务分层思想即可;

DDD 落地核心不变:技术细节下沉基础设施,业务规则收敛领域层,彻底实现业务与技术解耦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值