六边形架构中:领域实体与端口的领域函数划分规则咨询
领域实体与Ports间领域专属函数的划分规则
核心原则
领域实体是领域逻辑的核心载体,负责封装自身的属性和固有行为;Ports是领域层与外部(应用层、基础设施层)交互的边界接口,处理需要跨边界协调或依赖外部资源的逻辑。两者的划分可以从以下几个维度判断:
1. 函数是否属于实体的"固有行为"
如果函数的逻辑完全依赖实体自身的属性,是实体作为领域对象"天生该会"的能力,就应该放在实体里:
- 比如
calculateBalance():银行账户本身就需要知道如何根据自身的起始余额、交易记录计算当前余额,这是它的固有行为,属于实体范畴。 - 再比如账户的
lock()/unlock():控制自身状态的操作,也属于实体的固有行为。
反之,如果逻辑需要协调多个实体、或者依赖外部上下文,就该放在Ports中。
2. 区分创建逻辑的不同场景
createAccount这类创建函数的归属分两种情况:
- 纯初始化逻辑:如果只是根据
account id、starting balance等参数创建实体实例,同时做基础的领域校验(比如起始余额不能为负数),这部分属于实体的构造函数或静态方法,放在实体里。 - 带外部交互的创建逻辑:如果创建账户需要联动外部资源(比如生成全局唯一ID、保存到数据库、发送创建通知),这部分逻辑要放在Ports的应用服务或领域服务接口中——由Ports负责协调实体初始化和外部交互的流程。
3. 是否依赖外部资源/服务
判断函数是否需要调用外部依赖:
- 若函数只依赖实体自身数据,无外部依赖(比如
calculateBalance只用到自身的交易列表和起始余额),放在实体里。 - 若函数需要依赖外部服务(比如计算外币账户余额需要获取实时汇率),则把依赖外部的逻辑抽离到Ports的被驱动端口(比如汇率服务端口),实体只提供基础计算逻辑,由Ports传入外部获取的数据。
4. 结合六边形架构的Port分类
六边形架构的Ports分两类,对应不同的逻辑边界:
- 驱动端口(Driving Ports):作为外部触发领域逻辑的入口(比如应用服务接口),会调用实体的方法完成核心逻辑,再处理外部响应。
- 被驱动端口(Driven Ports):定义领域层依赖的外部服务接口(比如数据库存储、第三方API),实体不会直接依赖这些端口,而是由Ports层的实现来协调。
实体的行为始终是纯领域逻辑,不依赖任何Port接口;Ports则作为"桥梁",连接实体与外部世界。
举个实际对比例子
实体中的方法:
public class BankAccount { private String accountId; private BigDecimal startingBalance; private List<Transaction> transactions; // 纯初始化构造,带领域校验 public BankAccount(String accountId, BigDecimal startingBalance) { if (startingBalance.compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("起始余额不能为负"); } this.accountId = accountId; this.startingBalance = startingBalance; this.transactions = new ArrayList<>(); } // 固有行为:计算余额 public BigDecimal calculateBalance() { return transactions.stream() .map(Transaction::getAmount) .reduce(startingBalance, BigDecimal::add); } }Ports中的应用服务:
// 驱动端口接口 public interface AccountServicePort { BankAccount createAccount(String accountId, BigDecimal startingBalance); } // 驱动端口实现 public class AccountServiceImpl implements AccountServicePort { private final AccountRepositoryPort accountRepository; // 被驱动端口 @Override public BankAccount createAccount(String accountId, BigDecimal startingBalance) { BankAccount account = new BankAccount(accountId, startingBalance); accountRepository.save(account); // 调用外部存储 // 发送账户创建事件等外部交互逻辑 return account; } }
内容的提问来源于stack exchange,提问作者Vinn
相关产品推荐
相关产品推荐

