暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

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

JAVA前线 2020-04-28
702

公众号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方法,这也是一种权衡策略


          长按二维码关注本公众号精彩文章

          文章转载自JAVA前线,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

          评论