欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,欢迎大家加我微信「java_front」一起交流学习
最近一段时间在学习领域驱动设计,同时也学习了COLA代码并进行了一些项目实践,COLA代码整洁优雅,但有一定学习成本和使用成本。最终一个工程思想还是要落地,我综合了一些DDD技术框架,删除了CQRS和事件总线模式,整理了一个简单实用易于落地的项目架构。
基础层。包含基础性功能,例如数据库访问功能,缓存访问功能,消息发送功能,还需要提供通用工具包
外部访问层。在这个模块中调用外部RPC服务,解析返回码和返回数据
领域层。这个模块包含类似于三层架构的BO(Business Object),但不同的是使用充血模式进行定义,所以领域层本身也包含业务逻辑,不是简单进行属性声明
业务层。虽然领域层和业务层都包含业务,但是用途不同。业务层可以组合不同领域的业务,并且可以增加流控、监控、日志、权限控制切面,相较于领域层更为丰富
对外接口层。提供面向外部接口声明
对外访问层。提供面向外部访问入口
在使用上述框架之前我们一般使用三层架构进行业务开发:
Repository + Entity Service + BO(Business Object) Controller + VO(View Object)在三层架构业务开发中,大家经常使用基于贫血模型的开发模式。贫血模型是指业务逻辑全部放在service层,业务对象只包含数据不包含业务逻辑。我们来分析代码实例。
/** * 账户业务对象 * * @author 微信公众号「JAVA前线」 * */ public class AccountBO { /** * 账户ID */ private String accountId; /** * 账户余额 */ private Long balance; /** * 是否冻结 */ private boolean isFrozen; public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } public Long getBalance() { return balance; } public void setBalance(Long balance) { this.balance = balance; } public boolean isFrozen() { return isFrozen; } public void setFrozen(boolean isFrozen) { this.isFrozen = isFrozen; } } /** * 转账业务服务实现 * * @author 微信公众号「JAVA前线」 * */ @Service public class TransferServiceImpl implements TransferService { @Autowired private AccountService accountService; @Override public boolean transfer(String fromAccountId, String toAccountId, Long amount) { AccountBO fromAccount = accountService.getAccountById(fromAccountId); AccountBO toAccount = accountService.getAccountById(toAccountId); /** 检查转出账户 **/ if (fromAccount.isFrozen()) { throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN); } if (fromAccount.getBalance() < amount) { throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE); } fromAccount.setBalance(fromAccount.getBalance() - amount); /** 检查转入账户 **/ if (toAccount.isFrozen()) { throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN); } toAccount.setBalance(toAccount.getBalance() + amount); /** 更新数据库 **/ accountService.updateAccount(fromAccount); accountService.updateAccount(toAccount); return Boolean.TRUE; } }TransferServiceImpl实现类中就是贫血模型开发方式,AccountBO只有数据没有业务逻辑。整个代码风格偏向于面向过程,所以也有人把贫血模型称为反模式。
在基于充血模型DDD开发模式中我们引入了Domain层。Domain层包含了业务对象BO,但并不是仅仅包含数据,这一层也包含业务逻辑,我们来分析代码实例。
/** * 账户业务对象 * * @author 微信公众号「JAVA前线」 * */ public class AccountBO { /** * 账户ID */ private String accountId; /** * 账户余额 */ private Long balance; /** * 是否冻结 */ private boolean isFrozen; /** * 出借策略 */ private DebitPolicy debitPolicy; /** * 入账策略 */ private CreditPolicy creditPolicy; /** * 出借方法 * * @param amount 金额 */ public void debit(Long amount) { debitPolicy.preDebit(this, amount); this.balance -= amount; debitPolicy.afterDebit(this, amount); } /** * 转入方法 * * @param amount 金额 */ public void credit(Long amount) { creditPolicy.preCredit(this, amount); this.balance += amount; creditPolicy.afterCredit(this, amount); } public boolean isFrozen() { return isFrozen; } public void setFrozen(boolean isFrozen) { this.isFrozen = isFrozen; } public String getAccountId() { return accountId; } public void setAccountId(String accountId) { this.accountId = accountId; } public Long getBalance() { return balance; } /** * BO和DO转换必须加set方法这是一种权衡 */ public void setBalance(Long balance) { this.balance = balance; } public DebitPolicy getDebitPolicy() { return debitPolicy; } public void setDebitPolicy(DebitPolicy debitPolicy) { this.debitPolicy = debitPolicy; } public CreditPolicy getCreditPolicy() { return creditPolicy; } public void setCreditPolicy(CreditPolicy creditPolicy) { this.creditPolicy = creditPolicy; } } /** * 入账策略实现 * * @author 微信公众号「JAVA前线」 * */ @Service public class CreditPolicyImpl implements CreditPolicy { @Override public void preCredit(AccountBO account, Long amount) { if (account.isFrozen()) { throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN); } } @Override public void afterCredit(AccountBO account, Long amount) { System.out.println("afterCredit"); } } /** * 出借策略实现 * * @author 微信公众号「JAVA前线」 * */ @Service public class DebitPolicyImpl implements DebitPolicy { @Override public void preDebit(AccountBO account, Long amount) { if (account.isFrozen()) { throw new MyBizException(ErrorCodeBiz.ACCOUNT_FROZEN); } if (account.getBalance() < amount) { throw new MyBizException(ErrorCodeBiz.INSUFFICIENT_BALANCE); } } @Override public void afterDebit(AccountBO account, Long amount) { System.out.println("afterDebit"); } } /** * 转账业务服务实现 * * @author 微信公众号「JAVA前线」 * */ @Service public class TransferServiceImpl implements TransferService { @Resource private AccountService accountService; @Resource private CreditPolicy creditPolicy; @Resource private DebitPolicy debitPolicy; @Override public boolean transfer(String fromAccountId, String toAccountId, Long amount) { AccountBO fromAccount = accountService.getAccountById(fromAccountId); AccountBO toAccount = accountService.getAccountById(toAccountId); fromAccount.setDebitPolicy(debitPolicy); toAccount.setCreditPolicy(creditPolicy); fromAccount.debit(amount); toAccount.credit(amount); accountService.updateAccount(fromAccount); accountService.updateAccount(toAccount); return Boolean.TRUE; } }AccountBO包含了策略对象,策略对象可以实现业务逻辑,这样把业务逻辑实现在策略对象,减少service层面向过程的代码。我们再分析一个将校验逻辑内聚在Domain对象代码实例:
/** * 校验器 * * @author 微信公众号「JAVA前线」 * */ public interface BizValidator { BizValidateResult validate(); } /** * 校验结果 * * @author 微信公众号「JAVA前线」 * */ public class BizValidateResult { private boolean isSuccess; private String message; public BizValidateResult(boolean isSuccess, String message) { super(); this.isSuccess = isSuccess; this.message = message; } public boolean isSuccess() { return isSuccess; } public void setSuccess(boolean isSuccess) { this.isSuccess = isSuccess; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } } /** * 商品业务对象 * * @author 微信公众号「JAVA前线」 * */ public class GoodsBO implements BizValidator { private String goodsId; private String goodsName; private GoodsService goodsService; public String getGoodsId() { return goodsId; } public void setGoodsId(String goodsId) { this.goodsId = goodsId; } public String getGoodsName() { return goodsName; } public void setGoodsName(String goodsName) { this.goodsName = goodsName; } public GoodsService getGoodsService() { return goodsService; } public void setGoodsService(GoodsService goodsService) { this.goodsService = goodsService; } @Override public BizValidateResult validate() { if(StringUtils.isEmpty(goodsId)) { throw new MyBizException(ErrorCodeBiz.ILLEGAL_ARGUMENT); } if(StringUtils.isEmpty(goodsName)) { throw new MyBizException(ErrorCodeBiz.ILLEGAL_ARGUMENT); } Integer stock = goodsService.getGoodsStock(goodsId); if(stock <= 0) { throw new MyBizException(ErrorCodeBiz.NO_STOCK); } return new BizValidateResult(Boolean.TRUE, null); } } /** * 订单服务实现 * * @author 微信公众号「JAVA前线」 * */ @Service public class OrderServiceImpl implements OrderService { @Resource private GoodsService goodsService; @Override public String createOrder(GoodsBO goods) { if(null == goods) { throw new MyBizException(ErrorCodeBiz.ILLEGAL_ARGUMENT); } goods.setGoodsService(goodsService); goods.validate(); System.out.println("创建订单"); return "orderId_111"; } }有人可能会说充血模式只是把一些业务放在Domain层进行,没有什么特别之处。关于这个观点我有以下思考:
(1) 代码业务风格更加面向对象,而不是面向过程,整个逻辑也变得更加内聚
(2) 在设计原则中有一条开闭原则:面向扩展开放,面向修改关闭,我认为这是最重要的一条设计原则,很多设计模式如策略模式、模板方法模式都是基于这个原则设计的。充血模型BO中可以包含各种策略,可以使用策略模式管理策略
(3) Domain层不是取代Service层,而是一种补充增强,虽然领域层和业务层都包含业务但是用途不同。业务层可以组合不同领域的业务,并且可以增加流控、监控、日志、权限控制切面,相较于领域层更为丰富
(4) Lombok框架现在非常流行,使代码变得非常简洁。有一点需要注意,随意使用Lombok可能会破坏代码封装性。例如AccountBO对象不应该暴露setBalance方法,但由于各层对象需要属性拷贝必须暴露setBalance方法,这也是一种权衡策略
欢迎大家关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思维、职场分享、产品思考等等,欢迎大家加我微信「java_front」一起交流学习
