领域驱动设计学习总结(一):关于银行转账的思考
由于项目需要,在办公室抱着领域驱动设计这本书啃了一星期。今天突发奇想想写个学习总结。于是乎就拿前段时间大伙儿都在讨论的银行转账问题来练练手,第一次接触领域驱动设计,有不妥的地方请大伙多多指教。
一、问题描述
实现银行账号汇款功能。
核心业务:将账号A的若干资金转到账号B上。
设转账金额为M(下同)
Amount:账号资金
二、问题分析
1.账号A:账号A按照资金转出规则处理M
2.账号B:账号B按照资金转入规则处理M
3.系统:提供转账功能
4.转账规则:不收手续费,资金转出M时,Amount减少M;资金转入M时,Amount增加M.
5.对比该问题与调漆程序的区别:调漆时,两种油漆混合是得到第三种油漆,原有两种油漆不变;账号转账后,A、B两个账号都没发生改变,Amount发生改变。
三、解决思路
建立初步模型:
这个图画的蛮难看的。
相应代码如下(下午贴的那段代码,有些人应该看过):
public class AccountEntity
{
public string AccountId { get; set; }
public Account CurrentAccount { get; set; }
public void TransferTo(AccountEntity targetAccount, decimal Amount)
{
IRule _rule = new SimpleAccountRule();
CurrentAccount.Update(_rule.GetPayRule(Amount));
targetAccount.CurrentAccount.Update(_rule.GetIncomeRule(Amount));
}
}
public class Account
{
public string AccountNO { get; set; }
public string PassWord { get; set; }
public decimal Amount{ get; set; }
public void Update(decimal Amount)
{
this.Amount += Amount;
}
}
public interface IRule
{
decimal GetIncomeRule(decimal Amount);
decimal GetPayRule(decimal Amount);
}
public class SimpleAccountRule : IRule
{
public decimal GetIncomeRule(decimal Amount) { return Amount; }
public decimal GetPayRule(decimal Amount)
{
return (-1) * Amount;
}
}
public interface ITransferService
{
void TransferCash(AccountEntity sourceAccount, AccountEntity targetAccount, decimal Amount);
}
public class TransferService : ITransferService
{
public void TransferCash(AccountEntity sourceAccount, AccountEntity targetAccount, decimal Amount)
{
sourceAccount.TransferTo(targetAccount, Amount);
}
}
话说这东西出来的时候还挺满意的,但是看久了就觉得有点别扭了。
为什么我把Account独立定义了?画蛇添足啊!!!
对于Account来说,系统需要直接对其进行访问,而不需要通过对其它对象的遍历来获取Account,因此Account本身就是一个根,应该定义成Entity而不是Value Object。而对于多个Entity的操作,不应凡在Entity内部进行。于是乎对AccountEntity进行修改并引入AccountRepository。结果如下:
此时,AccountEntity的代码变为:
public class AccountEntity
{
public string AccountId { get; set; }
public string AccountNO { get; set; }
public string PassWord { get; set; }
public decimal Amount { get; set; }
public void Update(decimal Amount)
{
this.Amount += Amount;
}
}
public interface IAccountRepository
{
void TransferCash(AccountEntity sourceAccount, AccountEntity targetAccount, decimal Amount);
}
public class AccountRepository:IAccountRepository
{
public void TransferCash(AccountEntity sourceAccount, AccountEntity targetAccount, decimal Amount)
{
IRule _rule=new SimpleAccountRule();
sourceAccount.Update(_rule.GetPayRule(Amount));
targetAccount.Update(_rule.GetIncomeRule(Amount));
}
}
TransferService代码如下:
public interface ITransferService
{
void TransferCash(AccountEntity sourceAccount, AccountEntity targetAccount, decimal account);
}
public class TransferService : ITransferService
{
public void TransferCash(AccountEntity sourceAccount, AccountEntity targetAccount, decimal Amount)
{
IAccountRepository _repository = new AccountRepository();
_repository.TransferCash(sourceAccount, targetAccount, Amount);
}
}
通过重构后,引入的Repository使得AccountEntity除了自身的Update事件外,不需要再去关心任何的外部变换。
六、最后的满足
最后,我们来关心下银行卡透支问题,假设账号A和账号B都不允许透支,因此,在转账前,必须对Amout与M进行比对。
引入声明类Specification,Specification代码如下
public interface ISpecification
{
bool IsSatisfy(AccountEntity account,decimal amount);
}
public interface AccountSpecification:ISpecification
{
public bool IsSatisfy(AccountEntity account,decimal amount)
{
return account.Amount>=amount;
}
}
此时,Repository的代码添加声明:
public void TransferCash(AccountEntity sourceAccount, AccountEntity targetAccount, decimal Amount)
{
ISpecification _specification=new AccountSpecification();
if(_specification.IsSatisfy(sourceAccount,Amount))
{
IRule _rule=new SimpleAccountRule();
sourceAccount.Update(_rule.GetPayRule(Amount));
targetAccount.Update(_rule.GetIncomeRule(Amount));
}
}
end.