1. 程式人生 > >ddd的戰術篇: aggregate的設計策略

ddd的戰術篇: aggregate的設計策略

上一篇文章講了repository的實現。結合前面的文章基本把ddd戰術篇中的登場人物都介紹了一遍。依次是如下幾個角色。
首先是位於domain層的domain objects
- aggregate
- entity
- value object
- domain service
- repository
- specification
- factory
另外有還有兩種service。application service, infrastructure service.
當我們分析業務,實際建模時,最重要的當然是aggregate, entity與value object。
我們會將業務邏輯儘量寫進entity與value object。
而aggregate還有確保資料完整性的責任。entity,value object也最終隸屬於某個aggregate。(value object如果不需要永久化的話,那它可以獨立於aggregate)
因此aggregate的設計是十分重要的,這篇文章就來談談aggregate的設計策略。

儘量設計小aggregate?

什麼是小aggregate?
那我們先看看什麼是大的吧~
大aggregate
如果所示,root entity直接引用了很多的entity。
我們用例子來說明一下。假設我們對處理一個教室物品管理的問題。教室裡有椅子,桌子和黑板這些物品,為了解釋資料完整性的問題,假設一個教室裡不能有超過50樣物品。另一個需求是假設這些物品需要維護,要記錄各個物品是在何時維護的。
大概是這麼一個關係
classroom aggregate
比較直觀的方案當然是把教室當作一個aggregate,裡邊有它的物品。
首先是教室類,他是一個entity

public class Classroom {
  private
ClassroomId classroomId; private String classRoomNo; // eg: 402 private List<Item> items; public Item add(Item item) throws ItemLimitExceededException { if(items.size() == 50) { throw new ItemLimitExceededException(); } item.add(item); } public Item findItem(ItemId itemId){ items.toStream().filter(item -> item.getItemId() == itemId).findFirst().getOrElse( null
); } }

通過在add()方法中增加檢驗來保證資料完整性。
物品類(椅子,桌子,黑板什麼的)

@Getter
public class Item {
  private ItemId itemId;
  private ItemType itemType;
  private Date maintenanceDate;
  public void maintain(){
    maintenanceDate = DateUtils.now(); // 更新檢測日
  }
}

public enum ItemType {
  Chair, Desk, Blackboard
}

需不需要具體的Desk,Chair,Blackboard類還是隻要Item一個類就夠了是可以討論的問題,不過現在我們關注的不是這個,而是Classroom這個aggregate。當我們一這個方式實現時,我們獲取Classroom的引用時,必然會把它所包含的物品一起獲取。
想象一下ClassroomRepository的實現,獲得某一個id的Classroom。不僅要讀取classroom,我們不得不也把所有的物品items也一起讀取。這個是個比較低效的做法的。
另外,按照這樣的做法,如果你對某一個教室進行更新時,還會更新它所包含的物品。比如當你需要更改教室的名字,明明是和椅子,桌子無關的處理,但也必須去更新他們。這樣的更新範圍較大,當這個系統需要管理的東西變得十分龐大,併發處理很多時,容易導致鎖問題的出現,造成更新的失敗。(當然現實可能是這個系統永遠不會變得那麼大,難道這麼一個管理系統會指望同時線上使用者數億?我承認這個例子是在是太不合適勒orz,不過意思能傳達就好了…早知道該用論壇什麼的做例子!)
此外,因為ddd的aggregate思想的一些限制,位於某一個aggregate內的entity,你必須通過它的root entity才能獲取的。在這個例子中即你要獲得一張Chair的引用,你必須先獲得它所在Classroom的引用才行。Chair不是root aggregate,它沒有repository,所以也不存在跳過Classroom的作弊方式。那這會有什麼問題呢。假設我們需要維修了一張Chair。記錄他的維護時間。

 chair.maintain();

大概會是相面這個樣子

  Classroom classroom = classroomRepository.findOne(new ClassroomSpecificationById(classroomId);
  Item chair = classroom.findItem(chairId);
  chair.maintain();

為了呼叫一張Item椅子的方法,我們必須把Classroom讀取出,連帶一群我們根本不在意的Desk,Chair。這樣的做法超級低效,在高併發情況下,會有更新失敗就更容易發生(這樣使用者體驗會很差哦!如果你這麼幹,你就等著和產品經理或者設計師單挑吧!或者你是被群毆的那位!)
那如果不把所有object都扔進一個aggregate,怎麼辦。那就拆分成好幾個aggregate。把物品Item也作為aggregate。
classroom

@Getter
public class Item {
  private ItemId itemId;
  private ItemType itemType;
  private Date maintenanceDate;
  public void maintain(){
    maintenanceDate = DateUtils.now(); // 更新檢測日
  }
}

其實Item本身的實現沒有什麼變化啦~
接著是教室類

public class Classroom {
  private ClassroomId classroomId;
  private String classRoomNo; // eg: 402
  private List<ItemId> itemIds;

  public Item add(ItemId itemId) throws ItemLimitExceededException {
    if(itemIds.size() == 50) {
      throw new ItemLimitExceededException();
    }
    itemIds.add(itemId);
  }
}

aggregate之間的引用,在ddd中是通過id引用的。例子中Classroom這個aggregate是持有的Item的root aggregate的id(好繞口)來表示Classroom與Item的包含關係的。
按照這樣的設計,Item可以有自己的repository,你可以不通過Classroom對某個Item進行操作。

  Item item = itemRepository.find(new ItemSpecification(itemId));
  item.maintain();

當然你也必須放棄一個功能,便是從Classroom這個aggregate中直接獲取某個Item引用的功能。findItem()這個方法就實現不了啦。
* 我見過一些裡類似於在entity 中放入repository的做法。比如像下面這樣。

public class Classroom {
  private ItemRespository itemRepository;

  public Item findItem(ItemId itemId) {
    return itemRepository.findOne(new ItemSpecificationById(itemId));
  }
}

個人不是很推薦這種做法。因為entity依賴於外部的repository。另外repository是無狀態的,和entity不太般配~
歸納一下
大aggregate的缺點。
1. 從aggregate的角度來說,每次獲得一個aggregate的引用會讀取過多的資料
2. 從aggregate的子entity的角度來說,所有的操作都必須經過root entity,如果對子entity的操作可能影響資料完整性,這種方式是可行的。反之,這樣的方式便是很笨拙的。
3. 在高併發情況下,容易出現鎖問題。
大aggregate的優點
1. 更好的維護資料完整性

大aggregate的用處

前面總結了大aggregate在維護資料完整性上是有優勢的。但上面例子中的資料完整性即Classroom最多有50個物品,我們可以用小aggregate的方式來實現,所以我們傾向使用小aggregate。
如果我們的系統需求是不一樣的,比如我們要記錄教室中的物品位置,同時要保證相同位置上不能有兩個物品(資料完整性!)。那麼我們的模型就會發生變化。

@Getter
public class Item {
  private ItemId itemId;
  private ItemType itemType;
  private Position position;
  private maintenanceDate;
  public void maintain(){
    maintenanceDate = DateUtils.now(); // 更新檢測日
  }
}
@Value
public class Position {
  private Integer x;
  private Integer y;
}

當我們要向教室裡加物品時,小aggregate的設計就滿足不了需求了,於是我們可能不得不啟用大aggregate的設計。

public class Classroom {
  private ClassroomId classroomId;
  private String classRoomNo; // eg: 402
  private List<Item> items;

  public Item add(Item item) throws ItemLimitExceededException, PositionOccupiedException {
    if(items.size() == 50) {
      throw new ItemLimitExceededException();
    }
    if(isOccupied(item.getPosition())) {
      throw new PositionOccupiedException();
    }
    item.add(item);
  }

  private boolean isOccupied(Position position) {
    return items.toStream().map(Item::getPosition)
      .anyMatch(p -> p.equals(position));
  }

  public Item findItem(ItemId itemId){
  items.toStream().filter(item -> item.getItemId() == itemId).findFirst().getOrElse( null );
  }
}

總結

這次我們講了一下aggregate的設計策略。討論了大aggregate的優與劣。這部分的理論主要參考《實踐ddd》,作者是提倡使用小aggregate的。個人認為,選擇哪個策略歸根到底會是兩個問題。
1. 權衡資料完整性和抗併發的問題
2. aggregate的非root子entity是否擁有相當多的獨立業務邏輯
根據上邊的原則可以幫助我們決定aggregate的設計。

相關推薦

ddd戰術: aggregate設計策略

上一篇文章講了repository的實現。結合前面的文章基本把ddd戰術篇中的登場人物都介紹了一遍。依次是如下幾個角色。 首先是位於domain層的domain objects - aggregate - entity - value object

ddd戰術: application service, domain service, infrastructure service

之前的一篇文章談到了貧血模型,而ddd是提倡充血模型的,即儘量把邏輯寫在domain object中,而不是寫一大堆的service類,對資料類進行操作。那麼為什麼ddd裡會有service類呢?這篇文章會對service進行說明。 ddd中的servic

ddd戰術: domain object之一

首先ddd的戰術這個講法是不太好的。ddd書中說的是戰術性建模(tactical modeling)。其意思是在戰術層面的建模,那當然有戰略層面的建模啦。以後會專門講。 domain object是ddd區別於其他建模/設計方法的一個部分。他定義了概念幫助我們去建立mo

loadrunner提高-場景設計實踐

start ref 由於 www 對話 彈出 場景 功能 fff 集合點設置 一、為什麽要進行集合點設置?   因為在測試過程中,並不能保證所有的Vuser都在同一時刻進行操作,這樣就達不到並發

互聯網產品消息推送設計策略(轉)

做出 可能 依然 應用 存在 第一次 時間段 時間 保持 在移動互聯時代,消息推送越來越受到各個APP的重視,本文就以互金產品為例闡述消息推送的幾個類別以及應用的場景方式、運營策略,希望對你有益。 在之前一文中,筆者概括性的介紹了通知功能是互金理財平臺的一個基礎但重要

DDD】領域驅動設計實踐 —— 架構風格及架構實例

讀取 bili 邏輯 stat orcal ransac 應用服務 業務場景 解讀 概述 DDD為復雜軟件的設計提供了指導思想,其將易發生變化的業務核心域放置在限定上下文中,在確保核心域一致性和內聚性的基礎上,DDD可以被多種語言和多種技術框架實現,具體的框架實現需要根據

DDD】領域驅動設計實踐 —— 限界上下文識別

團隊協作 協作 tin 組織 領域 ges 承擔 產品 進行 本文從戰略層面街上DDD中關於限界上下文的相關知識,並以ECO系統為例子,介紹如何識別上下文。限界上下文(Bounded Context)定義了每個模型的應用範圍,在每個Bounded Context中確保領域模

優秀測試用例的設計策略

原則 關註 前端測試 存儲 正常的 等價 lis pan 了解 測試工作最為基礎核心的內容就是設計測試用例,什麽樣的測試用例是好的測試用例?我們一般會認為數量越少,發現缺陷越多的用例就是最好的用例。 那麽我們如何才能設計出好的測試用例呢? 一份好的用例是設計出來的,是測試人

[BZOJ] 4552: [Tjoi2016&Heoi2016]排序 #二分+線段樹+算法設計策略

spa close problems ref spl blog nod ans img 4552: [Tjoi2016&Heoi2016]排序 Time Limit: 60 Sec Memory Limit: 256 MBSubmit: 1451 Solv

緩存設計策略

幸福 web服務 相關 流量 輸入 http 一個 現在 清空 高並發請求的緩存設計策略 https://www.cnblogs.com/bethunebtj/p/9159914.html 前幾天,我司出了個簍子。當時正值某喜聞樂見的關鍵比賽結束,一堆人打開我司app準備看

大資料開發之Hadoop----YARN設計架構

1,Yarn架構設計 在hadoop當中無論是hdfs還是yarn都是服從主從設計的架構的,就是一個主節點用於管理整個叢集,而一堆的從節點就是實際工作者了。而在yarn當中,主節點程序為ResourceManager,從節點程序為NodeManager。我們簡單回顧一下提交作業到yarn上面的流

HBase(1)-設計與應用場景

【每日五分鐘搞定大資料】系列,HBase第一篇 講完了Zookeeper, 接下來我們來說下Google三駕馬車之一BigTable的開源實現:HBase,要講得內容如下: hbase的特點 千萬級高併發 PB級儲存 非結構化儲存 動態列,稀疏列 支援二級索引

軟體工程之系統建模設計實體型別模型】

 本文主要介紹實體類模型的設計過程,首先識別類及類之間的關係,然後畫出類圖和包圖,最後識別類的屬性和操作。類是面向物件方法的一個全新概念,類模型是面向物件分析的核心,實體類位於系統結構的商業規則服務層。實體類是系統需要持久儲存的物件最終要對映到資料庫。實體類模型用類圖和包圖描述。 1、類的識別  &nbs

軟件工程之系統建模設計實體類型模型】

author 世界 去掉 account 分析 識別方法 src 隱式 語義  本文主要介紹實體類模型的設計過程,首先識別類及類之間的關系,然後畫出類圖和包圖,最後識別類的屬性和操作。類是面向對象方法的一個全新概念,類模型是面向對象分析的核心,實體類位於系統結構的商業規則服

軟體工程之系統建模設計視窗設計

    在建立使用者介面原型之前,應該先建立視窗結構圖,視窗結構用於描述視窗之間的關係,於UML沒有直接的關係,本章介紹視窗結構的設計過程,先介紹視窗結構的設計方法,然後設計總體視窗結構圖,最後設計下一層的視窗結構圖。   1、設計方法   視窗結構是視窗之間的切換流程,通過視窗結構,可以直觀

從壹開始微服務 [ DDD ] 之一 ║ D3模式設計初探 與 我的計劃書

緣起 哈嘍大家週四好!又是開心的一天,時間過的真快,我們的 《從壹開始 .net core 2.1 + vue 2.5 》前後端分離系列共 34 篇已經完結了,當然以後肯定還會有更新和修改,直接在文章內更新,並在文章開頭做提醒,如果有大的改動或者新功能,會在目錄頁進行重點說明(可能簡書的更新速度沒有部落

軟體工程之系統建模設計系統類模型】

 類模型是面向物件分析的核心,系統類模型用包圖描述,前面的文章我們分析了實體類、介面類、介面控制類和用例控制類,本章我們將介紹系統類模型的設計,首先簡要介紹類模型的設計方法,然後設計子系統的類模型,最後設計系統類模型。   1、設計方法 設計系統類模型,要明確子系統或系統的組成,及各個組成部分之間的關係,子系

軟體工程之系統建模設計用例控制類模型】

用例控制類模型描述介面控制類與實體類之間的通訊,用例控制類位於系統結構的商業規則服務層,用例控制類模型用包圖描述。本章介紹用例控制類模型的設計過程,首先介紹用例控制類模型的設計方法,然後設計子系統包圖,最後設計系統包圖。   1、設計方法 用例控制類代表用例,它的每一個操作對應一條通過用例的途徑。介面控制類執

騰訊力作!超實用的iOS 9人機介面指南(2):設計策略

2.1 設計原則(Design Principles) 2.1.1 美學完整性(Aesthetic Integrity) 美學完整性不評判應用的視覺設計,也不是用來描述應用的風格特徵。美學完整性是指在一款應用的視覺表現和互動行為與功能結合後所傳達出的整體一致性。

開源EDR(OSSEC)基礎- 01 -設計定位與能力輸出

前言 介紹OSSEC之前,不得不提到當前比較熱門的技術EDR,近幾年隨著大資料SIEM系統的發展,EDR(端點威脅檢測與響應)技術成為了安全界萬眾寵愛的驕子,廣泛用於威脅檢測、攻擊溯源和響應處理的安全場景。 而OSSEC是一款開源的跨平臺的準EDR入侵檢測響應系統,可以實現商業EDR 大部分的功能,可以說