日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

如何使用ABP进行软件开发(2) 领域驱动设计和三层架构的对比

發(fā)布時間:2023/12/20 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何使用ABP进行软件开发(2) 领域驱动设计和三层架构的对比 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

簡述

上一篇簡述了ABP框架中的一些基礎(chǔ)理論,包括ABP前后端項(xiàng)目的分層結(jié)構(gòu),以及后端項(xiàng)目中涉及到的知識點(diǎn),例如DTO,應(yīng)用服務(wù)層,整潔架構(gòu),領(lǐng)域?qū)ο?#xff08;如實(shí)體,聚合,值對象)等。

筆者也曾經(jīng)提到,ABP依賴于領(lǐng)域驅(qū)動設(shè)計這門方法論,由于其門檻較高,給使用者帶來了不少理解上的難度。尤其是三層架構(gòu)對.NET開發(fā)者影響太深,有時很難對領(lǐng)域驅(qū)動設(shè)計產(chǎn)生直觀的理解。

在本文中,打算從傳統(tǒng)的簡單三層架構(gòu)談起,介紹一個實(shí)際場景下的三層業(yè)務(wù)邏輯實(shí)現(xiàn),然后再與領(lǐng)域驅(qū)動設(shè)計中的對應(yīng)實(shí)現(xiàn)形成對比,以便讓開發(fā)者形成直觀具體的印象。

回顧三層架構(gòu)

對于.NET開發(fā)者來說,三層架構(gòu)相比都不陌生,這種架構(gòu),將代碼層次劃分為用戶界面層,業(yè)務(wù)邏輯層、數(shù)據(jù)訪問層三個邏輯層次,實(shí)現(xiàn)了代碼的關(guān)注度分離,且因其易于理解,已經(jīng)成為眾多.NET開發(fā)者的"條件反射"。

三層架構(gòu)簡介

三層架構(gòu)就是為了符合“高內(nèi)聚,低耦合”思想,把各個功能模塊劃分為表示層(UI)、業(yè)務(wù)邏輯層(BLL)和數(shù)據(jù)訪問層(DAL)三層架構(gòu),各層之間采用接口相互訪問,并通過對象模型的實(shí)體類(Model)作為數(shù)據(jù)傳遞的載體,不同的對象模型的實(shí)體類一般對應(yīng)于數(shù)據(jù)庫的不同表,實(shí)體類的屬性與數(shù)據(jù)庫表的字段名一致。

三層架構(gòu)區(qū)分層次的目的是為了 “高內(nèi)聚,低耦合”。開發(fā)人員分工更明確,將精力更專注于應(yīng)用系統(tǒng)核心業(yè)務(wù)邏輯的分析、設(shè)計和開發(fā),加快項(xiàng)目的進(jìn)度,提高了開發(fā)效率,有利于項(xiàng)目的更新和維護(hù)工作。

三層架構(gòu)的分層邏輯

UI層:用戶界面層,實(shí)現(xiàn)與UI交互有關(guān)的邏輯。用于輸入用戶數(shù)據(jù),輸出和呈現(xiàn)數(shù)據(jù)。在基于WebAPI的現(xiàn)代Web框架中,往往會使用MVC架構(gòu),將界面的數(shù)據(jù)行為,拆分成“模型-視圖-控制器”,實(shí)現(xiàn)了針對對UI層上關(guān)注度的進(jìn)一步分離,

業(yè)務(wù)邏輯層: 業(yè)務(wù)邏輯層是用戶界面層和數(shù)據(jù)訪問層之間的轉(zhuǎn)換層,負(fù)責(zé)完成對數(shù)據(jù)的業(yè)務(wù)組裝,界面數(shù)據(jù)處理,將數(shù)據(jù)層的對象輸出(轉(zhuǎn)換)給用戶界面層。

數(shù)據(jù)訪問層:實(shí)現(xiàn)數(shù)據(jù)的存儲(持久化)操作,包括集成存儲過程,集成SQL語句,或集成現(xiàn)代ORM組件的形式,實(shí)現(xiàn)實(shí)現(xiàn)數(shù)據(jù)的存儲。

三層架構(gòu)的應(yīng)用

遇到項(xiàng)目,先從實(shí)體關(guān)系建模開始,使用PowerDesign或其他數(shù)據(jù)庫設(shè)計軟件分析業(yè)務(wù)與業(yè)務(wù)之間的關(guān)系,是一對多,還是一對一,還是多對多,繪制實(shí)體關(guān)系圖。

