CloudNotes之領域建模篇:領域模型簡介


CloudNotes領域模型還是相對簡單的,並不一定需要采用面向領域驅動的設計方法來解決CloudNotes的領域問題。但出於以下幾個方面的原因,我還是采用了面向領域驅動的方式來開發CloudNotes:

  1. 領域驅動是企業級應用開發的一種指導性模型,以領域模型作為軟件開發的中心,符合解決問題的基本思路
  2. 現有的企業級應用開發框架對面向領域的開發模式支持得越來越好,如果選用這種方式,可以在CloudNotes中更好地利用這些框架的最新功能,為系統開發尋求新的機遇
  3. 自己對領域驅動設計相對比較熟悉,而且也維護了一套自己研發的DDD開發框架Apworks。在CloudNotes中直接復用該框架,可以大大減小開發投入,縮短開發周期
  4. 溫故而知新,選用DDD來指導CloudNotes開發,可以獲取到有關DDD的更多信息

接下來,讓我們一起對CloudNote的領域模型作些簡單的了解。

基本模型

如果你使用的是Visual Studio 2013/2015旗艦版(Ultimate Edition),那么在打開CloudNotes解決方案后,你可以在CloudNotes.Design項目下,找到CloudNotesModel.classdiagram類圖設計文件:

image

雙擊該文件,可以打開CloudNotes的領域模型設計視圖。到目前為止,CloudNotes的領域模型如下:

image

咱們暫且不考慮C# class、aggregate root等這些UML構造型,這些內容我會在接下來的文章中詳細介紹(順帶會介紹Visual Studio對於模型設計與自動化代碼產生的支持)。從該圖可以看出,CloudNotes的領域模型還是相對簡單的。基本上可以分為三個大部分:筆記、用戶以及客戶包(ClientPackage)。或許你會考慮,是否可以將這些部分看成是DDD的界定上下文(Bounded Context)呢?

界定上下文(Bounded Context)

是的,我們可以考慮將CloudNotes的領域模型划分為三個界定上下文:

  • 筆記界定上下文:管理系統中所有的筆記內容
  • 用戶認證與授權上下文:管理系統賬戶、角色以及權限
  • 客戶包管理上下文:管理針對所有客戶端平台的升級包

從DDD角度看,由於模型被划分為多個上下文,而且上下文中的子模型也都是高內聚的(界定的),因此有可能會產生概念或語義上的二義性,而這又是通用語言(Ubiquitous Language)所不能容忍的。比如,一個經典的例子就是銀行系統里“Account” 的概念:一個提供在線服務的銀行系統,簡單地說可以粗略地分為兩個界定上下文:銀行業務以及在線服務。對於銀行業務而言,Account表示客戶的銀行賬戶,而對於在線服務而言,Account則又表示客戶的在線登錄賬號,而且一個在線登錄賬號下可以承載多個銀行賬戶,好比招商銀行一網通賬號可以有多個銀行卡賬戶和信用卡賬戶等。那么在做系統設計的時候,遇到Account概念時,如何保證團隊交流的准確性呢?因此,需要在領域模型中有一個能夠解除這種二義性的“組件”,負責幫助團隊人員在整個領域模型的理解與交流上保持一致。這種“組件”就是平時常說的“上下文映射(Context Map)”。有關界定上下文以及上下文映射的詳細介紹,可以參考這篇文章:Strategic Domain Driven Design with Context Mapping

當應用程序所需處理的領域變得很大時,引入界定上下文是很有必要的,從實踐角度考慮,使用界定上下文不僅可以在一個相對封閉的范圍內,使用一個相對較小的子領域模型,而且還可以從一定程度上提升應用程序的性能:比如某個操作只會發生在系統的某個子領域內部,又如較小的領域模型能夠減少系統的處理開銷。在Entity Framework 代碼先行(Code-First)的開發過程中,可以很好地引入界定上下文的概念,以將復雜的領域模型划分成多個相對較小的簡單的領域模型,不僅在模型設計還是在性能上,都有着很好的表現。有關這個話題的更多內容,可以參考我之前翻譯的一篇文章:Entity Framework模型在領域驅動設計界定上下文中的應用

CloudNotes的領域模型相對簡單,而且雖然目前后台采用的是Entity Framework,但使用Entity Framework並不是必須的,今后有可能會換成其它的數據持久化系統(比如MongoDB),因此也沒有按照上面所述的方式應用界定上下文的相關概念。

實體鍵

