雖然領域驅動設計的思想很誘人,但我們依然會面臨各種隱藏的困難,就比如今天我們要講的主題“持久化”:即使前期我們設計了足夠完整的領域對象,但是依然需要持久化它們到資料庫中,而普通的關係型資料庫可能很難維持領域對象的原有結構,所以我們必須要使用一些特有的手段來處理它。將值對象持久化成欄位好呢?還是將值對... ...
目錄
概述
在實踐領域驅動設計(DDD)的過程中,我們會根據項目的所在領域以及需求情況捕獲出一定數量的領域對象。設計得足夠好的領域對象便於我們更加透徹的理解業務,方便系統後期的擴展和維護,不至於隨著需求的擴展和代碼量的累積,系統逐漸演變為大泥球(Big Ball of Mud)。
雖然領域驅動設計的思想很誘人,但我們依然會面臨各種隱藏的困難,就比如今天我們要講的主題“持久化”:即使前期我們設計了足夠完整的領域對象,但是依然需要持久化它們到資料庫中,而普通的關係型資料庫可能很難維持領域對象的原有結構,所以我們必須要使用一些特有的手段來處理它。
開篇
本篇文章屬於《如何運用領域驅動設計》系列的一個補充,如果您閱讀過該系列的其它文章,您就會發現關於“持久化”的這個問題已經不止在一篇博文中提及到了。
那麼,到底是什麼原因讓我們面臨這個問題呢? 是的!值對象! 如果您認真的瞭解過值對象的話(如果還不瞭解值對象,您可以參考 如何運用領域驅動設計 - 值對象),您會發現值對象是由許多基元類型構成的(比如string,int,double等),所以我們可以理解它為對細粒度基元類型的包裹,構成我們所在領域中的一個基礎類型,比如說下麵這個例子:
public sealed class City : ValueObject
{
public string Name { get; }
public int Population { get; }
public City(string name, int population)
{
Name = name;
Population = population;
}
}
我們假設現在有一個叫做City的值對象,它是由名稱(Name)和人口數量(Population)構成。通常我們這樣建立值對象的原因很簡單,在該領域中我們一聯繫到“人口”數量就會和“城市”連同在一起(你不會說我想知道人口數量,而你會說我想知道紐約的人口數量),所以“城市”這一概念成為我們該領域中的小顆粒對象,而該對象在代碼實現中是由多個小基元類型構成的,比如該例子就是由一個string和一個int。
這樣建模的好處之一就是我們考慮的問題是一個整體,將零碎的點構建為一個整體對象,如果該對象的行為需要發生改變,只需要修改該對象本身就可以了,而不是代碼散落在各處需要到處查找(這也是滾成大泥球的原因之一)。
如果您喜歡捕獵有關DDD的知識,您可能不止一次會看到這樣一條建議規則:
In the world of DDD, there’s a well-known guideline that you should prefer Value Objects over Entities where possible. If you see that a concept in your domain model doesn’t have its own identity, choose to treat that concept as a Value Object.
該建議的內容就是提倡DDD實踐者多使用值對象。當然也不是說無論什麼東西都建立成值對象,只是要我們多去發現領域中的值對象。
但是這往往給持久化帶來了難度,先來想一下傳統的編碼持久化方式:一個對象(或者POCO)裡面包含了各個基元類型的屬性,當需要持久化時,每個屬性都對應資料庫的一個欄位,而該對象就成為了一個表。 但是這在領域驅動設計中就不好使用了,值對象成了我們考慮問題的小顆粒,而它在代碼中成了一個類,如果直接持久化它是什麼樣子呢?表,使用它的實體或者聚合根也是一個表,兩個表通過主外鍵關係鏈接。
那麼這樣持久化方式好不好呢? 答案是不確定的,可能瞭解了下文的這些方案後,您會有自己的見解。
本篇文章的持久化方案都是基於關係型資料庫,如果您是非關係型資料庫(比如mongodb),那麼您應該不會面臨這樣的問題。
欄位 Or 表
將值對象持久化成欄位好呢?還是將值對象持久化為表好呢? 這個問題其實也有很多廣泛的討論,就好比.NET好還是Java好(好吧,我php天下**),目前其實也沒有個明確的結果:
- 覺得持久化為表欄位的原因是 如果持久化為表,必須給表添加一個ID供引用的實體或者聚合關聯,這就不滿足值對象不應該有ID的準則了。
- 覺得持久化為表的原因是 數據表模型並不代表代碼層面的模型,代碼裡面的值對象其實並沒有ID的說法,所以它是符合值對象的,而持久化為欄位的話,同一個值對象數據會被覆製為多份導致數據冗餘。
當然哈,各有各的道理,我們也不用特別偏向於使用哪個結論。應該站在客觀的角度,實際的項目需要哪種手段就根據切實的情況來選擇。
來說一下持久化為欄位的情況
該手段其實在近期來說比較流行,特別是在EFCore2.0之後,為什麼呢?因為EF Core2.0提供了一個叫做 從屬實體類型 的概念,其實這個技術手段在EF中很早就有了,在EF中有一個叫做Complex的東西,只是在EF Core 1.x時代沒有引入而已。
在EFCore引入了Owned之後,微軟那個最著名的微服務教程 eShopOnContainers 也順勢推出了用於該特性來持久化值對象的方案:
所以這也是為什麼大家都在使用Owned持久化值對象的原因。(當然,大家項目中只有Address被建立為值對象的習慣不知道是不是從這兒養成的