在進(jìn)行軟件開發(fā)時,根據(jù)數(shù)據(jù)需求,定制想要的數(shù)據(jù)接口,從而實(shí)現(xiàn)以數(shù)據(jù)為核心的業(yè)務(wù)功能開發(fā)。

于是,在業(yè)務(wù)層次上,這種三層架構(gòu),進(jìn)一步可以表示為如下分層結(jié)構(gòu):

在三層架構(gòu)中,實(shí)體是業(yè)務(wù)的核心,所有的業(yè)務(wù)代碼,都是圍繞實(shí)體展開,而左側(cè)三個功能層,其主要目的都是為了實(shí)現(xiàn)對實(shí)體的“增刪改查”操作。

以下代碼簡述了一個訂單對象提交的全過程。(模型和代碼僅供參考,不能直接運(yùn)行)

復(fù)制代碼

/// <summary> /// UI控制器 /// </summary> public class OrderController {private OrderBll OrderBll = new OrderBll();/// <summary>/// 新增訂單/// </summary>/// <param name="userId"></param>/// <param name="productId"></param>/// <param name="count"></param>public void AddOrder(int userId, int productId, int count){OrderBll.AddOrder(userId, productId, count);} }/// <summary> /// 業(yè)務(wù)邏輯層 /// </summary> public class OrderBll {private UserInfoDal userInfoDal = new UserInfoDal();private ProductInfoDal productInfoDal = new ProductInfoDal();private OrderDal orderDal = new OrderDal();/// <summary>/// 新增訂單/// </summary>/// <param name="userId"></param>/// <param name="productId"></param>/// <param name="count"></param>public void CreateOrder(int userId, int productId, int count){UserInfo userInfo = userInfoDal.Get(userId);ProductInfo productInfo = productInfoDal.Get(productId);//新訂單Order order = new Order();order.Address = userInfo.Address;order.UserId = userId;order.TotalPrice = productInfo.Price * count;order.ProductId = productId;orderDal.Insert(order);} } /// <summary> /// 數(shù)據(jù)訪問層 /// </summary> public class OrderDal {/// <summary>/// 插入數(shù)據(jù)/// </summary>/// <param name="order"></param>public void Insert(Order order){ } }

這種基于實(shí)體驅(qū)動建模的三層架構(gòu),變成了以數(shù)據(jù)為核心的“表模塊模式”。
參見《企業(yè)應(yīng)用架構(gòu)模式》第87頁中關(guān)于表模塊的介紹:

表模塊以一個類對應(yīng)數(shù)據(jù)庫中的一個表來組織領(lǐng)域邏輯,而且使用單一的類實(shí)體來包含將對數(shù)據(jù)進(jìn)行的各種操作程序。
通常,表模塊會與面向表的后端數(shù)據(jù)結(jié)構(gòu)一起使用。以列表形式排列的數(shù)據(jù)通常是某個SQL調(diào)用的結(jié)果,它們被至于一個記錄集中,用于某一個SQL表。表模塊提供了一個明確的基于方法的接口對數(shù)據(jù)進(jìn)行操作。
要進(jìn)行一些實(shí)際的操作,一般需要多個表模塊的行為。
表模塊中的“表”一詞,暗示你數(shù)據(jù)庫中的每一個表對應(yīng)一個表模塊。雖然大多數(shù)情況下都是如此,但也并非絕對。對于通用的視圖或其他查詢,建立一個表模塊也是有用的。事實(shí)上,表模塊的結(jié)構(gòu)并非真的取決于數(shù)據(jù)庫表的結(jié)構(gòu),更多的是由應(yīng)用程序能識別的虛擬表所標(biāo)識,例如視圖或查詢。

在《Microsoft.NET企業(yè)級架構(gòu)設(shè)計》一書中,作者認(rèn)為“多數(shù).NET開發(fā)者在成長的過程中都受到了表模塊模式的影響”。而相比之下,多數(shù)Java開發(fā)者則“深陷事務(wù)腳本的泥足”。

三層架構(gòu)的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

軟件分層架構(gòu)的目的是為了分離關(guān)注點(diǎn),三層架構(gòu)也同樣如此,簡簡單單的三層代碼+ER圖,就能設(shè)計出一個良好結(jié)構(gòu)的軟件系統(tǒng)。

這種模式,建立了以數(shù)據(jù)庫表為核心的開發(fā)模式,使得開發(fā)者能夠很便捷的對業(yè)務(wù)進(jìn)行分析,進(jìn)而驅(qū)動軟件功能的快速開發(fā)。

在應(yīng)對簡單業(yè)務(wù)變遷過程中,由于能夠快速完成代碼的堆積,也使得開發(fā)者只需關(guān)注數(shù)據(jù)庫表的拼湊,就能快速的完成代碼開發(fā),為開發(fā)項(xiàng)目帶來了不少便利。

除了簡單業(yè)務(wù)普遍采用三層,事實(shí)上許多復(fù)雜項(xiàng)目也會同樣采用,大概是由于三層架構(gòu)的思想已經(jīng)深入人心,許多資深開發(fā)者都形成的只要有表就能完成項(xiàng)目的開發(fā)的思維定勢。

缺點(diǎn):

還是使用上述示例代碼,我們假設(shè)需求發(fā)生了變化,要求減少訂單的數(shù)量或增加訂單,我們會怎么做?也許,我們很容易就寫出了下面的代碼:

(當(dāng)然,實(shí)際項(xiàng)目中,如果訂單已經(jīng)提交,很少會直接對訂單數(shù)量進(jìn)行修改的,往往會重新發(fā)起新訂單,但為了演示方便,我們先設(shè)定有這么一個奇怪的需求吧。)

復(fù)制代碼

/// <summary> /// 減少訂單數(shù)量 /// </summary> /// <param name="orderId"></param> /// <param name="minusCount"></param> public void MinusOrder(int orderId, int minusCount) {Order order = orderDal.Get(orderId);order.Count -= minusCount;order.TotalPrice -= order.Price * minusCount;orderDal.Update(order); } /// <summary> /// 增加訂單數(shù)量 /// </summary> /// <param name="orderId"></param> /// <param name="minusCount"></param> public void AddOrder(int orderId, int addCount) {Order order = orderDal.Get(orderId);order.Count += addCount;order.TotalPrice += order.Price * addCount;orderDal.Update(order); }

這個代碼寫起來非常快,因?yàn)橹皇切略隽藘尚《未a邏輯,而從減少訂單,到新增訂單,只是加法和減法的區(qū)別,自然而然就更快了。
但是,速度快,一定是優(yōu)點(diǎn)么?如果需求繼續(xù)持續(xù)不斷的累積呢。

例如,我們要修改訂單收貨人,收貨地址,修改訂單價格,是不是我們這種代碼邏輯會越來越多,而且不同的業(yè)務(wù)邏輯互相攪合,使得后期的維護(hù)變得越來越困難?

所以,筆者認(rèn)為,三層架構(gòu)的缺點(diǎn),就是前期開發(fā)速度太快,由于缺乏設(shè)計思想和設(shè)計模式的參與,太容易導(dǎo)致異味、垃圾代碼、重復(fù)代碼等問題產(chǎn)生。

所有這些問題,最終都被歸類于“技術(shù)債”的范疇。

詳見維基百科。
技術(shù)債:指開發(fā)人員為了加速軟件開發(fā),在應(yīng)該采用最佳方案時進(jìn)行了妥協(xié),改用了短期內(nèi)能加速軟件開發(fā)的方案,從而在未來給自己帶來的額外開發(fā)負(fù)擔(dān)。
這種技術(shù)上的選擇,就像一筆債務(wù)一樣,雖然眼前看起來可以得到好處,但必須在未來償還。
軟件工程師必須付出額外的時間和精力持續(xù)修復(fù)之前的妥協(xié)所造成的問題及副作用,或是進(jìn)行重構(gòu),把架構(gòu)改善為最佳實(shí)現(xiàn)方式。

回顧領(lǐng)域驅(qū)動設(shè)計

領(lǐng)域驅(qū)動設(shè)計簡介

領(lǐng)域驅(qū)動設(shè)計思想來源于埃里克埃文斯在2002年前后出版的技術(shù)書籍《領(lǐng)域驅(qū)動設(shè)計·軟件系統(tǒng)復(fù)雜性核心應(yīng)對之道》,在這本書中,作者介紹了領(lǐng)域驅(qū)動設(shè)計相關(guān)的核心模式,例如:統(tǒng)一語言,模型驅(qū)動設(shè)計,領(lǐng)域?qū)嶓w,聚合,值對象,倉儲,限界上下文等模式。

隨著微服務(wù)的不斷興起,領(lǐng)域驅(qū)動設(shè)計也越來越受到互聯(lián)網(wǎng)人的廣泛追捧,在許多不同的行業(yè)應(yīng)用實(shí)踐過程中,已經(jīng)逐漸扮演了非常基礎(chǔ)的作用。無論是微服務(wù)架構(gòu)下的服務(wù)粒度拆分,或者甚至是中臺應(yīng)用,以及傳統(tǒng)的單體應(yīng)用,都可以利用領(lǐng)域驅(qū)動設(shè)計思想下提供的模式,為應(yīng)用程序的開發(fā)插上想象的翅膀。