由於CloudNotes使用了Apworks框架,因此CloudNotes領域模型中的實體都是使用Guid作為實體鍵的。這一問題就要引入對Apworks框架的討論。在Apworks框架中,我們可以看到以下代碼:

public interface IEntity
{
    /// <summary>
    /// Gets or sets the identifier of the entity.
    /// </summary>
    Guid ID { get; set; }
}

在Apworks中,實體鍵是Guid類型的。有關實體鍵的類型選擇,在Vaughn Vernon的《實現領域驅動設計》一書中,有相關的描述。概括起來,大致有以下幾種情況:

  • 用戶指定實體鍵:這種方式最為直觀:用戶在界面上直接提供某個值,作為實體的鍵值。但通常而言,實體鍵值應該是不可變(immutable)的,於是,當用戶為某個實體選定了實體鍵值后,一般是不允許修改的。但這樣做又顯得跟標准的用戶操作流程不相符:我自己輸入的東西,憑什么不讓我改?另一種情況就是,用戶可以修改實體鍵的值,但這種修改或許還會帶來更多的影響,例如某個實體鍵發生變化時,由該實體發起的所有事件數據中針對該實體的引用也要發生改變。當然,如果應用程序需要保證實體鍵的可讀性時,我們就不得不設計一套合理的機制,來確保可讀性和可行性不會發生沖突
  • 應用程序產生實體鍵: 很多應用程序會通過應用程序來產生實體鍵,比如通過應用程序開發框架提供的Guid/UUID生成算法來產生實體鍵。使用Guid不僅簡單而且相對比較高效,更重要的是,它不需要依賴於應用程序以外的任何外部機制,比如持久化系統等。此外,正如《實現領域驅動設計》一書中所述,有些對性能要求很高的系統,會在一個獨立的服務器上(或者有可能是一個后台的服務上)緩存一定數量的ID值,使用Guid就不會產生因ID服務器崩潰或重啟而出現ID值重復或不連續的情況,因為Guid的生成程序始終能夠保證ID值的唯一性。另一方面,如果需要通過應用程序來產生實體鍵值,那么我們就需要完美地解決ID值的同步問題,而且還需要考慮性能問題,更進一步,企業級應用系統往往都是由多個應用程序組成的,是否,以及如何保證實體鍵在這些應用之間的統一,也是一件不容易的事情
  • 借助持久化機制產生實體鍵:這種情況非常常見,例如ORM系統會借助關系型數據庫為實體設置鍵值。這也是一種簡單方便的ID值獲取方式,因為可以借用持久化機制來獲得ID值緩存的功能,而且還能保證ID值的唯一性。但這往往也並不高效,畢竟需要借助外部系統,持久化機制的性能表現,也將對應用程序本身的性能造成影響甚至成為性能瓶頸。對於大型分布式企業級應用解決方案而言,也存在ID值的同步和統一的問題,例如應用程序A和B采用了不同的持久化機制,然而卻無法保證原本應該保持一致的實體鍵值
  • 外部界定上下文指定實體鍵:這種方式並不常見,在大型的企業級應用系統解決方案中可以用到,但也並不一定會采用這種方式。它是通過領域事件的方式,在界定上下文之間進行信息傳遞,比如本地界定上下文(Local Bounded Context)通過訂閱來自外部界定上下文的領域事件,並通過事件中的信息,以在本地界定上下文中還原實體,於是ID值也一並被還原過來,以便表示當前實體與外部界定上下文中另一對象是同一實體

很顯然,Apworks采用的就是上述Guid的方式,這樣做實現起來比較簡單高效,而且Guid值類型的特性,在絕大多數存儲系統中都能很好地支持。當然它也有一些弊端,比如可讀性差,以及在某些存儲系統中性能並不算太好等。無論如何,就Apworks框架本身而言,應該提供一套實體鍵產生的框架,以便開發人員能夠擴展ID值的產生機制,而不應該將ID值類型限定在Guid之上。這在老版本的Apworks中是有類似機制的,只是在考慮到裝箱、拆箱帶來的性能問題后,就在新的版本中,把這種機制給取消了,也就演變到目前的這種情形。今后我還是會繼續改進這部分的設計的。

總結

CloudNotes的領域模型相對簡單,實現上采用了Apworks框架,並借用了Visual Studio Ultimate的Architecture功能,對領域模型進行可視化設計和自動化代碼生成。或許在今后領域模型會進一步修改,或者逐漸變大,或許也會由此而采用一些新的技術,並產生新的解決方案。到目前為止,CloudNotes的領域模型規模還是很小的,本系列文章也會首先針對當前的這個模型來進行介紹。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM