業務層——跨越邊界傳輸數據

跨越邊界傳輸數據

物理層意味着需要跨越的物理邊界,不管是進程邊界還是機器邊界。跨越邊界是一個昂貴的操作。觸及遠程物理機器的代價比觸及同一臺機器的另一個進程的更高。一個可以參考的經驗法則是跨越進程邊界的調用比對應的進程內調用要慢100倍。如果要通過網絡傳輸才能到達端點的話就更慢了。
一個調用是如何通過網線跨越邊界的?輕裝傳輸?還是揹負一切傳輸?選擇最適合的方式跨越邊界(邏輯的或物理的)傳輸數據是應用程序的業務部分要解決的另一個設計問題。

分層架構裏的數據流

在這裏插入圖片描述
上圖(跨越分層架構各層的數據流的示意圖)展示了分層架構裏的一個相對抽象的數據流。

  1. 數據從用戶界面以InputModel的形式穿過應用程序層。
  2. 根據被請求的操作,應用程序層可能需要使用InputModel的內容創建領域實體(比如說,從提供的信息創建一個新的訂單)。
  3. 在分層的領域模型系統裏,持久化通常意味着把領域實體轉換成物理數據模型(通常是關係型數據模型)。
  4. 在回來的路上,在請求查詢時,從數據模型讀到的內容首先會被轉換成領域實體圖,然後適配進視圖模型給用戶界面渲染。

這4種模型在邏輯上都是不同的,但有時候它們可能是重合的。領域模型可以直接持久化到基礎設施層,在這種意義上,領域模型和數據模型通常是相同的。在ASP .NET MVC應用程序裏,輸入模型和視圖模型在控制器操作的GET和POST實現裏通常是重合的。在CRUD系統裏,所有模型都可能重合,也就是隻有一個一一一它將會是MVC模式裏的“模型”。

早在20世紀80年代剛被設計出來時,MVC是一個應用程序模式,可以用來架構整個應用程序。那是一體化系統的年代,被端到端創建成單一事務腳本。多邏輯層和多物理層系統的出現改變了MVC的角色,但沒有否定它的重要性。MVC仍是一個強大的模式,但單一模型的理念不再有效。MVC裏的模型被定義爲“在視圖裏使用的數據"。這意味着今天的MVC基本上是一個表現模式。

共享領域模型實體

在遵循領域模型模式的分層架構裏,領域實體是最合適的輸入容器。領域實體是否適合向上傳
給用戶界面,並在需要時序列化它們穿越物理層?
此外,在數據驅動的應用程序裏,如果你要把領域模型傳給表現層(某種自我跟蹤的實體),那
麼貧血領域模型更加適合。
當你在類裏使用帶有方法的富模型(領域模型)時,把領域實體傳給表現層沒有意義,因爲這會
讓表現層得以實體裏的那些方法。在DDD應用程序裏,表現層應該有一個不同的表現模型,基於DTO
模型(在很多情況下是一個視圖模型),DTO是根據屏幕/頁面的需求而不是基於領域模型來設計的。

在各層裏的領域實體

我們認爲在應用程序層編排的組件和模塊之中傳遞領域模型的類是沒有問題的。只要數據傳輸
135

發生在邏輯層之間,就應該沒有問題,不管是技術上的還是設計上的。如果數據傳輸發生在物理層
之間,你可能會遭遇序列化問題,尤其是領域實體構成了一個錯綜複雜的圖,裏面還有循環引用。
在這種情況下,引入一些專門的數據傳輸對象針對一兩個場景進行處理可能更加容易。
湖注意:確切地說,當實體具有延遲加載屬性時,在不同的邏輯層之間傳輸領域實體也可能面臨
問題。在這種情況下,當接收實體的層試圖通過延遲加載屬性讀取數據時,就會引發異常,因爲數
據還沒加載,而存儲環境已經不再可用。

領域模型(Domain Model)實體包含的內容與用戶界面(View Model)一致,可以在用戶界面使用Domain Model,省下一堆其他的數據傳輸類。原則上,在各個邏輯層和物理層上使用領域模型實體不但可以接受,在某些情況下更是合適的,但是,有一些可能的副作用需要考慮。

爲命令和查詢使用單一模型的危險-補充

從下一章開始我們會探討這個問題,在第10章“CQRS導論”和第11章“實現CQRS”裏會有 更深入的討論。

就目前而言,從用戶界面引用領域實體可能需要表現層代碼充分利用領域實體內建的行爲。這個行爲本質上就是領域邏輯,而領域邏輯必須一直與業務規則保持一致,我們認爲表現層代碼有打破這個一致性的潛在風險。

將來擴展的可能約束

