1. 程式人生 > >【DDD】持久化領域物件的方法實踐

【DDD】持久化領域物件的方法實踐

目錄

  • 概述
  • 開篇
  • 欄位 Or 表
    • 來說一下持久化為欄位的情況
    • 來說一下持久化為表的情況
  • 怎麼持久化集合值物件
    • 將集合值物件存為欄位
    • 將集合值物件存為表
  • 基於快照的資料儲存物件
  • 比較
  • 總結

概述

在實踐領域驅動設計(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被建立為值物件的習慣不知道是不是從這兒養成的