广告
您当前的位置: 首页 >  技术 >  编程开发

领域战术设计:解密领域服务(Domain Service)无状态与多聚合协作

作者:CoderWang 时间:2026-07-09 阅读数:5人阅读

在落地领域驱动设计(DDD)的战术开发中,我们常常被引导去追求极致的“充血模型(Rich Domain Model)”,即提倡将所有的业务属性与校验逻辑全部收拢在聚合根(Aggregate Root)和实体(Entity)内部,避免产生贫血模型。

然而,当遇到一些跨聚合根的复杂业务逻辑时(例如:**“从账户 A 扣减 100 元并充值到账户 B 的资金转账操作”**),问题便随之而来。转账逻辑应该写在账户 A 还是账户 B 内部?写在任何一方都会引入对另一方的强耦合,直接打破了聚合根的事务独立性防线。

为了解决跨聚合业务逻辑的归属难题,DDD 战术建模引入了**领域服务(Domain Service)**。它是一个纯粹无状态的逻辑协调器,专门用来处理那些在概念上不属于任何单一实体或值对象的业务规则。

本文将系统拆解领域服务的定义三大判定规则、领域服务与应用服务的职责边界、以及 Java 多聚合转账自愈场景的代码设计规范。

一、 职责界定:领域服务 vs. 实体/值对象 vs. 应用服务

三者在状态维持、业务规则封装以及技术组件组装上具有明确的逻辑分工:

服务/组件类型是否有状态 (Stateful)是否包含核心领域逻辑是否涉及底层技术装配 (如事务/安全)典型开发场景
实体/值对象 (Entity/Value Object)**有状态**。由自身 ID 与各维度字段属性维持状态。**是**。仅封装高内聚、针对自身属性的计算与规则校验。否。纯粹的内存对象,无任何外部物理依赖。order.pay(price) 校验余额并改变订单状态。
领域服务 (Domain Service)**无状态**。不属于任何聚合,只负责操作其他实体。**是**。负责协调多个聚合的交互,计算跨域的核心公式。否。不包含数据库事务事务控制或权限验证。transferService.transfer(from, to, amount) 转账协调。
应用服务 (Application Service)**无状态**。一般作为外部网关与领域边界的物理防线。否。纯粹的工作流组装。**是**。负责声明式事务控制(@Transactional)、日志记录、安全认证。orderAppService.createOrder(dto) 流程组装。
---

二、 领域服务多聚合协调与事务隔离拓扑

当客户端调用“转账”服务时,领域服务作为业务内核,与写端应用服务、数据库事务的协作拓扑如下:

      [ 外部客户端请求 (POST /transfer) ]
                       │
                       ▼ 1. 拦截请求,解析为 DTO,开启本地事务 (Spring @Transactional)
      [ 转账应用服务 (TransferApplicationService) ]
                       │
                       ├─ 2. 从仓储加载两个独立的聚合根 (fromAccount, toAccount)
                       │
                       ▼ 3. 委派跨聚合核心业务逻辑
      【 资金转账领域服务 (MoneyTransferDomainService) 】 <-- 纯无状态逻辑引擎
                       │
                       ├─ 4. 调用源账户执行扣款 (fromAccount.withdraw(amount))
                       │    (内部执行:校验余额是否充足)
                       │
                       ├─ 5. 调用目标账户执行存款 (toAccount.deposit(amount))
                       │    (内部执行:校验账户状态是否正常)
                       │
                       ▼ 6. 验证自愈成功,返回处理结果
      [ 保存两个聚合根,物理提交事务 (Commit) ]
---

三、 代码实战:在 Java 中编写严格无状态的领域转账服务

以下代码展示了如何区分应用服务与领域服务,以及如何编写一个纯无状态的领域转账服务协调两个账户聚合根:

1. 领域层:设计高内聚 Account 聚合根(Domain Layer)

package com.company.sales.domain.account;

public class Account {

    private final String accountId;
    private double balance;
    private boolean active;