由於現代應用程序會擴展新的前端。這可能需要爲用戶界面提供數據的不同聚合,而這些聚合並不存在於領域模型裏。如果不想爲了滿足特定前端的需要而修改領域模型。可以添加專門的數據傳輸對象(DTO)。

使用數據傳輸對象

有時候,使用領域實體可能很方便;有時候,使用數據傳輸對象(DTO)會更好。沒有哪個解決方案總比其他的好。需要具體問題具體分析。

數據傳輸對象概論

數據傳輸對象專門用來在不同的物理層之間攜帶數據。DTO沒有行爲,只是一個簡單的get和set的容器,創建起來也相對不昂貴(比如說,它不需要單元測試)。作爲一個簡單容器,使用DTO的原因是它允許你打包多塊數據,在單次往返里傳輸所有數據。DTO與生俱來就是可序列化對象。在涉及遠程組件時通常都推薦用它。但是,我們希望從一個
更廣泛的角度來看待DTO,考慮在不同的邏輯層之間使用。

2.DTO與領域實體
DTO的通常用法是,舉個例子,當你需要同時顯示或處理訂單和客戶信息,但實際處理所需的信息只用到訂單和客戶實體上的一部分屬性。DTO可以把複雜的層次結構簡化成簡單的數據容器,只包含必要的數據。

在這裏插入圖片描述

圖7.4在跨越不同的物理層和邏輯層傳輸數據時的DTO與領域實體

在不同的邏輯層共享領域實體通常是沒問題的,而且可以最大限度減少牽涉的類的總量。從單純的設計角度來看,使用DTO是“完美的”解決方案,它保證了接口組件之間最大程度的解耦,也保證了可擴展性。

但是,完整的DTO解決方案毫無疑問會導致各個VisualStudio項目出現大量的小類。一方面,你需要通過文件夾和命名仝間來管理和組織那些數量可觀的類。另一方面,你也不得不面對把數據加載到DTO以及從DTO提取數據的代價。

AutoMapper和適配器

使用DTO真正的代價就是數據填充以及讀取數據。AutoMapper可以把屬性從領域實體複製到DTO,反之亦然。使用示例

// 源類型和目標類型之間創建一個映射,啓動映射過程,用源類型的實例裏的數據填充目標類型的實例
mapper.CreateMap<YourSourceType,YourDtOType>();
// 通過Map方法調用它
var dto=Mapper.Map<YourDtOType>(sourceObject);

AutoMapper這類自動化工具的缺點:
當用它從實體創建DTO時,無可避免地要遍歷整個實體圖,爲此必須把整個實體圖從存儲讀到內存裏。或許,通過領域服務返回現成的DTO更加便捷。

注意:另一個傳輸數據的方案是使用IQueryabIe對象。一個極具爭議但正在普及的做法是從數據倉儲返回IQueryable。
IQueryable是LINQ的核心接口,它所做的是針對支持LINQ的數據源提供計算查詢的功能。
從倉儲返回IQueryable的一個原因是讓上層可以輕易創建不同類型的查詢。這樣做可以保持倉儲接口輕薄,同時減少使用DTO的需要,因爲某些DTO可以是匿名類型。雖然DTO是從查詢創建的,但它們屬於特定的層,隔離在層的上下文裏,並且易於管理。

總結

經典的二元論:做正確的事與正確地做事。它們沒有誰比誰更優先,二者都是重要的觀點。

正確地做事是表現層應該關心的事。正確地做事的核心理念是效率:以優化的方式實現任務,快速且流暢。做正確的事則是業務層應該關心的事。做正確的事的核心理念是效益和達成目標。軟件系統的終極目標是滿足需求和忠實再現領域空間。

爲了使得領域建模更加高效,領域模型等模式和IDDD等方法學是必不可少的。這些方法學對多年以來被稱爲業務層的東西進行了改造。我們引入了應用程序層和領域層,同時把數據訪問和其他基礎設施組件(如郵件服務器、文件系統和外部服務)都隔離到基礎設施層裏。

我們在這裏提及的領域模型模式都是比較通用的內容,在下一章裏,我們將會着重探討領域驅
動設計。

笑到最後

一個成功的業務層需要敏銳的觀察和建模。它還需要通過儘可能簡單的方式做事的能力。“簡約而不簡單”是我們最喜歡的真言之一。此外,我們爲本章列出的第一條墨菲定律是:如果你有任何複雜的東西可以工作,那麼你可以肯定過去也有某些簡單的東西可以工作。
一個可以工作的複雜系統總是從一個可以工作的簡單系統進化而來的。
在軟件可靠性上的投入會持續增加,直到超出錯誤的可能代價。
理論和實踐在理論上沒有區別,但在實踐上有。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章