針對網域導向設計撰寫程式碼:進行資料型開發的秘訣
這一年,Eric· 埃文斯的突破性軟體設計的書,"領域驅動設計:軟體核心複雜性應對"(艾迪生-衛斯理專業,2003 年, amzn.to/ffL1k),成立 10 周年。 埃文斯對這本書帶來了多年的經驗指導大型企業通過建立軟體的過程。 他然後花了多年思考如何封裝的這些專案導致成功的模式 — — 與用戶端交互,分析正在解決的業務問題、 建立團隊和軟體架構設計。 這些模式的重點在於業務域,和他們共同構成了 Domain-Driven 設計 (DDD)。 DDD,與您的模型問題域。 從這種抽象的您有關域的知識模式結果。 即使在今天重讀 Martin Fowler 前言和埃文斯的前言,繼續提供豐富的本質的 DDD 概述。
在此列,接下來的兩,我會分享一些説明我隨著我的工作讓我受益于一些 DDD 技術模式的代碼獲得清晰的資料集中,Entity Framework大腦的指標。
為什麼關心 DDD?
DDD 我介紹來自一個簡短的視頻面試 InfoQ.com 與吉米尼爾森,一個受人尊重建築師在.NET 社區 (和其他地方),在談論LINQto SQL 和Entity Framework(bit.ly/11DdZue)。結束時,尼爾森被問他最喜歡的科技書的名字。他的答覆:"我最喜愛的電腦書是書的Eric· 埃文斯,"領域驅動設計。我覺得就像詩歌。它不只是內容很棒,但是你可以閱讀它很多次而且它讀起來像詩。詩歌 !我在寫我第一次的科技書,"程式設計Entity Framework"(O'Reilly 媒體,2009年),當時,我是通過這種描述感興趣。所以我去讀一點點埃文斯的書來看看它是什麼樣子。埃文斯是美麗的流體的作家。加上他的軟體發展、 感知、 自然主義的看法和不會讓這本書閱讀的快樂。但我也感到驚訝讀什麼書。不只是書寫精彩,他在寫什麼很吸引我。他談與客戶建立關係,並真正瞭解他們的業務和他們的業務問題 (與相關問題的軟體),不只進入的代碼。這是我 25 年的軟體發展一直對我重要的東西。我想要更多。
我踮 DDD 邊緣周圍幾年更多,然後開始學習更多 — — 埃文斯舉行一次會議,然後出席他浸泡為期四天的講習班。雖然我很不專家在 DDD 中,發現綁定上下文模式是我工作的轉變我自己軟體的創建過程,邁向一個更有組織的、 易於管理的結構馬上可以利用的東西了。您可以閱讀關於這一主題在專欄裡 2013 年 1 月,"收縮 EF 模型與 DDD 綁定上下文"(msdn.microsoft.com/magazine/jj883952)。
從那時起我已經進一步探討。我好奇,DDD,但鬥爭的鼓舞我資料驅動的角度來理解一些技術模式,使它成功。它可能似乎很多開發人員經歷同樣的掙扎,所以我要去分享一些經驗教訓,我一直在學習與説明、 利息和埃文斯和其他 DDD 從業人員、 教師,包括Paul雷納、 沃恩農、Greg年輕,數目的慷慨 Cesar de la Torre 和 Yves Reynhout。
當類比域,忘了持久性
建模域都是側重于業務的任務。當設計類型和它們的屬性和行為,我恨不得想關係將如何資料庫中,以及如何選擇我的物件關係映射 (ORM) 框架 — —Entity Framework— — 將處理的屬性、 關係和我建的繼承層次結構。除非您正在構建軟體公司,其業務是資料存儲和檢索 — — 有點像 Dropbox — — 資料持久性只扮演輔助的角色在您的應用程式。它非常像親熱打個電話到天氣源 API,向使用者顯示當前溫度。或者從您的應用程式到外部服務,也許在 Meetup.com 上的註冊發送資料。當然,您的資料可能會更複雜,但與定界框的上下文、 側重于行為和建築類型後 DDD 指導 DDD 辦法,持續存在可比你今天可能會建設的系統太複雜。
如果你去研究你的 ORM,例如學習如何設定資料庫映射使用Entity FrameworkFluent API,您應該能夠使持續存在和所需的工作。在最壞的情況下,您可能需要對您的類進行一些調整。在極端的情況下,如與舊版資料庫,您可以甚至添加持久性模型設計資料庫的映射,然後使用 AutoMapper 之類來解決您的域模型和持久性模型之間的事情。
但這些憂慮是無關的業務問題,您的軟體旨在解決,所以堅持不應干擾域設計。這是我的挑戰,因為我設計我的實體,如我不能説明,但考慮 EF 將如何推斷出它們的資料庫映射。於是我試著阻止這種噪音。
私人 Setter 和公共方法
另一條經驗法則是使私有屬性 setter。而不是允許調用代碼隨機設置各種屬性,你應該控制交互與 DDD 物件和其相關的資料,使用修改屬性的方法。並且,我不是指方法,如 SetFirstName 和 SetLastName。例如,而不是具現化一個新的客戶類型,然後設置其屬性的每個,可能有一些規則來創建一個新的客戶時考慮。你可以到客戶的建構函式生成這些規則、 使用原廠模式的方法或甚至客戶類型中具有一種創建方法。圖 1 顯示了客戶定義的類型,是繼 DDD 模式的聚合根 (就是"父母"的圖的物件,也稱為"根實體"在 DDD 中)。客戶屬性有私人的 setter,以便只有客戶類的其他成員可以直接影響到這些屬性。類公開來控制如何具現化和隱藏無參數建構函式 (Entity Framework所必需) 作為內部的建構函式。
圖 1 屬性類型和方法,作為聚合的根
public class Customer : Contact
{
public Customer(string firstName,string lastName, string email)
{ ... }
internal Customer(){ ... }
public void CopyBillingAddressToShippingAddress(){ ... }
public void CreateNewShippingAddress(
string street, string city, string zip) { ... }
public void CreateBillingInformation(
string street, string city, string zip,
string creditcardNumber, string bankName){ ... }
public void SetCustomerContactDetails(
string email, string phone, string companyName){ ... }
public string SalesPersonId { get; private set; }
public CustomerStatus Status{get;private set;}
public Address ShippingAddress { get; private set; }
public Address BillingAddress { get;private set; }
public CustomerCreditCard CreditCard { get; private set; }
}
客戶類型控制和總體保護其他實體 — — 的一些位址和信用卡類型 — — 通過公開具體方法 (如 CopyBillingAddressToShippingAddress) 這些物件將創建和操縱。 聚合根必須確保每個聚合內的實體定義的規則應用使用域邏輯和在這些方法中實現的行為。 最重要的是,聚合根是不變的邏輯和整個聚合的一致性。 我會再談不變數在我下一列中,但同時,我建議閱讀博加德吉米的博客帖子,"加強您的域:聚合建設,"在 bit.ly/ewNZ52,其中提供了優秀的集料中的不變數解釋。
在結束時,由客戶公開的什麼是行為,而不是屬性:CopyBillingAddressToShippingAddress、 CreateNewShipping位址、 CreateBillingInformation 和 SetCustomerContactDetails。
注意,連絡人類型,從哪些客戶派生,生活在不同的程式集,名為"共同的",因為它可能需要的其他類。 我需要隱藏屬性的連絡人,但他們不能是私有的或客戶不能訪問它們。 相反,它們是作為被保護範圍:
public class Contact: Identity
{
public string CompanyName { get; protected set; }
public string EmailAddress { get; protected set; }
public string Phone { get; protected set; }
}
關於身份的一側注:因為他們有沒有金鑰的值,客戶和連絡人可能像 DDD 值物件。 然而,我的解決方案,在金鑰值是的類提供的身份聯繫派生的。 這些類型均不可變的所以他們無論如何不能被視為價值的物件。
客戶從接觸繼承,因為它會對那些受保護的屬性的訪問,能夠設置它們,這種 SetCustomerContactDetails 方法:
公共 void SetCustomerContactDetails (字串電子郵件、 電話字串字串公司名稱)
{
電子郵件地址 = 電子郵件 ;
手機 = 電話 ;
公司名稱 = 公司名稱 ;
}
有時你需要的只是 CRUD
不是一切都在您的應用程式需要創建使用 DDD。 DDD 是有説明處理複雜的行為。 如果你只是需要做一些原始的、 隨機的編輯或查詢,然後簡單類 (或一組類),定義只是像您通常用 EF 代碼第一次 (使用的屬性和關係) 和聯合插入、 更新和刪除方法 (通過存儲庫或只是 DbCoNtext),是所有你需要的。 所以,要完成某事象創建順序和其行專案,您可能希望 DDD 説明工作通過特殊的商務規則和行為。 例如,這是黃金的明星客戶下訂單的嗎? 在這種情況下,您需要獲取某些客戶詳細資訊以確定是否答案是肯定的和,如果是這樣,適用于每個專案被添加到訂單的折扣率為 10%。 使用者提供他們的信用卡資訊了嗎? 然後,你可能需要調用驗證服務,以確保它是有效的卡。
在 DDD 中的關鍵是要包含的域邏輯作為內域的實體類,而不實施"的事務性腳本"利用 OOP 方法內無國籍的業務物件,第一類看起來像什麼典型的演示潔具代碼。
但有時你做的所有東西非常標準,類似于創建連絡人記錄:名稱、 位址,所述的等等,和保存它。這是剛創建、 讀取、 更新和刪除 (CRUD)。 你不需要創建聚合和根和行為來滿足的。
最有可能您的應用程式將包含複雜行為和簡單的 CRUD 的組合。 花時間去澄清的行為,不要浪費時間、 精力和金錢高真的只是簡單的應用程式塊。 在這些情況下,它是重要的是要找出不同的子系統或有界的上下文之間的界限。 一個有界的上下文可能會非常資料驅動 (只是 CRUD),雖然關鍵核心域界方面,另一方面,設計應繼 DDD 的辦法。
共用的資料可以在複雜系統中是一個詛咒
另一個問題我敲我的頭,然後咆哮和哭訴,慈祥的人想要進一步,解釋有關共用的類型和資料跨子系統。 它變得清楚,我不能"有我的蛋糕,吃它太"所以我被迫再想想我假設我絕對地必須跨系統共用類型和有那些類型,所有與同一表中相同的資料庫進行交互。
我正在學習真的認為我需要共用資料,然後選擇我的戰役。 只是有些事情不可能值得一試,從不同的上下文映射到單個表或甚至單個資料庫一樣。 最常見的示例分享試圖滿足所有人需要跨系統的聯繫。 你如何調和和利用連絡人類型,可能需要大量系統的原始程式碼控制? 如果一個系統需要修改該連絡人類型的定義嗎? 關於 ORM 如何映射用於跨系統到單個資料庫中的單個表的聯繫?
DDD 將引導您從共用域模型和解釋你不總是需要指向同一人在單個資料庫中表的資料。
我最大推回這是 25 年的基礎側重于重用的好處 — — 重用代碼和重用資料。 所以,以下的想法與硬有空但我氣候變暖方案對它:它不是犯罪,要重複的資料。 不是所有的資料將當然適合本 (向我) 新的范式。 但是,羽量級的東西呢喜歡一個人的名字嗎? 所以如果你重複的人的第一和最後在多個表或甚至有多個資料庫專用於不同子系統軟體解決方案的名稱嗎? 長遠來說,通過放手的共用資料的複雜性,可以構建您的系統變得更加簡單的工作。 在任何情況下,您必須始終最小化在有界不同上下文中的資料和屬性重複。 有時你只需要客戶的 ID 和狀態計算折扣定價界的上下文中。 同一客戶的第一個和最後一個名稱可能需要僅在連絡人管理界的上下文中。
但仍有很多需要系統之間共用的資訊。 DDD 所提到的您可以利用"抗腐敗層"(其中可以作為簡單的東西作為一種服務或訊息佇列) 以確保,例如,如果有人在一個系統中創建一個新的連絡人,你要麼承認該人已經存在其它地方,或確保的人,和一個共同的身份金鑰,創建另一個子系統中。
很多到下個月才能上嚼
為我讓路通過學習和理解方面的 Domain-Driven 設計,努力調和舊習慣以新的想法,並抵達無數"哈 ! 的時刻,我在這裡討論的指標技術是真正幫我看看更多的光線,比黑暗。 有時它只是問題的角度來看,和我在這裡表達他們的方式反映了説明做出更清晰的東西對我的觀點。
我要分享一些更多的我的"啊哈!"時刻在我下一列中,凡我會談談那居高臨下的期限,你可能聽說過:"貧血的域模型,"以及它的 DDD 表兄弟,"富域模型"。此外將討論什麼期望的時候如果你使用的Entity Framework添加在資料持久性和單向關係。 我還會碰一些更多的 DDD 專題引起我足夠的悲傷中努力縮短自己的學習曲線。
直到那時,為什麼不採取仔細看看您自己的類,請參閱如何將更多的控制狂,隱藏這些屬性 setter 和揭露更多描述性和顯式方法。 而且,請記住:不允許的"SetLastName"方法。 那作弊 !
Julie Lerman 是 Microsoft MVP,.NET 的導師和顧問住在山裡的佛蒙特。你可以找到她提出關於資料訪問和其他 Microsoft.NET 使用者組和世界各地的會議的主題。在她的博客 thedatafarm.com/blog 和"程式設計Entity Framework"的作者 (2010 年) 以及代碼第一版 (2011 年) 和 DbCoNtext 版 (2012 年),都是來自 O'Reilly 媒體。跟著她在 Twitter 上 twitter.com/julielerman 看看她的 Pluralsight 課程 juliel.me/PS-視頻
感謝以下技術專家對本文的審閱: Cesar de la Torre (Microsoft)