領(lǐng)域驅(qū)動設(shè)計的分層邏輯

在上一篇博客中,我們也介紹了領(lǐng)域驅(qū)動設(shè)計思想分層邏輯結(jié)構(gòu),共劃分為如下四個層次:

  • 用戶界面層(或者表示層):負(fù)責(zé)向用戶顯示信息和解釋用戶指令。這里的用戶,既可以是使用用戶界面的人,也可以是另外一個計算機(jī)系統(tǒng)。
  • 應(yīng)用層:定義軟件要完成的任務(wù),并且只會表達(dá)領(lǐng)域概念的對象來解決問題。這一層實(shí)際上負(fù)責(zé)的是系統(tǒng)與應(yīng)用層進(jìn)行交互的必要渠道。
  • 領(lǐng)域?qū)?#xff1a;負(fù)責(zé)表達(dá)業(yè)務(wù)概念、業(yè)務(wù)狀態(tài)信息以及業(yè)務(wù)規(guī)則。盡管技術(shù)細(xì)節(jié)由基礎(chǔ)設(shè)施層實(shí)現(xiàn),但業(yè)務(wù)情況狀態(tài)的反映則需要有領(lǐng)域?qū)舆M(jìn)行控制。領(lǐng)域?qū)邮菢I(yè)務(wù)軟件的核心。
  • 基礎(chǔ)設(shè)施層:為上面各層提供通用的技術(shù)能力:為應(yīng)用層傳遞消息,為領(lǐng)域?qū)犹峁┏志没瘷C(jī)制,為用戶界面層繪制屏幕組件等等。基礎(chǔ)設(shè)施層還能夠通過架構(gòu)框架來支持4個層次間的交互模式。

領(lǐng)域驅(qū)動設(shè)計的應(yīng)用步驟

1)形成統(tǒng)一語言

統(tǒng)一語言是圍繞產(chǎn)品展開的一系列流程,方案,術(shù)語和名詞解釋及匹配的注釋。在領(lǐng)域驅(qū)動設(shè)計為每個應(yīng)用設(shè)計成體系的【統(tǒng)一語言】是核心要點(diǎn)。

統(tǒng)一語言的形成是團(tuán)隊(duì)成員協(xié)同參與,圍繞不同的需求,達(dá)成一致性理解的過程。

形成統(tǒng)一語言有時需要領(lǐng)域?qū)<业膮⑴c,但有時可能難以達(dá)到這個條件,用需求代言人也同樣能夠滿足這個條件。

