DDD项目架构与充血模型实例

欢迎你们关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思惟、职场分享、产品思考等等,同时欢迎你们加我微信「java_front」一块儿交流学习java


1 DDD

最近在学习领域驱动设计,同时也学习了COLA代码并进行了一些项目实践,COLA代码整洁优雅,但有必定学习成本和使用成本。最终一个工程思想仍是要落地,我综合了一些DDD技术框架,删除了CQRS和事件总线模式,整理了一个简单实用易于落地的项目架构。数据库

(1) demo-infrastructure

基础层。包含基础性功能,例如数据库访问功能,缓存访问功能,消息发送功能,还须要提供通用工具包设计模式

(2) demo-dependency

外部访问层。在这个模块中调用外部RPC服务,解析返回码和返回数据api

(3) demo-domain

领域层。这个模块包含相似于三层架构的BO(Business Object),但不一样的是使用充血模式进行定义,因此领域层自己也包含业务逻辑,不是简单进行属性声明缓存

(4) demo-service

业务层。虽然领域层和业务层都包含业务,可是用途不一样。业务层能够组合不一样领域的业务,而且能够增长流控、监控、日志、权限控制切面,相较于领域层更为丰富微信

(5) demo-api

对外接口层。提供面向外部接口声明markdown

(6) demo-controller

对外访问层。提供面向外部访问入口架构


2 三层架构与贫血模型

在使用上述框架以前咱们通常使用三层架构进行业务开发:框架

Repository + Entity
Service + BO(Business Object)
Controller + VO(View Object)
复制代码

在三层架构业务开发中,你们常常使用基于贫血模型的开发模式。贫血模型是指业务逻辑所有放在service层,业务对象只包含数据不包含业务逻辑。咱们来分析代码实例。dom

/** * 帐户业务对象 * * @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只有数据没有业务逻辑。整个代码风格偏向于面向过程,因此也有人把贫血模型称为反模式。


3 充血模型

3.1 实例一

在基于充血模型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层面向过程的代码,代码结构更加内聚。

3.2 实例二

咱们再分析一个将校验逻辑内聚充血模型实例。

/** * 校验器 * * @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";
    }
}
复制代码

4 一些思考

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

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

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

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

(4) Lombok框架如今很是流行,使代码变得很是简洁。有一点须要注意,随意使用Lombok可能会破坏代码封装性,例如AccountBO对象不该该暴露setBalance方法。但因为各层对象须要属性拷贝必须暴露setBalance方法,这也是一种权衡策略

欢迎你们关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思惟、职场分享、产品思考等等,同时欢迎你们加我微信「java_front」一块儿交流学习

相关文章
相关标签/搜索