导入
实现领域模型
学习如何实现领域模型。
简介
让我们来看看如何在实际代码中实现之前讨论的架构。
领域代码建模
由于在我们的架构中,领域代码与应用层、Web 层和持久化层之间耦合度非常低,因此我们可以自由地按照自己的方式来设计领域代码。我们可以采用 DDD:领域驱动设计,可以实现富领域模型或贫血领域模型,也可以发明自己独特的方式。
六边形架构
本章描述了一种带有观点倾向的方式来实现用例,该方式基于我们在前几章介绍过的六边形架构风格。
作为一个以领域为中心的架构,我们将首先从一个领域实体开始,然后围绕它构建一个用例。
我们希望实现“从一个账户向另一个账户转账”的用例。在面向对象的设计中,一种建模方式是创建一个 Account 实体,使其支持取款和存款操作,从而可以从源账户扣款,并向目标账户入账。
创建 Account 实体
1package buckpal.account.domain; 2 3public class Account { 4 5 private AccountId id; 6 private Money baselineBalance; 7 private ActivityWindow activityWindow; 8 9 // 构造函数和 getter 方法省略 10 11 public Money calculateBalance() { 12 return Money.add( 13 this.baselineBalance, 14 this.activityWindow.calculateBalance(this.id)); 15 } 16 17 public boolean withdraw(Money money, AccountId targetAccountId) { 18 19 if (!mayWithdraw(money)) { 20 return false; 21 } 22 23 Activity withdrawal = new Activity( 24 this.id, 25 this.id, 26 targetAccountId, 27 LocalDateTime.now(), 28 money); 29 this.activityWindow.addActivity(withdrawal); 30 return true; 31 } 32 33 private boolean mayWithdraw(Money money) { 34 return Money.add( 35 this.calculateBalance(), 36 money.negate()) 37 .isPositive(); 38 } 39 40 public boolean deposit(Money money, AccountId sourceAccountId) { 41 Activity deposit = new Activity( 42 this.id, 43 sourceAccountId, 44 this.id, 45 LocalDateTime.now(), 46 money); 47 this.activityWindow.addActivity(deposit); 48 return true; 49 } 50 51}
说明
Account 实体表示某个实际账户的当前快照。每次账户的取款或存款操作都会被记录为一个 Activity 实体。由于将某个账户的所有活动历史全部加载到内存中并不现实,因此 Account 实体仅维护最近几天或几周的活动窗口,这部分数据由 ActivityWindow 值对象封装。
为了仍能准确计算当前账户余额,Account 实体还包含一个 baselineBalance 属性,它表示在活动窗口起始时间点之前的账户余额。最终的总余额即为基准余额加上活动窗口内所有交易活动所产生的余额变动之和。
在此模型下,向账户取款或存款只需向活动窗口添加一条新的活动记录即可,这正是 withdraw() 和 deposit() 方法所做的事情。在执行取款前,我们会检查一项业务规则:账户不可透支。
现在我们已经有了一个支持取款和存款的 Account 实体,接下来就可以向外扩展,围绕它构建具体的用例逻辑。