2)使用UML建模和畫圖

  • 建模的必要性
  • 在我們工作過程中模型無處不在,不管是在紙上繪制的簡單模型,或者使用專業(yè)軟件繪制的各種模型,都是模型。領(lǐng)域驅(qū)動設(shè)計本身,依然依賴于模型驅(qū)動設(shè)計。

    學(xué)會建模對于廣大開發(fā)者來說,都是一項(xiàng)基本技能,當(dāng)然也是眾多最弱技能中的一種,因?yàn)閺V泛依賴于實(shí)體關(guān)系建模的思維模式,使得開發(fā)者已經(jīng)很難形成有效的模型設(shè)計思想,代碼也越來越趨于【過程化】。

    有時開發(fā)者甚至連實(shí)體關(guān)系建模這個步驟都會省略,直接使用Code First或甚至數(shù)據(jù)庫開始建表,這樣看起來速度非常快,但是太容易翻車了。

    在團(tuán)隊(duì)協(xié)作項(xiàng)目中,沒有良好的模型,僅憑高級開發(fā)者或有經(jīng)驗(yàn)開發(fā)者的“”一面之詞”進(jìn)行設(shè)計,幾乎很難完成一個復(fù)雜項(xiàng)目。

    而uml統(tǒng)一建模語言也是這樣的良好工具。

  • 使用哪些模型
  • 筆者曾經(jīng)有幸請教國內(nèi).NET技術(shù)圈擁有多年DDD實(shí)踐經(jīng)驗(yàn)的阿里技術(shù)專家,湯雪華老師,他指出:

    采用實(shí)體關(guān)系建模很容易看出對象與對象的關(guān)系,但僅此而已。數(shù)據(jù)并非對象,數(shù)據(jù)也無法看出行為,如果要依托實(shí)體關(guān)系建模來構(gòu)建系統(tǒng),往往需要開發(fā)者發(fā)揮自己的主觀抽象思維,根據(jù)客戶提供的資料或可用的原型,自行思考代碼的邏輯實(shí)現(xiàn)。
    但顯然,具備優(yōu)秀邏輯思維能力和設(shè)計思想的開發(fā)者鳳毛麟角,僅憑ER圖,代碼寫出來往往很糟糕。

    他認(rèn)為,采用領(lǐng)域驅(qū)動設(shè)計,產(chǎn)品架構(gòu)圖,系統(tǒng)架構(gòu)圖,領(lǐng)域模型圖,類圖,關(guān)鍵業(yè)務(wù)場景的交互時序圖,這些是必不可少的。

    • 產(chǎn)品架構(gòu)圖:列出產(chǎn)品功能,表現(xiàn)出產(chǎn)品模塊間的相關(guān)性。

    圖來自http://www.woshipm.com/pmd/1065960.html

    • 系統(tǒng)架構(gòu)圖:從技術(shù)層面列出系統(tǒng)模塊組成關(guān)系。

    原圖來自互聯(lián)網(wǎng)

    • 領(lǐng)域模型關(guān)系圖:反映出各領(lǐng)域模型間的相關(guān)性,限界上下文,聚合,和聚合根。

    來自https://102.alibaba.com/detail?id=174

  • 如何建模?
  • 如果說代碼語言是為了與其他開發(fā)者進(jìn)行溝通交流,那我們建立的各種軟件設(shè)計模型將極大的方便不同領(lǐng)域的人員進(jìn)行交流。建模也可以稱之為語言的一部分。利用uml建立類圖,是一種可以比較易于接受的方式。我們可以采用以下手段來建立領(lǐng)域模型。

    1)建立一個與實(shí)現(xiàn)綁定的模型。初版的模型也許很簡陋,但是它可以成為一個基礎(chǔ),然后在后期逐漸完善。

    2)建立一種基于模型的通用語言或表達(dá)形式和機(jī)制。通過通用語言讓參與項(xiàng)目的所有人理解模型。

    3)開發(fā)一個蘊(yùn)含豐富知識的模型。模型不是單純的數(shù)據(jù)結(jié)構(gòu),它更是各類知識的聚合體。

    4)提煉模型,模型應(yīng)該能在項(xiàng)目過程中動態(tài)改變,發(fā)現(xiàn)新的概念就加進(jìn)來,過時的概念就適時移除,避免臃腫。

    5)頭腦風(fēng)暴和實(shí)驗(yàn)。模型在于實(shí)踐和應(yīng)用,它需要項(xiàng)目參與者共同的努力,而頭腦風(fēng)暴是發(fā)揮集體智慧的良好方式。對模型進(jìn)行實(shí)驗(yàn)或者進(jìn)行場景的模擬,有利于讓模型更符合需求。

    當(dāng)然,對于領(lǐng)域?qū)<叶?#xff0c;不同類型的模型也許無法理解,例如類圖可能過于復(fù)雜,可以使用畫圖的形式,通過解釋性的圖形,甚至紙面上的圖,更能直觀的表現(xiàn)出領(lǐng)域的邏輯層次。

    這張來自TW分享的一張圖,就是一個基于.NET MVC的產(chǎn)品設(shè)計UML設(shè)計圖。

    建模也并非這篇博客所能講清楚的,包括筆者自己,也只是偶爾設(shè)計過用例圖,時序圖和類圖,可能需要在后期系統(tǒng)的學(xué)習(xí)一下。

    3)代碼實(shí)現(xiàn)

    回到最開始的那個三層架構(gòu)下的代碼示例,如果采用領(lǐng)域驅(qū)動設(shè)計,大概如下圖所示:

    回到開始那個示例代碼,如果采用DDD的代碼實(shí)現(xiàn),大概是這樣的:

    復(fù)制代碼

    /// <summary> /// 應(yīng)用服務(wù)層 /// </summary> public class OrderAppService {private OrderRepository _orderRepository;private UserInfoRepository _userInfoRepository;private ProductInfoRepository _productInfoRepository;public OrderAppService(OrderRepository orderRepository, UserInfoRepository userInfoRepository, ProductInfoRepository productInfoRepository){_orderRepository = orderRepository;_userInfoRepository = userInfoRepository;_productInfoRepository = productInfoRepository;?}/// <summary>/// 新增訂單/// </summary>/// <param name="userId"></param>/// <param name="productId"></param>/// <param name="count"></param>public void CreateOrder(int userId, int productId, int count){UserInfo userInfo = _userInfoRepository.Get(userId);ProductInfo productInfo = _productInfoRepository.Get(productId);if (userInfo != null && productInfo != null){//新訂單Order order = Order.CreateOrder(productId, userInfo.Address, userId, productInfo.Price, count);_orderRepository.Insert(order);}}/// <summary>/// 減少訂單數(shù)量/// </summary>/// <param name="orderId"></param>/// <param name="minusCount"></param>public void MinusOrder(int orderId, int minusCount){Order order = _orderRepository.Get(orderId);order.Minus(minusCount);_orderRepository.Update(order);}/// <summary>/// 增加訂單數(shù)量/// </summary>/// <param name="orderId"></param>/// <param name="minusCount"></param>public void AddOrder(int orderId, int addCount){Order order = _orderRepository.Get(orderId);order.Add(addCount);_orderRepository.Update(order);} }/// <summary>///訂單對象?/// </summary>public class Order{/// <summary>/// 主鍵/// </summary>public int Id { get; protected set; }/// <summary>/// 地址/// </summary>public string Address { get; protected set; }/// <summary>/// 用戶id/// </summary>public int UserId { get; protected set; }/// <summary>/// 產(chǎn)品id/// </summary>public int ProductId { get; protected set; }/// <summary>/// 數(shù)量/// </summary>public int Count { get; protected set; }/// <summary>/// 單價/// </summary>public double Price { get; protected set; }/// <summary>/// 總價/// </summary>public double TotalPrice { get; protected set; }/// <summary>/// 創(chuàng)建訂單/// </summary>/// <param name="productId"></param>/// <param name="address"></param>/// <param name="userId"></param>/// <param name="price"></param>/// <param name="count"></param>/// <returns></returns>public static Order CreateOrder(int productId, string address, int userId, double price, int count){return new Order(){Address = address,UserId = userId,TotalPrice = price * count,ProductId = productId,};}/// <summary>/// 新增/// </summary>/// <param name="count"></param>public void Add(int count){}/// <summary>/// 減少/// </summary>/// <param name="count"></param>public void Minus(int count){}}

    這段代碼,最主要的變化是如下幾點(diǎn):

  • 引入領(lǐng)域模型,在三層架構(gòu)的示例代碼中,我們建立了如下模型:
  • 這個模型是當(dāng)我們Entity Framework腳本時生成的實(shí)體模型,在業(yè)內(nèi)通常稱其為“貧血模型”。對人類來說,紅細(xì)胞負(fù)責(zé)把氧氣輸送到組織細(xì)胞,然后新陳代謝,產(chǎn)生ATP,產(chǎn)生動力。

    而“貧血模型”這個術(shù)語恰如其份的表現(xiàn)出這類模型雖然還能有效的工作,但是需要由其他對象來驅(qū)動其完成動作的含義。

    領(lǐng)域模型與貧血模型相比,更關(guān)注對象的行為,而關(guān)注行為的目的是創(chuàng)建帶有公共接口并與在現(xiàn)實(shí)世界觀察到的實(shí)體相似的對象,使得依照統(tǒng)一語言的名字和規(guī)則進(jìn)行建模變得更加容易。

  • 將原來的Order對象抽象化建模為一個DDD實(shí)體。DDD實(shí)體是一個包含數(shù)據(jù)(屬性)和行為(方法)的POCO對象。
  • 在《Microsoft .NET企業(yè)級應(yīng)用架構(gòu)實(shí)戰(zhàn)》書第9.2.2中指出了領(lǐng)域?qū)嶓w的特點(diǎn):

    定義明確的身份標(biāo)識。
    通過公共和非公共方法表示行為。
    通過只讀屬性暴露狀態(tài)。
    限制基元類型的使用,使用值對象代替。
    工廠方法優(yōu)于多個構(gòu)造函數(shù)。

  • 私有set或protected set:
  • 在示例代碼中將Order中的所有屬性設(shè)置為

    復(fù)制代碼

    public double TotalPrice { get; protected set; }

    這樣的目的是為了避免對該屬性的隨意更改,使得開發(fā)者在對屬性進(jìn)行操作過程中,多了一個環(huán)節(jié),即需要謹(jǐn)慎思考這樣的代碼修改,從行為角度來分析是否符合業(yè)務(wù)需要。
    在設(shè)計時實(shí)體時,開放set可能會帶來嚴(yán)重的副作用,例如影響實(shí)體的狀態(tài)。

    在張逸老師的《領(lǐng)域驅(qū)動設(shè)計實(shí)戰(zhàn),戰(zhàn)術(shù)篇第15課》中,作者指出:

    對象之間若要默契配合,形成良好的協(xié)作關(guān)系,就需要通過行為進(jìn)行協(xié)作,而不是讓參與協(xié)作的對象成為數(shù)據(jù)的提供者。
    《ThoughtWorks 軟件開發(fā)沉思錄》中的“對象健身操”提出了優(yōu)秀軟件設(shè)計的九條規(guī)則,其中最后一條提出:不使用任何 Getter/Setter/Property。
    作者 Jeff Bay 認(rèn)為:“如果可以從對象之外隨便詢問實(shí)例變量的值,那么行為與數(shù)據(jù)就不可能被封裝到一處。在嚴(yán)格的封裝邊界背后,真正的動機(jī)是迫使程序員在完成編碼之后,一定有為這段代碼的行為找到一個適合的位置,確保它在對象模型中的唯一性。”

    當(dāng)然,在實(shí)際開發(fā)過程中,有時并不一定把get方法也設(shè)置為protected,畢竟有時候還需要有所妥協(xié)。

  • 將創(chuàng)建方法從業(yè)務(wù)邏輯層,移動到了領(lǐng)域?qū)ο驩rder中的靜態(tài)工廠方法。
  • 復(fù)制代碼

    /// <summary> /// 創(chuàng)建訂單 /// </summary> /// <param name="productId"></param> /// <param name="address"></param> /// <param name="userId"></param> /// <param name="price"></param> /// <param name="count"></param> /// <returns></returns> public static Order CreateOrder(int productId, string address, int userId, double price, int count) {? ? ? ? return new Order(){Address = address,UserId = userId,TotalPrice = price * count,ProductId = productId,}; }

    創(chuàng)建過程應(yīng)該是一個非常嚴(yán)謹(jǐn)?shù)倪^程,而原來在業(yè)務(wù)邏輯層中初始化對象的方法,隨意性比較高,很容易就出現(xiàn)開發(fā)者在創(chuàng)建過程中將無關(guān)屬性賦值的現(xiàn)象。
    但如果把創(chuàng)建過程改成使用構(gòu)造方法,又可能會造成可讀性問題,而使用工廠方法,并創(chuàng)建一個受保護(hù)的構(gòu)造方法則不會造成這個擔(dān)憂。

  • 將訂單新增內(nèi)容和減少內(nèi)容從業(yè)務(wù)邏輯層移動到了領(lǐng)域?qū)ο笊?#xff0c;并封裝為方法。采用迪米卡法則,只暴露最小的參數(shù),每次只對最該賦值的屬性進(jìn)行操作,也容易約束開發(fā)者的操作。
  • 復(fù)制代碼

    /// <summary> /// 新增 /// </summary> /// <param name="count"></param> public void Add(int count) { }/// <summary> /// 減少 /// </summary> /// <param name="count"></param> public void Minus(int count) {}

    大概修改過程是最容易造成領(lǐng)域知識丟失的地方,而通過封裝為方法,使得這個過程得以以受控的形式進(jìn)行,有助于讓其他開發(fā)者通過暴露的方法。
    但這樣做要確保所使用的命名規(guī)范符合統(tǒng)一語言,否則會重蹈貧血模型的覆轍。當(dāng)然,在領(lǐng)域設(shè)計中,經(jīng)常會糾結(jié)于哪些行為應(yīng)該放在領(lǐng)域?qū)ο笾?#xff0c;可以參考這樣的規(guī)則:

    • 如果方法只處理實(shí)體的成員,它可能屬于這個實(shí)體。
    • 如果方法訪問相同聚合的其他實(shí)體或值對象,它可能屬于聚合根。
    • 如何方法里的代碼需要查詢或更新持久層,或者需要用到實(shí)體(或聚合)邊界以外的引用,它屬于領(lǐng)域服務(wù)方法。

    對比分析

    二者的對比

    筆者整理了一個簡單的圖表來表現(xiàn)二者的對比關(guān)系。顯然,三層架構(gòu)并非毫無優(yōu)勢,領(lǐng)域驅(qū)動設(shè)計也并非銀彈。

    ?三層架構(gòu)領(lǐng)域驅(qū)動設(shè)計
    業(yè)務(wù)識別方法結(jié)合瀑布模型,通過需求分析,形成數(shù)據(jù)字典,指導(dǎo)數(shù)據(jù)庫設(shè)計。團(tuán)隊(duì)協(xié)作形成統(tǒng)一語言,并從統(tǒng)一語言中提取術(shù)語,指導(dǎo)類、流程,變量,行為定義等。
    業(yè)務(wù)參與者具備IT知識的開發(fā)人員,業(yè)務(wù)人員只能提供需求,往往不能參與設(shè)計過程。由需求提供者或客戶、開發(fā)者、測試、產(chǎn)品經(jīng)理等組成的跨職能團(tuán)隊(duì)全力參與。
    建模方法實(shí)體關(guān)系建模為主,有時可以用UML以UML方法為主,畫圖為輔
    業(yè)務(wù)代碼分層業(yè)務(wù)代碼理論上應(yīng)該在業(yè)務(wù)邏輯層,但有時游離在控制器、業(yè)務(wù)邏輯層或數(shù)據(jù)訪問層,甚至受依賴的其他業(yè)務(wù)邏輯中業(yè)務(wù)代碼在領(lǐng)域?qū)?#xff0c;有時在領(lǐng)域?qū)ο笊?#xff0c;有時在領(lǐng)域服務(wù)中。
    修改代碼的難易程度隨時隨地想改就改需要遵循一定的設(shè)計原則或步驟、流程
    可維護(hù)性項(xiàng)目簡單時,易于維護(hù);復(fù)雜時,難于維護(hù)。掌握方法時,維護(hù)難度比較平滑。
    數(shù)據(jù)持久化在數(shù)據(jù)訪問層中完成,有時可以適當(dāng)復(fù)用;也有開發(fā)者將數(shù)據(jù)訪問層提取出倉儲的模板方法進(jìn)行復(fù)用。一般在倉儲層中實(shí)現(xiàn),且倉儲一般是基礎(chǔ)設(shè)施,意味著除特定場景外,基礎(chǔ)設(shè)施不會依賴于領(lǐng)域而二外定制行為。
    多業(yè)務(wù)邏輯的整合一般在業(yè)務(wù)邏輯層中實(shí)現(xiàn)一般在應(yīng)用服務(wù)層實(shí)現(xiàn)。
    可測試性比較難以加入測試代碼易于加入測試代碼;也可以根據(jù)UML使用TDD來進(jìn)行開發(fā)。

    該如何取舍?

    下圖這種流傳已久,同樣來自馬丁弗勒老爺子《企業(yè)架構(gòu)應(yīng)用模式》。

    表現(xiàn)了隨著軟件復(fù)雜度的逐漸提升,數(shù)據(jù)驅(qū)動設(shè)計和領(lǐng)域驅(qū)動設(shè)計模式兩種不同類型的設(shè)計模式的開發(fā)效率(時間)對比曲線。

    • 數(shù)據(jù)驅(qū)動設(shè)計建立了一個比較平滑的發(fā)展軌跡,但是隨著拐點(diǎn)的到來,將變得越來越為難以維護(hù),最終造出一個難以維護(hù)的“意大利面”。

    • 領(lǐng)域驅(qū)動設(shè)計,前期的起點(diǎn)確實(shí)比數(shù)據(jù)驅(qū)動設(shè)計要高很多,而且甚至在剛剛使用一段時間后,由于業(yè)務(wù)復(fù)雜度的提升,會迎來一個拐點(diǎn)。這個拐點(diǎn)有點(diǎn)像“鄧寧·克魯格效應(yīng)”中遇到的“絕望之谷”,讓開發(fā)者和管理層感覺有點(diǎn)力不從心,不少企業(yè)最終又拆掉了他們的領(lǐng)域驅(qū)動設(shè)計搭建的軟件;
    • 使用領(lǐng)域驅(qū)動設(shè)計,隨著復(fù)雜度的逐漸推移,軟件開發(fā)人員的信心越來越足,代碼自然也能夠不斷演進(jìn),平滑發(fā)展,。

    • 從長期來看,領(lǐng)域驅(qū)動建模將給復(fù)雜系統(tǒng)帶來更加高效的維護(hù)效能。

    總結(jié)

    本文介紹了三層架構(gòu)和領(lǐng)域驅(qū)動設(shè)計兩種不同的設(shè)計思想中如何實(shí)現(xiàn)業(yè)務(wù)邏輯代碼的過程,并對針對代碼的維護(hù)性問題進(jìn)行了分析。由于時間倉促,部分觀點(diǎn)、設(shè)計圖、代碼可能還不夠成熟,還請大家批評指正。

    下一篇將介紹ABP框架開發(fā)中的具體實(shí)踐步驟。

    總結(jié)

    以上是生活随笔為你收集整理的如何使用ABP进行软件开发(2) 领域驱动设计和三层架构的对比的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。