    public Account(String accountId, double initialBalance) {
        this.accountId = accountId;
        this.balance = initialBalance;
        this.active = true;
    }

    /**
     * 业务方法:扣减款项
     */
    public void withdraw(double amount) {
        if (!active) {
            throw new IllegalStateException("账户已冻结,无法出账!");
        }
        if (amount <= 0) {
            throw new IllegalArgumentException("扣减金额必须大于 0!");
        }
        if (balance < amount) {
            throw new IllegalArgumentException("账户余额不足,转账失败!");
        }
        this.balance -= amount;
    }

    /**
     * 业务方法:存入款项
     */
    public void deposit(double amount) {
        if (!active) {
            throw new IllegalStateException("账户已冻结,无法入账!");
        }
        if (amount <= 0) {
            throw new IllegalArgumentException("存入金额必须大于 0!");
        }
        this.balance += amount;
    }

    public String getAccountId() { return accountId; }
    public double getBalance() { return balance; }
}

2. 领域层:纯无状态的领域转账服务(Domain Service)

package com.company.sales.domain.service;

import com.company.sales.domain.account.Account;

/**
 * 核心规约一:用特定后缀 DomainService 命名,区别于普通的应用服务
 * 核心规约二:此类不含任何类成员变量属性,100% 保持“无状态(Stateless)”,可并发安全调用
 */
public class MoneyTransferDomainService {

    /**
     * 执行跨聚合转账逻辑协调
     */
    public void transfer(Account fromAccount, Account toAccount, double amount) {
        // 核心规约三:将转账动作的核心业务规则封装在此,实现对聚合根的指挥协同
        fromAccount.withdraw(amount); // 源账户扣款
        toAccount.deposit(amount);    // 目标账户存款
    }
}

3. 应用层:组装工作流并托管事务的应用服务(Application Service)

package com.company.sales.app;

import com.company.sales.domain.account.Account;
import com.company.sales.domain.service.MoneyTransferDomainService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class TransferApplicationService {

    // 依赖注入无状态领域服务和仓储
    private final MoneyTransferDomainService transferDomainService = new MoneyTransferDomainService();
    // private final AccountRepository accountRepository;

    /**
     * 核心规约四:应用服务层负责声明式事务回滚与物理持久化,领域服务决不能染指事务!
     */
    @Transactional
    public void executeTransfer(String fromId, String toId, double amount) {
        // 1. 从持久化层加载两个聚合根
        // Account fromAccount = accountRepository.findById(fromId);
        // Account toAccount = accountRepository.findById(toId);
        
        Account fromAccount = new Account(fromId, 500.0);
        Account toAccount = new Account(toId, 100.0);

        // 2. 调用无状态领域服务执行核心交互,不产生本地事务泄漏
        transferDomainService.transfer(fromAccount, toAccount, amount);

        // 3. 仓储层保存更新(持久化落盘)
        // accountRepository.save(fromAccount);
        // accountRepository.save(toAccount);
        System.out.println(String.format("[App Service] 转账执行成功!源账户余额: %.2f, 目标账户余额: %.2f", 
            fromAccount.getBalance(), toAccount.getBalance()));
    }
}
```
---

四、 总结

领域服务(Domain Service)的无状态特质与多聚合协作设计,是保持 DDD 战术模型“高纯净度、强解耦”的关键技术阀门。

它通过**将跨聚合的交互逻辑从实体剥离到独立的无状态服务中,巧妙捍卫了聚合根自身的强内聚和低耦合边界,避开了贫血模型导致的业务流失;配合应用服务声明式事务外包设计,彻底理清了业务计算与底层持久化的界限**。掌握这三层职责界定原则、领域服务无状态开发与应用服务组装事务代码,是搭建高性能云原生服务底座、防范业务逻辑失控蔓延 of 必备看家本领!

本站所有文章、数据、图片均来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。

评论交流 (0)

正在加载评论...
头像

CoderWang

当你还撑不起你的梦想时,就要去奋斗。如果缘分安排我们相遇,请不要让她擦肩和过。我们一起奋斗!

微信