一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天。
1.分层架构
现代应用架构通常采取分层的方式来将应用中的几个主要的关注点分离开来。如图所示由表示层、领域逻辑层和数据层组成的三层架构是最常见。
《领域驱动设计》提到了类似的架构,如下图所示,该架构包含用户界面层、应用层、领域层和基础设施层。
Java 后台服务通常按照分层架构分为controller、service、DAL(数据访问层,如mybatis)。Controller 可以认为是表示层的一部分,是表示层前端请求的入口,负责解析请求,调用请求的服务接口,渲染响应内容(对于动静分离的项目,Java后台服务通常只返回数据,由前端代码渲染响应内容;而对于非动静分离的项目,Java 后台还负责渲染响应内容)。
本文关注的是领域层,到底什么是领域层,什么是领域逻辑。
2.领域逻辑(业务逻辑)
Service 是我们的领域层吗?Service 中的代码都是领域逻辑吗? 我们通常会在service 中查询数据库获取相关的数据、做很多判断、修改数据、将修改保存到数据库、更新缓存、通过消息队列发布变更事件等。这些都是领域逻辑吗?
《领域驱动设计》以及其他一些分析设计类的书都强调领域逻辑的重要性,应该将领域逻辑与其他逻辑分开。但是通常都没有做出明确的定义什么是领域逻辑。
这里我尝试给个判断原则:如果代码逻辑做出了有业务意义的决策,我们就可以说这个代码是领域逻辑。其他部分代码为这个决策判断逻辑提供输入参数、将决策结果持久化、将决策结果通知给其他感兴趣的模块。
我们来看个例子,代码如下:
public void withdraw(long accountId, long amount) {
Account account = repository.findAccountById(accountId);
if(account == null){
notifyClient("account does not exist!");
return ;
}
if (!account.canWithdraw(amount)) {
notifyClient("balance is not enough!");
return ;
}
long commission = account.caluculateCommission(amount);
paymentGateway.chargePayment(commission);
account.withdraw(amount);
repository.save(account);
notifyClient("You have taken " + amount);
}
这个例子描述的是在银行柜台或者ATM 机上取款的业务。在用户取款的时候,后台 service 的逻辑,service 会判断账户是否存在、余额是否足够、并针对每笔取款会收取一定比例的手续费。
我们可以仔细看看这段 service 代码,看看这里面是不是都是领域逻辑。正如方法名暗示,这是取款的领域逻辑。对于取款来说,有意义的业务领域决策是判断有两个,一个是判断否具备取款条件;二是做出取款决策。这两个逻辑分别封装在了账户类的canWithdraw 和 caluculateCommission方法中了。而 service 本身的这些代码是将这两个与取款相关的领域逻辑编排在一起,并加上了支持性的代码逻辑(获取取款的账户对象、更新账户对象、调用支付模块支付手续费、以及知客户端)。
所以说service.withdraw 方法并不是领域逻辑,领域逻辑封装在了领域对象 Account 中了。通过将领域逻辑封装在领域对象中。我们实现了领域逻辑与编排逻辑分离,从而实现了高内聚低耦合。
3.领域逻辑分离的优点
经过这样的分离,我们可以分别聚焦于各自的逻辑,而不是将所有的逻辑混杂在一起。另外我们可以更加容易的对我们的代码逻辑进行单元测试。比如对 Account 方法做单元测试,将变得非常容易,无需考虑数据库、通知客户端、扣除手续费等等。