公众号IT徐胖子原创本文,请勿转载
1 DDD
最近一段时间在学习领域驱动设计,同时也学习了COLA代码并进行了一些项目实践,COLA代码整洁优雅,但有一定学习成本和使用成本。最终一个工程思想还是要落地,我综合了一些DDD技术框架,删除了CQRS和事件总线模式,整理了一个简单实用易于落地的项目架构。
(1) demo-infrastructure
基础层。包含基础性功能,例如数据库访问功能,缓存访问功能,消息发送功能,还需要提供通用工具包
(2) demo-dependency
外部访问层。在这个模块中调用外部RPC服务,解析返回码和返回数据
(3) demo-domain
领域层。这个模块包含类似于三层架构的BO(Business Object),但不同的是使用充血模式进行定义,所以领域层本身也包含业务逻辑,不是简单进行属性声明
(4) demo-service
业务层。虽然领域层和业务层都包含业务,但是用途不同。业务层可以组合不同领域的业务,并且可以增加流控、监控、日志、权限控制切面,相较于领域层更为丰富
(5) demo-api
对外接口层。提供面向外部接口声明
(6) demo-controller
对外访问层。提供面向外部访问入口
2 三层架构与贫血模型
在使用上述框架之前我们一般使用三层架构进行业务开发:
Repository + Entity
Service + BO(Business Object)
Controller + VO(View Object)
复制
在三层架构业务开发中,大家经常使用基于贫血模型的开发模式。贫血模型是指业务逻辑全部放在service层,业务对象只包含数据不包含业务逻辑。我们来分析代码实例。
/**
* 账户业务对象
*
* @author 微信公众号「IT徐胖子」
*
*/
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 微信公众号「IT徐胖子」
*
*/
@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只有数据没有业务逻辑。整个代码风格偏向于面向过程,所以也有人把贫血模型称为反模式。
3 充血模型
在基于充血模型DDD开发模式中我们引入了Domain层。Domain层包含了业务对象BO,但并不是仅仅包含数据,这一层也包含业务逻辑,我们来分析代码实例。
/**
* 账户业务对象
*
* @author 微信公众号「IT徐胖子」
*
*/
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 微信公众号「IT徐胖子」
*
*/
@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 微信公众号「IT徐胖子」
*
*/
@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 微信公众号「IT徐胖子」
*
*/
@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 微信公众号「IT徐胖子」
*
*/
public interface BizValidator {
BizValidateResult validate();
}
/**
* 校验结果
*
* @author 微信公众号「IT徐胖子」
*
*/
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 微信公众号「IT徐胖子」
*
*/
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 微信公众号「IT徐胖子」
*
*/
@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";
}
}
复制
4 一些思考
有人可能会说充血模式只是把一些业务放在Domain层进行,没有什么特别之处。关于这个观点我有以下思考:
(1) 代码业务风格更加面向对象,而不是面向过程,整个逻辑也变得更加内聚
(2) 在设计原则中有一条开闭原则:面向扩展开放,面向修改关闭,我认为这是最重要的一条设计原则,很多设计模式如策略模式、模板方法模式都是基于这个原则设计的。充血模型BO中可以包含各种策略,可以使用策略模式管理策略
(3) Domain层不是取代Service层,而是一种补充增强,虽然领域层和业务层都包含业务但是用途不同。业务层可以组合不同领域的业务,并且可以增加流控、监控、日志、权限控制切面,相较于领域层更为丰富
(4) Lombok框架现在非常流行,使代码变得非常简洁。有一点需要注意,随意使用Lombok可能会破坏代码封装性。例如AccountBO对象不应该暴露setBalance方法,但由于各层对象需要属性拷贝必须暴露setBalance方法,这也是一种权衡策略
长按二维码关注本公众号精彩文章