业务层——跨越边界传输数据
文章目錄
- 跨越邊界傳輸數(shù)據(jù)
- 分層架構(gòu)里的數(shù)據(jù)流
- 共享領(lǐng)域模型實(shí)體
- 在各層里的領(lǐng)域?qū)嶓w
- 為命令和查詢使用單一模型的危險(xiǎn)-補(bǔ)充
- 將來(lái)擴(kuò)展的可能約束
- 使用數(shù)據(jù)傳輸對(duì)象
- 數(shù)據(jù)傳輸對(duì)象概論
- AutoMapper和適配器
- 總結(jié)
- 笑到最后
跨越邊界傳輸數(shù)據(jù)
物理層意味著需要跨越的物理邊界,不管是進(jìn)程邊界還是機(jī)器邊界??缭竭吔缡且粋€(gè)昂貴的操作。觸及遠(yuǎn)程物理機(jī)器的代價(jià)比觸及同一臺(tái)機(jī)器的另一個(gè)進(jìn)程的更高。一個(gè)可以參考的經(jīng)驗(yàn)法則是跨越進(jìn)程邊界的調(diào)用比對(duì)應(yīng)的進(jìn)程內(nèi)調(diào)用要慢100倍。如果要通過網(wǎng)絡(luò)傳輸才能到達(dá)端點(diǎn)的話就更慢了。
一個(gè)調(diào)用是如何通過網(wǎng)線跨越邊界的?輕裝傳輸?還是背負(fù)一切傳輸?選擇最適合的方式跨越邊界(邏輯的或物理的)傳輸數(shù)據(jù)是應(yīng)用程序的業(yè)務(wù)部分要解決的另一個(gè)設(shè)計(jì)問題。
分層架構(gòu)里的數(shù)據(jù)流
上圖(跨越分層架構(gòu)各層的數(shù)據(jù)流的示意圖)展示了分層架構(gòu)里的一個(gè)相對(duì)抽象的數(shù)據(jù)流。
這4種模型在邏輯上都是不同的,但有時(shí)候它們可能是重合的。領(lǐng)域模型可以直接持久化到基礎(chǔ)設(shè)施層,在這種意義上,領(lǐng)域模型和數(shù)據(jù)模型通常是相同的。在ASP .NET MVC應(yīng)用程序里,輸入模型和視圖模型在控制器操作的GET和POST實(shí)現(xiàn)里通常是重合的。在CRUD系統(tǒng)里,所有模型都可能重合,也就是只有一個(gè)一一一它將會(huì)是MVC模式里的“模型”。
早在20世紀(jì)80年代剛被設(shè)計(jì)出來(lái)時(shí),MVC是一個(gè)應(yīng)用程序模式,可以用來(lái)架構(gòu)整個(gè)應(yīng)用程序。那是一體化系統(tǒng)的年代,被端到端創(chuàng)建成單一事務(wù)腳本。多邏輯層和多物理層系統(tǒng)的出現(xiàn)改變了MVC的角色,但沒有否定它的重要性。MVC仍是一個(gè)強(qiáng)大的模式,但單一模型的理念不再有效。MVC里的模型被定義為“在視圖里使用的數(shù)據(jù)"。這意味著今天的MVC基本上是一個(gè)表現(xiàn)模式。
共享領(lǐng)域模型實(shí)體
在遵循領(lǐng)域模型模式的分層架構(gòu)里,領(lǐng)域?qū)嶓w是最合適的輸入容器。領(lǐng)域?qū)嶓w是否適合向上傳
給用戶界面,并在需要時(shí)序列化它們穿越物理層?
此外,在數(shù)據(jù)驅(qū)動(dòng)的應(yīng)用程序里,如果你要把領(lǐng)域模型傳給表現(xiàn)層(某種自我跟蹤的實(shí)體),那
么貧血領(lǐng)域模型更加適合。
當(dāng)你在類里使用帶有方法的富模型(領(lǐng)域模型)時(shí),把領(lǐng)域?qū)嶓w傳給表現(xiàn)層沒有意義,因?yàn)檫@會(huì)
讓表現(xiàn)層得以實(shí)體里的那些方法。在DDD應(yīng)用程序里,表現(xiàn)層應(yīng)該有一個(gè)不同的表現(xiàn)模型,基于DTO
模型(在很多情況下是一個(gè)視圖模型),DTO是根據(jù)屏幕/頁(yè)面的需求而不是基于領(lǐng)域模型來(lái)設(shè)計(jì)的。
在各層里的領(lǐng)域?qū)嶓w
我們認(rèn)為在應(yīng)用程序?qū)泳幣诺慕M件和模塊之中傳遞領(lǐng)域模型的類是沒有問題的。只要數(shù)據(jù)傳輸
135
發(fā)生在邏輯層之間,就應(yīng)該沒有問題,不管是技術(shù)上的還是設(shè)計(jì)上的。如果數(shù)據(jù)傳輸發(fā)生在物理層
之間,你可能會(huì)遭遇序列化問題,尤其是領(lǐng)域?qū)嶓w構(gòu)成了一個(gè)錯(cuò)綜復(fù)雜的圖,里面還有循環(huán)引用。
在這種情況下,引入一些專門的數(shù)據(jù)傳輸對(duì)象針對(duì)一兩個(gè)場(chǎng)景進(jìn)行處理可能更加容易。
湖注意:確切地說,當(dāng)實(shí)體具有延遲加載屬性時(shí),在不同的邏輯層之間傳輸領(lǐng)域?qū)嶓w也可能面臨
問題。在這種情況下,當(dāng)接收實(shí)體的層試圖通過延遲加載屬性讀取數(shù)據(jù)時(shí),就會(huì)引發(fā)異常,因?yàn)閿?shù)
據(jù)還沒加載,而存儲(chǔ)環(huán)境已經(jīng)不再可用。
領(lǐng)域模型(Domain Model)實(shí)體包含的內(nèi)容與用戶界面(View Model)一致,可以在用戶界面使用Domain Model,省下一堆其他的數(shù)據(jù)傳輸類。原則上,在各個(gè)邏輯層和物理層上使用領(lǐng)域模型實(shí)體不但可以接受,在某些情況下更是合適的,但是,有一些可能的副作用需要考慮。
為命令和查詢使用單一模型的危險(xiǎn)-補(bǔ)充
從下一章開始我們會(huì)探討這個(gè)問題,在第10章“CQRS導(dǎo)論”和第11章“實(shí)現(xiàn)CQRS”里會(huì)有 更深入的討論。
就目前而言,從用戶界面引用領(lǐng)域?qū)嶓w可能需要表現(xiàn)層代碼充分利用領(lǐng)域?qū)嶓w內(nèi)建的行為。這個(gè)行為本質(zhì)上就是領(lǐng)域邏輯,而領(lǐng)域邏輯必須一直與業(yè)務(wù)規(guī)則保持一致,我們認(rèn)為表現(xiàn)層代碼有打破這個(gè)一致性的潛在風(fēng)險(xiǎn)。
將來(lái)擴(kuò)展的可能約束
由于現(xiàn)代應(yīng)用程序會(huì)擴(kuò)展新的前端。這可能需要為用戶界面提供數(shù)據(jù)的不同聚合,而這些聚合并不存在于領(lǐng)域模型里。如果不想為了滿足特定前端的需要而修改領(lǐng)域模型。可以添加專門的數(shù)據(jù)傳輸對(duì)象(DTO)。
使用數(shù)據(jù)傳輸對(duì)象
有時(shí)候,使用領(lǐng)域?qū)嶓w可能很方便;有時(shí)候,使用數(shù)據(jù)傳輸對(duì)象(DTO)會(huì)更好。沒有哪個(gè)解決方案總比其他的好。需要具體問題具體分析。
數(shù)據(jù)傳輸對(duì)象概論
數(shù)據(jù)傳輸對(duì)象專門用來(lái)在不同的物理層之間攜帶數(shù)據(jù)。DTO沒有行為,只是一個(gè)簡(jiǎn)單的get和set的容器,創(chuàng)建起來(lái)也相對(duì)不昂貴(比如說,它不需要單元測(cè)試)。作為一個(gè)簡(jiǎn)單容器,使用DTO的原因是它允許你打包多塊數(shù)據(jù),在單次往返里傳輸所有數(shù)據(jù)。DTO與生俱來(lái)就是可序列化對(duì)象。在涉及遠(yuǎn)程組件時(shí)通常都推薦用它。但是,我們希望從一個(gè)
更廣泛的角度來(lái)看待DTO,考慮在不同的邏輯層之間使用。
2.DTO與領(lǐng)域?qū)嶓w
DTO的通常用法是,舉個(gè)例子,當(dāng)你需要同時(shí)顯示或處理訂單和客戶信息,但實(shí)際處理所需的信息只用到訂單和客戶實(shí)體上的一部分屬性。DTO可以把復(fù)雜的層次結(jié)構(gòu)簡(jiǎn)化成簡(jiǎn)單的數(shù)據(jù)容器,只包含必要的數(shù)據(jù)。
圖7.4在跨越不同的物理層和邏輯層傳輸數(shù)據(jù)時(shí)的DTO與領(lǐng)域?qū)嶓w
在不同的邏輯層共享領(lǐng)域?qū)嶓w通常是沒問題的,而且可以最大限度減少牽涉的類的總量。從單純的設(shè)計(jì)角度來(lái)看,使用DTO是“完美的”解決方案,它保證了接口組件之間最大程度的解耦,也保證了可擴(kuò)展性。
但是,完整的DTO解決方案毫無(wú)疑問會(huì)導(dǎo)致各個(gè)VisualStudio項(xiàng)目出現(xiàn)大量的小類。一方面,你需要通過文件夾和命名仝間來(lái)管理和組織那些數(shù)量可觀的類。另一方面,你也不得不面對(duì)把數(shù)據(jù)加載到DTO以及從DTO提取數(shù)據(jù)的代價(jià)。
AutoMapper和適配器
使用DTO真正的代價(jià)就是數(shù)據(jù)填充以及讀取數(shù)據(jù)。AutoMapper可以把屬性從領(lǐng)域?qū)嶓w復(fù)制到DTO,反之亦然。使用示例
// 源類型和目標(biāo)類型之間創(chuàng)建一個(gè)映射,啟動(dòng)映射過程,用源類型的實(shí)例里的數(shù)據(jù)填充目標(biāo)類型的實(shí)例 mapper.CreateMap<YourSourceType,YourDtOType>(); // 通過Map方法調(diào)用它 var dto=Mapper.Map<YourDtOType>(sourceObject);AutoMapper這類自動(dòng)化工具的缺點(diǎn):
當(dāng)用它從實(shí)體創(chuàng)建DTO時(shí),無(wú)可避免地要遍歷整個(gè)實(shí)體圖,為此必須把整個(gè)實(shí)體圖從存儲(chǔ)讀到內(nèi)存里?;蛟S,通過領(lǐng)域服務(wù)返回現(xiàn)成的DTO更加便捷。
注意:另一個(gè)傳輸數(shù)據(jù)的方案是使用IQueryabIe對(duì)象。一個(gè)極具爭(zhēng)議但正在普及的做法是從數(shù)據(jù)倉(cāng)儲(chǔ)返回IQueryable。
IQueryable是LINQ的核心接口,它所做的是針對(duì)支持LINQ的數(shù)據(jù)源提供計(jì)算查詢的功能。
從倉(cāng)儲(chǔ)返回IQueryable的一個(gè)原因是讓上層可以輕易創(chuàng)建不同類型的查詢。這樣做可以保持倉(cāng)儲(chǔ)接口輕薄,同時(shí)減少使用DTO的需要,因?yàn)槟承〥TO可以是匿名類型。雖然DTO是從查詢創(chuàng)建的,但它們屬于特定的層,隔離在層的上下文里,并且易于管理。
總結(jié)
經(jīng)典的二元論:做正確的事與正確地做事。它們沒有誰(shuí)比誰(shuí)更優(yōu)先,二者都是重要的觀點(diǎn)。
正確地做事是表現(xiàn)層應(yīng)該關(guān)心的事。正確地做事的核心理念是效率:以優(yōu)化的方式實(shí)現(xiàn)任務(wù),快速且流暢。做正確的事則是業(yè)務(wù)層應(yīng)該關(guān)心的事。做正確的事的核心理念是效益和達(dá)成目標(biāo)。軟件系統(tǒng)的終極目標(biāo)是滿足需求和忠實(shí)再現(xiàn)領(lǐng)域空間。
為了使得領(lǐng)域建模更加高效,領(lǐng)域模型等模式和IDDD等方法學(xué)是必不可少的。這些方法學(xué)對(duì)多年以來(lái)被稱為業(yè)務(wù)層的東西進(jìn)行了改造。我們引入了應(yīng)用程序?qū)雍皖I(lǐng)域?qū)?#xff0c;同時(shí)把數(shù)據(jù)訪問和其他基礎(chǔ)設(shè)施組件(如郵件服務(wù)器、文件系統(tǒng)和外部服務(wù))都隔離到基礎(chǔ)設(shè)施層里。
我們?cè)谶@里提及的領(lǐng)域模型模式都是比較通用的內(nèi)容,在下一章里,我們將會(huì)著重探討領(lǐng)域驅(qū)
動(dòng)設(shè)計(jì)。
笑到最后
一個(gè)成功的業(yè)務(wù)層需要敏銳的觀察和建模。它還需要通過盡可能簡(jiǎn)單的方式做事的能力?!昂?jiǎn)約而不簡(jiǎn)單”是我們最喜歡的真言之一。此外,我們?yōu)楸菊铝谐龅牡谝粭l墨菲定律是:如果你有任何復(fù)雜的東西可以工作,那么你可以肯定過去也有某些簡(jiǎn)單的東西可以工作。
一個(gè)可以工作的復(fù)雜系統(tǒng)總是從一個(gè)可以工作的簡(jiǎn)單系統(tǒng)進(jìn)化而來(lái)的。
在軟件可靠性上的投入會(huì)持續(xù)增加,直到超出錯(cuò)誤的可能代價(jià)。
理論和實(shí)踐在理論上沒有區(qū)別,但在實(shí)踐上有。
總結(jié)
以上是生活随笔為你收集整理的业务层——跨越边界传输数据的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#将unix时间戳转换成.net的Da
- 下一篇: AppleScript