主頁 > .NET開發 > 如何運用領域驅動設計 - 物體

如何運用領域驅動設計 - 物體

2020-09-20 06:12:42 .NET開發

目錄

  • 概述
  • 何為物體
  • 似曾相識
    • 你確定它真的需要ID嗎
  • 運用物體
    • 結合值物件
    • 為物體賦予它的行為
    • 嘗試轉移一部分行為給值物件
    • 愿景是美好的 現實是殘酷的
  • 總結

概述

本文將介紹領域驅動設計(DDD)戰術模式中另一個常見且非常重要的概念 - 物體,相對戰術模式中其他的一些概念(例如 值物件、領域服務等)來說,物體應該比較容易讓人理解和運用,但是我們如何去發現所在領域中的物體呢?如何保證建立的物體是富含行為的?物體運用時又有那些注意的細節呢?本文將從不同的角度來帶大家重新認識一下“物體”這個概念,并且給出相應的代碼片段(本教程的代碼片段都使用的是C#,后期的實戰專案也是基于 DotNet Core 平臺),

何為物體

按照國際慣例呢,我們先吹牛,直接來看看原著《領域驅動設計:軟體核心復雜性應對之道》 中對物體的解釋:

  • 物體(Entity,又稱為Reference Object) 很多物件不是通過他們的屬性定義的,而是通過一連串的連續事件和標識定義的,
  • 主要由標識定義的物件被稱為ENTITY,

上面的兩句話多讀了幾遍,好像這個定義還是能夠理解嘛,不像上一篇文章 如何運用DDD - 值物件 中的概念那么深奧,說白了,上面就是說明了一個問題,只要你所發現的事物/物件有一個唯一的標識,那么它可能就是物體了,而唯一的標識就是我們代碼中快寫爛了的那個ID,

似曾相識

來想一下,我們在以傳統的設計思路和開發程序中,我們會在什么情況下為一個物件賦予一個ID呢?給它賦予這個ID的作用呢?一般來說我們的目的無非就是 1、為了區分本物件,如果是在資料庫中,那就是為了區分本條資料和另外一條資料,而這個ID也往往作為主鍵而存在 2、加個索引吧,來提升關聯查找速度,所以我們如果將資料庫中的表映射到我們的代碼中以類的形式呈現的時候,它可能就是這個樣子:

//旅行的行程
public class Itinerary
{
    public int ID { get; set; }

    //參加本次旅行的人員
    public List<Person> Participants { get; set; }

    //旅行的地點
    public List<string> Places { get; set; } 

    //關于該行程的備注筆記資訊
    public string  Note { get; set; } 

    //旅行開始時間
    public DateTime StartTime { get; set; }

    //旅行開始時間
    public DateTime? EndTime { get; set; }

    //旅行的狀態(進行中 or 已完成)
    public int Status { get; set; }
}

上面的代碼對我們來說應該絲毫都不陌生,我們建立了一個旅行行程的類,至于為什么我們會選取旅行行程,而不是各個博客都出現的以訂單啊電商平臺作為案例,那是因為在后期我們會一起動手來實作一個旅行記賬的微信小程式,并且借助于我們慢慢所學習到的DDD理論作為基礎,開發屬于我們自己的領域驅動框架,當然專案也是基于 DotNet Core(版本應該是3.x),

好了,還是回到我們這個例子,來思考一下ID出現的目的,你可能會說:“這還不簡單嗎?老夫縱橫代碼界多年,你現在還來問我這個問題!ID肯定是用來區分的呀,行程千千萬萬,我要找出這一條行程肯定需要這個ID了呀,” 是的,這是一個毫無爭議的問題,我們需要一個唯一的身份標識來區別物件之間的差異,DDD中物體的這一點與我們平時所接觸的類的ID有異曲同工之妙,所以本文開頭也說了物體可能是相對其他戰術概念最為讓人理解的,

你確定它真的需要ID嗎

還記得我們在上一篇文章 如何運用DDD - 值物件 中所提到過的一個問題嗎? “當前背景關系的值物件可能是另一個背景關系的物體”,所以說,當前你所判定的物體一定是基于領域當前環境(背景關系)的,脫離了該環境之后,一切都將存在變數,同樣的事物(物件),在當前環境需要一個唯一標識來識別它,而在另一個環境中可能這個唯一標識對它來說是沒有意義的,則物體就有可能成為了值物件,請考慮下面的這個例子:

在一個銀行業應用程式中,一位顧客可能會在她的銀行賬戶中放入100美元,當她未來某一天提取她這100美元時,相較于她存進銀行的錢,她可能會收到不同的鈔票或硬幣,不過,這一差異是無關緊要的,因為資金的身份不重要;顧客只關心資金的價值,所以在這個領域中,資金無疑是一個值物件,但在另一個領域中,比如涉及鈔票印刷制作或鈔票可追溯性的行業,個體鈔票或硬幣的身份實際上可能就是一個重要的領域概念了,所以每一張鈔票都會是一個具有唯一識別符號的物體

運用物體

結合值物件

千萬不要忘記了我們上一章所學習到了的值物件:在物體的內部,除了它自己的唯一標識ID之外,也許還有許許多多表明它屬性的東西,而這些東西往往可以通過使用值物件來標識,
接下來讓我們來改寫一下上面的Itinerary類:

public class Itinerary
{
    public int ID { get; set; }

    public List<Person> Participants { get; set; }

    public List<Address> Places { get; set; } 

    public ItineraryNote  Note { get; set; } 

    public ItineraryTime TripTime { get; set; }

    public ItineraryStatus Status { get; set; }
}

public class ItineraryNote
{
    public string Content { get; set; }
    public DateTime NoteTime { get; set; }

    public ItineraryNote(string content)
    {
        Content = content;
        NoteTime = DateTime.Now;
    }
}

為物體賦予它的行為

當物件建立好了之后,為了實作我們的業務邏輯處理,我們需要對實體化的物件進行操作,現在我們為該系統提出第一個需求:用戶可以修改行程中的備注資訊,
回到我們的第一版代碼中,如果我們需要處理這個操作,我們會怎么做呢?

itineraryInstance.Note = "this is my new note info";

是不是會像上面這樣,將需要添加的值賦予實體化的物件呢, 這種操作,對我們現在正在進行的編程習慣來說,是再正常不過了,

那么我們來思考,如果我們的專案有多處需要對“備注資訊”處理呢,則對該屬性的變更將被散落在代碼各處,而當我們對該需求進行了一個增強驗證時,比如此時我們需要增加:用戶修改行程中的備注資訊時,只允許用戶錄入200個字以內的文本, OMG,此時我們需要去查找所有散落的片段,并且為他加上驗證,

從另外個角度來看,第一個版本我們所建立的類,我們無法通過僅僅查看它本身就能讀懂有關旅行行程有關的業務,我們僅僅知道它具有起始時間,備注資訊等,而對他們應該如何相互作用無從所知,
所以這種僅僅具有類的屬性,或者說以POCO呈現的型別,我們稱之為“貧血模型”

接下來,我們回到第二版代碼中,我們為它賦予屬于它的行為,從需求中我們得知了,行程的備注資訊是可以修改的,而備注資訊是屬于行程的,因此修改備注資訊改行為理應屬于行程本身,我們稍微改動代碼:

public class Itinerary
{
    public int ID { get; set; }

    public List<Person> Participants { get; set; }

    public List<Address> Places { get; set; } 

    public ItineraryNote  Note { get; set; } 

    public ItineraryTime TripTime { get; set; }

    public ItineraryStatus Status { get; set; }

    //ctor

    public void ChangeNote(string content)
    {
        if(content.Length > 200 )
            throw new NoteIsOverlengthException();
        Note  = new ItineraryNote(content);
    }
}

此時我們為Itinerary賦予了一個ChangeNote的行為,當外界需要更改備注時,則只需通過呼叫改方法既可以實作,而且當展開其他開發人員閱讀此類時,也會清楚的明白,業務上允許用戶更改200字以內的備注,

但是,我們依然有一個地方美中不足,我想你可能也發現了:屬性還是對外暴露的! 對,也就是說,我們除了通過類公開的行為修改類自身的屬性外,我們還可以在外界隨意更改,這顯然不符合我們設計的初衷,因此我們可以將所有屬性的set私有化,所以,一定要注意,我們在考慮物體的時候,一定要知道“物體是高度內聚和自治的”(敲重點!!!!!),

當然,有的開發者還會嘗試另外的寫法,讓物體完全自治,將上面的代碼中的屬性,全部轉變為私有的欄位,外界只能通過公開的行為來對物體進行處理,

public class Itinerary
{
    public int ID { get; set; }

    private List<Person> participants;

    private List<Address> places;

    private ItineraryNote  note;

    private ItineraryTime tripTime;

    private ItineraryStatus status;

    //ctor

    public void ChangeNote(string content)
    {
        if(content.Length > 200 )
            throw new NoteIsOverlengthException();
        note  = new ItineraryNote(content);
    }
}

但是當外界需要獲取該物體的值,或者需要ORM映射的時候可能就不是很友好了,不過你可以使用類似于像 備忘錄模式 的快照方法來處理,后期我們也會采用這種模式來實作部分案例,

通過將物體賦予它應用的行為所建立出來的物體我們稱為“充血模型”,那么貧血模型好還是充血模型好呢? 很多同學肯定會說,這還用問嗎,肯定是充血模型啦, 其實這個答案并沒有一個真正的答案,物體自身的行為是通過我們對領域的慢慢分析(可能是通過與領域專家溝通)得來的,如果因為為了使用充血模型而盲目的將一些不屬于物體的行為賦予給它,只會讓物體變的更加混亂,從而得不償失,所以,此時的貧血模型并不意味著一直是貧血模型,后期隨著領域的深入它可能會不斷豐富屬于自身的行為,

嘗試轉移一部分行為給值物件

保持物體專注于身份這一職責很重要,因為這樣會避免它們變得臃腫————這是它們將許多相關行為拉到一起時容易掉入的陷阱,實作這一專注需要將相關行為委托給值物件和領域服務(領域服務也將在后期的文章中進行介紹),
來考慮一下最近一版的代碼,我們已經將行為劃分給了Itinerary了,但是仔細看一看,我們在后期增加需求時增加了一條驗證的規則,那么這個規則我們可以轉移給值物件嗎? 答案是,可以的,而且轉移是有必要的,因為對備注的效驗這一行為往往應該屬于它自身,就好比機器啟動時的自我效驗,這一行為是屬于操作者還是機器自己呢?
所以我們來將部分行為轉移給值物件,優化后的代碼可能是這樣的:

public class Itinerary
{
    public int ID { get; set; }

    public List<Person> Participants { get; set; }

    public List<Address> Places { get; set; } 

    public ItineraryNote  Note { get; set; } 

    public ItineraryTime TripTime { get; set; }

    public ItineraryStatus Status { get; set; }

    //ctor

    public void ChangeNote(string content)
    {
        Note  = new ItineraryNote(content);
    }
}

public class ItineraryNote
{
    public string Content { get; set; }
    public DateTime NoteTime { get; set; }

    public ItineraryNote(string content)
    {
        if(content.Length > 200 )
            throw new NoteIsOverlengthException();
        Content = content;
        NoteTime = DateTime.Now;
    }
}

愿景是美好的 現實是殘酷的

到這里,我們仿佛真的一帆風順:建立了屬于自己的物體,并且融合了該有的值物件,物體的行為也被高度內聚在了其中,那是不是我們直接就可以將DDD落地了呢? 不好意思,就如同這個小標題一樣,現實真的是非常殘酷的,如果單單從代碼閱讀和業務處理上來說,我們可能確實已經成功了,但是!!!我們需要保存我們的資料,也就是持久化,因為物體中包含了大量的值物件,所有值物件持久化所面臨的問題,它都會遇到,甚至是讓難度翻倍!有關值物件持久化的難點可以參考上一篇文章 如何運用DDD - 值物件 ,

回看我們最后一版代碼,我們有兩個集合的屬性(Participants、Places),單一的值物件的持久化已經讓我們頭痛了,現在我們不得不面對持久化值物件集合的問題,假如你通過使用EF Core這類的ORM框架來進行持久化操作,你會發現我們不得不為List中的值物件加上一個ID,此時擁有了唯一標示的值物件顯然已經成為了物體,這是非常可怕的一件事,我們辛辛苦苦建立的領域模型在最后一步落地時居然成為改變了,這往往也是DDD落地困難的一個重要原因,被ORM框架或者關系型資料庫所限制,導致領域模型不斷被打亂,重構領域模型變得越來越四不像,最終又寫回了傳統的三層架構或者面向資料庫建模,

但是至少在現在,請相信自己的所見,認真考慮和發現你專案領域所擁有的值物件和物體,不要因為知道持久化的問題而放棄和妥協,這也是我們開發者應有的勇氣,在后面的文章中,我們會關于值物件和物體的一些問題提出解決辦法,當然包括持久化的問題,

總結

本文我們介紹了物體的概念以及怎么去運用物體到實際代碼中,請牢記前人為我們提供的有關物體的經驗:比如“物體一定是基于領域當前環境(背景關系)的”“物體是高度內聚和自治的”“應該專注于物體的行為而非資料”等等,后面的文章會為大家帶來物體和值物件的一些注意事項以及領域服務的內容,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/86531.html

標籤:.NET Core

上一篇:在IIS上部署 .Net Core 3.0 專案踩坑實錄

下一篇:jvm虛擬機筆記<三> 類檔案結構與類加載機制

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more