DDD-領域物件與領域服務

方丈的寺院發表於2019-02-20

問題

  1. 什麼是領域物件
  2. 什麼是領域服務
  3. 領域物件的行為,與領域服務的行為區別

原因

為什麼把這麼小的點拿出來講,最開始在討論中領域物件與領域服務時,覺得行為放在service/entity中區別不大,只是一個放置位置的問題,並不影響到程式碼的抽象和複用,所以沒有實行。但是最近在推動產品進行DDD業務建模,發現這個問題非常重要,關係到程式碼是否清晰表達了業務,這個也是我們進行DDD的初衷。

定義

領域物件
聚合根,實體,值物件
領域的資料與行為,
資料和行為應該與業務產品上的行為關聯。領域物件通常是有狀態的,理想情況下,我們的領域物件行為應該和產品業務定義意義對映

幾個阻抗

  • 覺得行為放在領域服務還是領域物件中區別不大,只是一個放置位置的問題,並不影響到程式碼的抽象和複用
  • 領域物件中還是隻有屬性,和物件之間的轉換
  • 業務邏輯沒有與程式碼對映
  • manager(持久化操作)放在領域物件中需要進行一個轉換(ApplicationContext)或者其他方式
  • 我們的業務很單薄,放在領域物件中的內容後,領域服務就很單薄了。濫用了領域服務導致了領域物件的貧血
  • 領域物件的集合操作

觀點

首先需要對概念明確定義,因為DDD其實是做了一個問題的分治,所以必然會導致在某些情況下,會有單薄這個說法。就像垂直架構中dao/manager/service層區分一樣。在初期我們可以明確按照概念來放置程式碼,當大家達成共識,深刻理解了這些概念時,沒有其中一層也無所謂了。

舉個例子
eg. 一個bad case
三個模型:A,B,C,他們之間存在狀態變更流動。

整理出來的狀態變更圖
在這裡插入圖片描述
AService.updateXXStatus

AService.cancelBy

AService.changeStatus()

這些方法都在處理狀態,反應不了業務的情況

領域物件:

一般包含以下邏輯

  • 領域物件的限制

    // 如什麼樣的設計師是不存在的,對於領域外的內容不關心你是不是軟刪,還是硬刪

    public  void checkDesignerExist() throws BizzException {
          // 清退的也是不存在的
          if (this == null || this.getStatus().equals(DesignerStatusEnum.DELETE)) {
              throw DdgCoreResponseCode.convertBizzException(DdgCoreResponseCode.DESIGNER_NOT_EXIST);
          }
      }
    
  • 領域行為與事件
    // 如商品物件的刪除,以及事件的publish(不限於CRUD)
    // 抽獎業務中從獎池中選取獎品

      public RoulettePrize executeRoulette(final List<RoulettePrize> prizes,
                  final Integer dailyFreeRouletteCount,
                  final int rouletteCountToday, final UserDTO userDTO) throws BizzException {
              final List<RoulettePrize> validPrize =  prizes.stream().filter(p -> checkPrizeValid(p))
                      // 排除掉中獎概率不合法和概率為0的獎品
                      .filter(p -> ArithUtil.checkIntegerRange(p.getRate(), 0, Integer.MAX_VALUE))
                      // 校驗中獎次數限制
                      .filter(p -> checkPrizeLimit(p))
                      // 校驗vip獎品限制
                      .filter(p -> checkPrizeVip(p, userDTO))
                      .filter(p -> checkFreePrize(p,dailyFreeRouletteCount, rouletteCountToday)).collect(
                              Collectors.toList());
              final int totalRate = validPrize.stream().mapToInt(p -> p.getRate()).sum();
              // 排除無效獎品,計算有效獎品概率之和
              if (CollectionUtils.isEmpty(validPrize)) {
                  throw RouletteResponseCode.convertBizzException(RouletteResponseCode.PRIZE_EMPTY);
              }
              return chooseResultPrize(validPrize, totalRate == 0 ? 1 : totalRate);
          }
    
  • 狀態的流轉

不應該做的事

領域物件不應該與其他的模型有互動,如manager(資源層管理),不應該持久化資料
如何持久化不應該是領域物件關心的。

領域服務

  • 構造(複雜的)領域物件
    呼叫防腐層方法,做支撐域和通用域物件的轉換與組合

  • 與dao層打交道

  • 呼叫其他限界上下文的內容

  • 提供領域方法給其他限界上下文/應用程式呼叫

領域服務與領域物件的關係

領域服務通常是領域物件的呼叫方,是微服務架構下,領域物件對外提供的方式。

AService

   // 構建領域物件
    final List<AAggr> aggr = mAManager.listByUserIds(userVal);
    final AEntity entity = CollectionUtils.isEmpty(aggr) ? null : aggr.get(0)
            .getA();	
   // 呼叫領域物件方法
    entity.checkDesignerExist();
    entity.checkUpdate();

在這裡插入圖片描述

相關文章