论领域驱动DDD项目中 应用层 和 领域模型层 区别对比

在DDD(领域驱动设计)项目中,应用服务层(Application Layer)和领域模型层(Domain Layer)的核心区别可以概括为:应用服务层是业务流程的"指挥家/编排者",而领域模型层是业务核心的"发动机/规则制定者"

应用服务层不包含任何核心业务逻辑,只负责协调和转发;领域模型层则承载了系统所有的业务规则、状态变化和逻辑校验。

以下是两者的详细对比、目录结构区别、适用场景以及完整的代码示例。

⚖️ 核心区别与职责对比

维度 应用服务层 (Application Layer) 领域模型层 (Domain Layer)
核心定位 业务流程的编排者协调者 业务核心逻辑与规则的承载者
业务逻辑 严禁包含核心业务规则 包含所有核心业务规则、策略和完整性约束
主要职责 用例编排、事务控制、权限校验、DTO与领域对象转换、发布领域事件 表达业务概念、维护业务状态、执行领域行为(实体/值对象/聚合根)、跨聚合逻辑(领域服务)
依赖关系 依赖领域层和基础设施层(调用仓储接口) 零依赖(不依赖其他任何层,不依赖框架和技术细节)
典型组件 应用服务 (Application Service)、命令/查询对象 (Command/Query)、DTO 聚合根 (Aggregate Root)、实体 (Entity)、值对象 (Value Object)、领域服务 (Domain Service)、仓储接口 (Repository Interface)

📂 目录结构区别

在标准的DDD四层架构中,这两层的代码目录通常如下划分(以Java/Maven项目为例):

text 复制代码
com.example.project
├── application/                 【应用服务层】
│   ├── service/                 # 应用服务接口与实现(如 OrderApplicationService)
│   ├── dto/                     # 数据传输对象(入参Command、出参DTO)
│   └── assembler/               # 负责 DTO 与 领域对象 之间的转换(或叫 Converter)
│
├── domain/                      【领域模型层】
│   ├── model/                   # 核心领域模型
│   │   ├── Order.java           # 聚合根 (Aggregate Root)
│   │   ├── OrderItem.java       # 实体 (Entity)
│   │   └── vo/                  # 值对象 (Value Object,如 Address, Money)
│   ├── service/                 # 领域服务 (处理跨聚合的复杂业务逻辑)
│   ├── event/                   # 领域事件 (Domain Event)
│   └── repository/              # 仓储接口 (Repository Interface,只定义接口,不含实现)
│
└── infrastructure/              【基础设施层】
    └── repository/              # 仓储接口的具体实现 (如 OrderRepositoryImpl)

🎯 场景选择与功能归属

在实际开发中,判断一段逻辑该写在哪一层,可以遵循以下原则:

  1. 适合放在【领域模型层】的场景:
    • 单一对象的行为与规则: 比如"订单金额计算"、"优惠券是否可用校验"、"订单状态从'待支付'流转到'已支付'"。这些逻辑应该封装在聚合根或实体的方法中(充血模型)。
    • 跨多个聚合的业务规则: 比如"转账业务"(涉及两个账户聚合的余额扣减与增加),或者"下单时校验用户信用额度并扣减库存"。这种不属于单一实体的逻辑,应放在领域服务中。
    • 业务概念的封装: 比如金额(Money)、收货地址(Address)等不可变对象及其自带的校验规则,应作为值对象
  2. 适合放在【应用服务层】的场景:
    • 业务流程的串联(用例): 比如"用户下单"这个动作,需要依次执行:参数转换 -> 调用领域服务校验 -> 调用聚合根创建订单 -> 调用仓储保存 -> 发布订单创建事件。应用服务只负责把这些步骤串起来。
    • 技术层面的横切关注点: 比如开启和提交数据库事务(@Transactional)、当前登录用户的权限校验、记录操作日志等。
    • 与外部世界的交互适配: 接收前端传来的DTO,将其转换为领域层能理解的领域对象或命令(Command),或者将领域对象转换为返回给前端的DTO。

💻 完整代码示例参考

以一个电商系统的 "创建订单" 为例,对比传统贫血模型与DDD富血模型的写法。

1. 领域模型层(Domain Layer)实现

这里体现了业务规则的封装。Order 聚合根负责保证自身数据的一致性,Money 值对象负责金额的运算规则。

java 复制代码
// 值对象:金额 (封装了金额的运算规则,不可变)
public class Money {
    private final BigDecimal amount;
    private final String currency;

