軟件架構編年史:EBI 架構

覃宇,Android 開發者 / ThoughtWorks 技術教練 // 譯者,熱衷於探究軟件開發的方方面面,從端到雲,從工具到實踐。喜歡通過翻譯來學習和分享知識,譯作有《Kotlin 實戰》、《領域驅動設計精粹》、《Serverless 架構:無服務器應用與 AWS Lambda》和《雲原生安全與 DevOps 保障》。

EBI 架構 (Entity-Boundary-Interactor,實體 - 邊界 - 交互器) 架構因爲 Robert C. Martin 關於整潔架構 (我會在後續的文章中介紹) 的講座而被人熟知。

然而,Ivar Jacobson 早在 1992 年就在他的著作 Object-Oriented Software Engineering: A use case driven approach 中提出了這個模式。那時,Jacobson 實際上把它叫做實體 - 接口 - 控制 (Entity-Interface-Control),但是後來改成了 EBI,避免“接口” 和編程語言中的結構 “接口” 混淆,以及 “控制” 和 MVC 中的控制器混淆。

◐  實體

實體對象承載着系統使用的數據與所有和這些數據天然耦合在一起的行爲。每一個實體對象都代表着一個和問題域相關的概念,以及它承載的身份和可恢復的 (持久化) 數據。Jacobson 告訴我們,實體對象應該包含和對象自己變同時發生變化的邏輯,例如,如果它的數據結構發生變化,這些數據上的操作也需要改變,因此它們也應該放在實體內。

有意思的是,早在 1992 年,Jacobson 就作出瞭如下警告:

新手也許有時會讓實體對象只攜帶數據,把所有動態的行爲放到控制對象中 [...]。然而,這是應該避免的。[...] 許多行爲反而應該放在實體對象中。——Ivar Jacobson 1992, pp. 134

這就是我們現在所知的 “貧血實體”。

◐  邊界 (接口)

邊界對象是對系統接口的建模。

[…] 和系統接口有關的一切都應該放在接口對象中——Ivar Jacobson 1992, pp. 134

所有依賴系統環境 (工具和傳達機制) 的功能都屬於邊界對象。

任何角色和系統的交互都要經過邊界對象。如 Jacobson 所述,角色可以是像客戶或管理員 (操作員) 這樣的人類用戶,也可以是像告警、打印機或者第三方 API 這樣的非人類“用戶”。

回味一下邊界的概念,再看看圖 7.14,把其中的四個邊界想像成六個,我不禁聯想到 2005 年才提出的端口和適配器架構 (我將在後續的文章中介紹),整整晚了 13 年。

◐ 交互器 (控制)

交互器對象承載了和其它任何對象類型天然無關的行爲。

這些行爲通常由對實體一些操作組成,最後將某個結果返回給邊界對象。

邊界對象和實體對象挑剩下的行爲會被放在控制對象中。—— Ivar Jacobson 1992, pp. 185

這意味着所有不適合放在邊界或實體的行爲都會被放在一個或多個控制對象中。

因此,Jacobson 認爲控制對象不僅僅是編排用例的對象,也包括那些擁有和用例有關的行爲的對象,它們既不是邊界也不是實體。

根據我的經驗,我認爲他稱爲交互器的對象就是我稱爲應用服務 (編排用例) 和領域服務 (包含領域行爲但不是實體) 的對象。

位於中間的交互器對象地位十分重要,原因在於,如果去掉它們,特定用例的邏輯就會被放到實體中。然而,實體會被多個用例使用,因此它們會有通用的用法。特定用例的邏輯如果被放到了實體中,就能被多個邊界使用,這些邊界最終會把這個實體當成通用邏輯。而我們就會不得不修改這個實體讓它適應另一個邊界,這會增加實體的複雜性而且可能會破壞用到該實體的其它用例。

◐ 爲什麼是三種對象類型?

當時,Jacobson 宣稱其它的 OO 方法會把所有的職責都放在實體本身,但是他 (和他的支持者) 則傾向與將這些職責分散到三種對象類型中,因爲這樣能讓系統更適應變化。

[…] 所有的系統都會發生變化。因此,只有所有的變化都發生在局部,穩定性纔會存在,也就是說,變化最好只能影響系統中的一個對象。—— Ivar Jacobson 1992, pg. 135

通過職責的封裝將系統的變化控制在局部,就是 EBI 架構的目標。我們仔細思考一下,Jacobson 只是沒有直接說出十年之後由 Robert C. Martin 在他的 “Agile Software Development, Principles, Patterns, and Practices” 一書中提出的單一職責原則罷了。

◐ 總結

和 MVC 模式中的 Model 代表着整個後端(包括所有實體、服務和它們之間的關係在內的一切)一樣,EBI 模式將邊界看作是和外部世界的完整連接,而不僅僅是一個視圖、一個控制器或是一個接口 (這裏指的是編程語言結構的接口)。邊界代表了對應着 MVC 中的 View 和 Controller 的整個展現層。EBI 中的實體代表了承載着數據及其行爲的真正實體,而交互器對象代表了展現層和實體之間的連接,也就是我所謂的應用服務和領域服務。

EBI 模式關注後端而 MVC 更關注前端。它們不能互相取代,它們是對方的補充。如果把它們放在一個模式中,我們可以把它叫做視圖 - 控制器 - 交互器 - 實體 (View-Controller-Interactor-Entity)。

◐ 引用來源

**張逸注:**不得不說,現在回頭看 Jacobson 提出的 EBI,真的是遵循了極簡主義,用最簡單的模型說清楚了對象範式進行建模的本質。不錯,六邊形架構似乎更爲對稱,然則,端口和適配器二者結合起來其實就是邊界對象要做的工作:端口是邊界的抽象,適配器是邊界的實現。

我個人提出的菱形對稱架構脫胎於整潔架構思想與六邊形架構,實際上也可以認爲是對 DDD 中上下文映射模式的運用,北向網關是開放主機服務,南向網關是防腐層,正是因爲菱形對稱架構的思想更貼近於 DDD,與限界上下文也更加匹配,我纔不揣冒昧地在我的書中提出了自己總結的模式。

從本質上講,無論菱形對稱架構的北向網關還是南向網關,其實都是 EBI 中的邊界對象。本文原作者認爲應用服務是控制對象(交互器),我並不這麼認爲。應用服務在面向進程內的調用時,它扮演的角色其實與作爲支持跨進程調用的遠程服務沒有什麼區別,都是邊界。領域服務纔是控制對象。

結合菱形對稱架構與業務服務,我總結了角色構造型,它與 EBI 的映射關係如下所示:

我將資源庫放到了南向網關的端口,這一做法並不符合 Eric 對資源庫的定義(他將資源庫放到了領域層);然而,根據邊界對象的定義——“所有依賴系統環境 (工具和傳達機制) 的功能都屬於邊界對象”——資源庫是對資源訪問的一種抽象,將其視爲邊界對象更加合理,本質上,它和訪問第三方接口的客戶端,以及發佈事件的發佈者並無本質差別。

☼ 屐痕處處:2021 年 4 月 20 日攝於成都杜甫草堂旁浣花溪。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/uEn4X7TMfMJl3ofRM7FYOA