DDD充血模型代码实例

1 贫血模型

我们一般使用三层架构进行业务开发:

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只有数据没有业务逻辑。整个代码风格偏向于面向过程,所以也有人把贫血模型称为反模式。

2 充血模型

在基于充血模型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层面向过程的代码。


3 一些思考

有朋友可能会说,充血模式就是把一些业务放在Domain层进行,没有什么特别之处。关于这个观点我有这些思考:

(1) 代码业务风格更加面向对象,而不是面向过程,整个逻辑也变得更加内聚明细。

(2) 在设计原则中有一条开闭原则:面向扩展开放,面向修改关闭,我认为这是最重要的一条设计原则,很多设计模式如策略模式、模板方法模式都是基于这个原则设计的。充血模型BO中可以包含各种策略,可以使用策略模式管理策略。

(3) Domain层不是为了取代Service层,而是一种补充和增强,虽然领域层和业务层都包含业务,但是用途不同。业务层可以组合不同领域的业务,并且可以增加流控、监控、日志、权限控制切面,相较于领域层更为丰富。

现在Lombok框架非常流行,使代码变得非常简洁。有一点需要注意,随意使用Lombok可能会破坏代码封装性,例如AccountBO对象不应该暴露setBalance方法,但是由于各层对象需要属性拷贝必须暴露setBalance方法,这也是一种权衡策略。

发布了535 篇原创文章 · 获赞 1162 · 访问量 450万+

猜你喜欢

转载自blog.csdn.net/woshixuye/article/details/104209013
今日推荐