    public Money add(Money other) {
        if (!this.currency.equals(other.currency)) {
            throw new IllegalArgumentException("货币单位不一致");
        }
        return new Money(this.amount.add(other.amount), this.currency);
    }
    // ... 省略构造器与getter
}

// 聚合根:订单 (封装了订单创建的核心业务规则)
public class Order {
    private OrderId id;
    private List<OrderItem> items;
    private Money totalAmount;
    private OrderStatus status;

    // 私有构造,通过工厂方法创建,确保创建出来的订单一定是合法的
    private Order(OrderId id, List<OrderItem> items) {
        this.id = id;
        this.items = items;
        this.status = OrderStatus.PENDING;
        // 业务规则:创建时自动计算总金额
        this.totalAmount = calculateTotalAmount(items);
    }

    // 工厂方法:创建订单
    public static Order create(List<OrderItem> items) {
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException("订单项不能为空");
        }
        // 业务规则:校验库存(这里假设订单项内部封装了库存校验逻辑)
        items.forEach(OrderItem::checkStock); 
        return new Order(new OrderId(UUID.randomUUID().toString()), items);
    }

    private Money calculateTotalAmount(List<OrderItem> items) {
        return items.stream()
                .map(OrderItem::getSubtotal)
                .reduce(new Money(BigDecimal.ZERO, "CNY"), Money::add);
    }
}

// 领域服务接口(定义在领域层)
public interface OrderRepository {
    void save(Order order);
}

2. 应用服务层(Application Layer)实现

这里体现了流程的编排。它不包含任何 if (amount > 100) 这样的业务判断,只是指挥领域对象去工作。

java 复制代码
@Service
public class OrderApplicationService {

    @Autowired
    private OrderRepository orderRepository; // 注入领域层定义的仓储接口

    // 事务控制通常放在应用服务层
    @Transactional
    public String createOrder(CreateOrderCommand command) {
        // 1. 参数转换与基础适配 (DTO -> 领域对象)
        List<OrderItem> items = command.getItems().stream()
                .map(item -> new OrderItem(item.getProductId(), item.getQuantity(), item.getPrice()))
                .collect(Collectors.toList());

        // 2. 委托领域层执行核心业务逻辑 (调用聚合根的工厂方法)
        // 此时,库存校验、金额计算等业务规则都在 Order.create 内部自动完成
        Order order = Order.create(items);

        // 3. 持久化 (调用仓储接口,实际实现由基础设施层提供)
        orderRepository.save(order);

        // 4. 发布领域事件 (通知其他系统,如积分服务、物流服务)
        // domainEventPublisher.publish(new OrderCreatedEvent(order.getId()));

        return order.getId().getValue();
    }
}

总结:

当你需要修改业务规则(比如"满100减20"或"新用户才能用券")时,你只需要去修改领域模型层OrderCoupon 实体;而当你需要调整业务流程(比如下单后增加一个短信通知步骤)时,你只需要修改应用服务层的编排逻辑。两者各司其职,保证了系统的可维护性和扩展性。

相关推荐
@卓越俊逸_角立杰出@2 小时前
深度拆解跨境支付系统架构:从资金流、账本系统到全球清算网络设计
网络·系统架构
@insist12320 小时前
系统架构设计师-软件质量属性战术与架构评估方法全解
架构·系统架构·软考·系统架构设计师·软件水平考试
@insist12320 小时前
系统架构设计师-五大经典软件架构风格详解与软考真题应用指南
架构·系统架构·软考·系统架构设计师·软件水平考试
雯宝1 天前
|____2.1 FreeRTOS 深度解析--链表
系统架构
RemainderTime1 天前
Spring Boot脚手架集成Sa-Token实现生产级RBAC权限管理
java·spring boot·后端·系统架构
@insist1232 天前
系统架构设计师-基于架构的软件开发方法(ABSD)核心原理
架构·系统架构·软考·系统架构设计师·软件水平考试
一切皆是因缘际会2 天前
底层重构与价值破壁人工智能产业变革
人工智能·安全·重构·系统架构
@insist1232 天前
系统架构设计师-软件架构核心概念与描述方法
系统架构·软件工程·软考·系统架构设计师·软件水平考试
郝学胜-神的一滴2 天前
Qt 高级开发 020:水平布局手写代码实战
开发语言·c++·qt·系统架构·软件构建·用户界面