前言
了解清晰架構之前需要大家先熟悉以下常見架構方案:
EBI架構(Entity-Boundary-Interactor Architecture)
領域驅動設計(Domain-Driven Design)
埠與配接器架構(Ports & Adapters Architecture,又稱為六邊形架構)
洋蔥架構(Onion Architecture)
整潔架構(Clean Architecture)
事件驅動架構(Event-Driven Architecture)
命令查詢職責分離模式(CQRS,即Command Query Responsibility Segregation)
面向服務的架構(Service Oriented Architecture)
清晰架構(Explicit Architecture,直譯為顯式架構)是將上述架構的部分優勢整合之后產生的另一種架構,因其2017年已經出現,已經不算是一種新的架構,實際應用的專案尚且較少,以下主要介紹架構的形成及各步驟的意義,
1 架構演化程序
1.1 系統的基本構建塊
埠和配接器架構明確地識別出了一個系統中的三個基本代碼構建塊:
- 運行用戶界面所需的構建塊;
- 系統的業務邏輯,或者應用核心;
- 基礎設施代碼,
1.2 工具
在遠離【應用核心】的地方,有一些應用會用到的工具,例如資料庫引擎、搜索引擎、Web 服務器或者命令列控制臺(雖然最后兩種工具也是傳達機制),
把命令列控制臺和資料庫引擎都是應用使用的工具,關鍵的區別在于,命令列控制臺和 Web 服務器告訴我們的應用它要做什么,而資料庫引擎是由我們的應用來告訴它做什么,
1.3 將傳達機制和工具連接到應用核心
連接工具和應用核心的代碼單元被稱為配接器(源自埠和配接器架構),配接器有效地實作了讓業務邏輯和特定工具之間可以相互通信的代碼,
“告知我們的應用應該做什么”的配接器被稱為主配接器或主動配接器,而那些“由我們的應用告知它該做什么”的配接器被稱為從配接器或者被動配接器,
1.3.1 埠
配接器需要按照應用核心某個特定的入口的要求來創建,即埠,在大多數語言里最簡單的形式就是介面,但實際上也可能由多個介面和 DTO 組成,
埠(介面)位于業務邏輯內部,而配接器位于其外部,這一點要特別注意,要讓這種模式按照設想發揮作用,埠按照應用核心的需要來設計而不是簡單地套用工具的 API,
1.3.2 主配接器或主動配接器
主配接器或主動配接器包裝埠并通過它告知應用核心應該做什么,它們將來自傳達機制的資訊轉換成對應用核心的方法呼叫,
換句話說,我們的主動配接器就是 Controller 或者控制臺命令,它們需要的介面(埠)由其他類實作,這些類的物件通過構造方法注入到 Controller 或者控制臺命令,
再舉一個更具體的例子,埠就是 Controller 需要的 Service 介面或者 Repository 介面,Service、Repository 或 Query 的具體實作被注入到 Controller 供 Controller 使用,
此外,埠還可以是命令總線介面或者查詢總線介面,這種情況下,命令總線或者查詢總線的具體實作將被注入到 Controller 中, Controller 將創建命令或查詢并傳遞給相應的總線,
1.3.3 從配接器或被動配接器
和主動配接器包裝埠不同,被動配接器實作一個埠(介面)并被注入到需要這個埠的應用核心里,
舉個例子,假設有一個需要存盤資料的簡單應用,我們創建了一個符合應用要求的持久化介面,這個介面有一個保存資料陣列的方法和一個根據 ID 從表中洗掉一行的方法,介面創建好之后,無論何時應用需要保存或洗掉資料,都應該使用實作了這個持久化介面的物件,而這個物件是通過構造方法注入的,
現在我們創建了一個專門針對 MySQL 實作了該介面的配接器,它擁有保存陣列和洗掉表中一行資料的方法,然后在需要使用持久化介面的地方注入它,
如果未來我們決定更換資料庫供應商,比如換成 PostgreSQL 或者 MongoDB,我們只用創建一個專門針對 PostgreSQL 實作了該介面的配接器,在注入時用新配接器代替舊配接器,
1.3.4 控制反轉
這種模式有一個特征,配接器依賴特定的工具和特定的埠(它需要提供介面的特定實作),但業務邏輯只依賴按照它的需求設計的埠(介面),它并不依賴特定的配接器或工具,
換句話說,配接器根據使用的工具不同可以靈活變更,但是業務邏輯產生的介面基本不會變化,
這意味著依賴的方向是由外向內的,這就是架構層面的控制反轉原則,
再一次強調,埠按照應用核心的需要來設計而不是簡單地套用工具的 API,
1.4 應用核心的結構
洋蔥架構采用了 DDD 的分層,將這些分層融合進了埠和配接器架構,這種分層為位于埠和配接器架構“六邊形”內的業務邏輯帶來一種結構組織,和埠與配接器架構一樣,依賴的方向也是由外向內,
1.4.1 應用層
在應用中,由一個或多個用戶界面觸發的應用核心中的程序就是用例,例如,在一個 CMS 系統中,我們可以提供普通用戶使用的應用 UI、CMS 管理員使用的獨立的 UI、命令列 UI 以及 Web API,這些 UI(應用)可以觸發的用例可能是專門為它設計的,也可以是多個 UI 復用的,
用例定義在應用層中,這是 DDD 提供的第一個被洋蔥架構使用的層,
這個層包括了應用服務(以及它們的介面),也包括了埠與配接器架構中的介面,例如 ORM 介面、搜索引擎介面、訊息介面等等,如果我們使用了命令總線和查詢總線,命令和查詢分別對應的處理程式也屬于這一層,
1.4.2 領域層
繼續向內一層就是領域層,這一層中的物件包含了資料和操作資料的邏輯,它們只和領域本身有關,獨立于呼叫這些邏輯的業務程序,它們完全獨立,對應用層完全無感知,
1.領域服務
我們偶爾會碰到某種涉及不同物體的領域邏輯,當然,無論物體是否相同,直覺告訴我們這種領域邏輯并不屬于這些物體,這種邏輯不是這些物體的直接責任,
所以,我們的第一反應也許是把這些邏輯放到物體外的應用服務中,這意味著這些領域邏輯就不能被其它的用例復用:領域邏輯應該遠離應用層,
解決方法是創建領域服務,它的作用是接收一組物體并對它們執行某種業務邏輯,領域服務屬于領域層,因此它并不了解應用層中的類,比如應用服務或者 Repository,另一方面,它可以使用其他領域服務,當然還可以使用領域模型物件,
2.領域模型
在架構的正中心,是完全不依賴外部任何層次的領域模型,它包含了那些表示領域中某個概念的業務物件,這些物件的例子首先就是物體,還有值物件、列舉以及其它領域模型中用到的任何物件,
領域事件也“活在”領域模型中,當一組特定的資料發生變化時就會觸發這些事件,而這些事件會攜帶這些變化的資訊,換句話說,當物體變化時,就會觸發一個領域事件,它攜帶著發生變化的屬性的新值,這些事件可以完美地應用于事件溯源,
1.5 組件
目前為止,我們都是使用層次來劃分代碼,但這是細粒度的代碼隔離,根據 Robert C. Martin 在尖叫架構中表達的觀點,按照子域和限界背景關系對代碼進行劃分這種粗粒度的代碼隔離同樣重要,這通常被叫做“按特性分包”或者“按組件分包”,和“按層次分包”相呼應,
我是“按組件分包”方式的堅定擁護者,在此我厚著臉皮將 Simon Brown 按組件分包的示意圖做了如下修改:
這些代碼塊在前面描述的分層基礎上再進行了“橫切”,它們是應用的組件(譯),
組件的例子包括認證、授權、賬單、用戶、評論或帳號,而它們總是都和領域相關,像認證和授權這樣的限界背景關系應該被看作外部工具,我們應該為它們創建配接器,把它們隱藏在某個埠之后,
1.5.1 組件解耦
與細粒度的代碼單元(類、介面、特質、混合等等)一樣,粗粒度的代碼單元(組件)也會從高內聚低耦合中受益,
我們使用依賴注入(通過將依賴注入類而不是在類內部初始化依賴)以及依賴倒置(讓類依賴抽象,即介面和抽象類,而不是具體類)來解耦類,這意味著類不用知道它要使用的具體類的任何資訊,不用參考所依賴的類的完全限定類名,
以同樣的方式完全解耦組件意味著組件不會直接了解其它任何組件的資訊,換句話說,它不會參考任何來自其它組件的細粒度的代碼單元,甚至都不會參考介面!這意味著依賴注入和依賴倒置對組件解耦是不夠用的,我們還需要一些架構層級的結構,我們需要事件、共享內核、最終一致性甚至發現服務!
1.觸發其它組件的邏輯
當一個組件(組件 A)中有事情發生需要另一個組件(組件B)做些什么時,我們不能簡單地從組件 A 直接呼叫組件 B 中的類/方法,因為這樣 A 就和 B 耦合在一起了,
但是我們可以讓 A 使用事件派發器,派發一個領域事件,這個事件將會投遞給任何監聽它的組件,例如 B,然后 B 的事件監聽器會觸發期望的操作,這意味著組件 A 將依賴事件派發器,但和 B 解耦了,
然而,如果事件本身“活在” A 中,這將意味著 B 知道了 A 的存在,就和 A 存在耦合,要去掉這個依賴,我們可以創建一個包含應用核心功能的庫,由所有組件共享,這就是共享內核,這意味著兩個組件都依賴共享內核,而它們之間卻沒有耦合,共享內核包含了應用事件和領域事件這樣的功能,而且還包含規格物件,以及其它任何有理由共享的東西,記住共享內核的范圍應該盡可能的小,因為它的任何變化都會影響所有應用組件,
而且,如果我們的系統是語言異構的,比如使用不同語言撰寫的微服務生態,共享內核需要做到與語言無關的,這樣它才能被所有組件理解,無論它們是用哪種語言撰寫的,例如,共享內核應該包含像 JSON 這樣無關語言的事件描述(例如,名稱、屬性,也許還有方法,盡管它們對規格物件來說更有意義)而不是事件類,這樣所有組件或者微服務都可以決議它,還可以自動生成各自的具體實作,
這種方法既適用于單體應用,也適用于像微服務生態系統這樣的分布式應用,然而,這種方法只適用于事件異步投遞的情況,在需要即時完成觸發其它組件邏輯的背景關系中并不適用!組件 A 將需要向組件 B 發起直接的呼叫,例如HTTP,這種情況下,要解耦組件,我們需要一個發現服務,A 可以詢問它得知請求應該發送到哪里才能觸發期望的操作,又或是向發現服務發起請求并由發現服務將請求代理給相關服務并最侄訓傳回應給請求方,這種方法會把組件和發現服務耦合在一起,但會讓組件之間解耦,例如jsf,
2.從其它組件獲得資料
原則上,組件不允許修改不“屬于”它的資料,但可以查詢和使用任何資料,
1)組件之間共享資料存盤
當一個組件需要使用屬于其它組件的資料時,比如說賬單組件需要使用屬于賬戶組件的客戶名字,賬單組件會包含一個查詢物件,可以在資料存盤中查詢該資料,簡單的說就是賬單組件知道任何資料集,但它只能通過查詢只讀地使用不“屬于”它的資料,
2)按組件隔離的資料存盤
這種情況下,這種模式同樣有效,但資料存盤層面的復雜度更高,
組件擁有各自的資料存盤意味著每個資料存盤都包含:
- 一組屬于它的資料,并且只允許它自己修改這些資料,讓它成為單一事實來源;
- 一組其它組件資料的副本,它自己不能修改這些資料,但組件的功能需要這些資料,而且一旦資料在其所屬的組件中發生了變化,這些副本需要更新,
每個組件都會創建其所需的其它組件資料的本地副本,在必要時使用,當資料在其所屬的組件中發生了變化,該組件將觸發一個攜帶資料變更的領域事件,擁有這些資料副本的組件將監聽這個領域事件并相應地更新它們的本地副本,
1.6 控制流
如前所述,控制流顯然從用戶出發,進入應用核心,抵達基礎設施工具,再回傳應用核心并最侄訓傳給用戶,但這些類到底是是如何配合的?哪些類依賴哪些類?我們怎樣把它們組合在一起?
1.6.1 沒有命令/查詢總線
如果沒有命令總線,控制器要么依賴應用服務,要么依賴查詢物件,
上圖中我們使用了應用服務介面,盡管我們會質疑這并沒有必要,因為應用服務是我們應用代碼的一部分,而且我們不會想用另外一種實作來替換它,盡管我們可能會徹底地重構它,
1.6.2 有命令/查詢總線
如果我們的應用使用了命令/查詢總線,UML 圖基本沒有變化,唯一的區別是控制器現在會依賴總線、命令或查詢,它將實體化命令或查詢,將它們傳遞給總線,總線會找到合適的處理程式接收并處理命令,
在下圖中,命令處理程式接下來將使用應用服務,然而,這不總是必須的,實際上大多數情況下,處理程式將包含用例的所有邏輯,只有在其它處理程式需要重用同樣的邏輯時,我們才需要把處理程式中的邏輯提取出來放到單獨的應用服務中,
總線和命令查詢,以及處理程式之間沒有依賴,這是因為實際上它們之間應該互相無感知,才能提供足夠的解耦,只有通過配置才能設定總線可以發現哪些命令,或者查詢應該由哪個處理程式處理,
如你所見,兩種情況下,所有跨越應用核心邊界的箭頭——依賴——都指向內部,如前所述,這是埠和配接器架構、洋蔥架構以及整潔架構的基本規則,
1.7 共享內核
共享內核由 DDD 之父 Eric Evans 定義,它是多個限界背景關系之間共享的代碼,由開發團隊決定:
[…] 兩個團隊同意共享的領域模型的子集,當然,和模型子集一起共享還包括代碼的子集,還有和這部分模型有關的資料庫設計,這部分明確要共享的內容有著特殊的狀態,而且在沒有和其他團隊達成一致的情況下不應該修改,
Shared Kernel(http://ddd.fed.wiki.org/view/shared-kernel), Ward Cunningham 的 DDD wiki
所以基本上,它可能是任何型別的代碼:領域層代碼、應用層代碼、庫,隨便什么代碼,
然而,在這份心智地圖里,我們將它當做一些特定型別的代碼的子集,共享內核包含的是領域層和應用層的代碼,這些代碼會在限界背景關系之間共享,讓這些背景關系可以互相通信,
這意味著,例如,一個或多個限界背景關系觸發的事件可以在其它的限界背景關系里被監聽到,需要和這些事件一起共享的還有它們用到的所有資料型別,例如:物體 ID、值物件、列舉,等等,事件不應該直接使用像物體這樣的復雜物件,因為將它們序列化到佇列中或是從佇列中反序列化時都會遇到一些問題,所以共享的代碼不應該太寬泛,
當然,如果我們手中的是一個由不同語言開發的微服務組成的多語言系統,共享內核必須是描述性的語言,格式是 json、xml、yaml 或者其它,這樣所有的微服務都能理解,
因此,共享內核就完全和其余的代碼以及組件完全解耦了,這樣很好,因為這意味著盡管組件耦合了共享內核,但組件之間不再耦合,共享代碼可以被清晰地識別出來,并輕松地提取到一個獨立的庫中,
如果我們決定將一個限界背景關系從單體中分離出來并提取成一個微服務,這也會很方便,我對共享代碼了然于心,可以輕松地將共享內核提取到一個庫中,而這個庫即可以安裝到單體中,也可以安裝到微服務中,
2 用代碼體現架構
2.1 兩張腦圖
第一張腦圖由一系列同心圓層級組成,它們最終按照業務維度的應用模塊切分,形成組件,在這張圖里,依賴的方向由外向內,意味著內層對外層可見,而外層對內層不可見,
第二張則是一組平面的層級,其中最上面的一層就是前面這張同心圓,下一層是組件之間共享的代碼(共享內核),再下一層使是我們自己對編程語言的擴展,最下面一層則是實際使用的編程語言,這里的依賴方向是自上而下的,
2.2 體現架構的代碼風格
使用體現架構的代碼風格,意味著代碼風格(編碼規范、類/方法/變數命名約定、代碼結構…)某種程度上可以和閱讀代碼的人交流領域和架構的設計意圖,要實作體現架構的代碼風格,主要有兩種思路,
“[…] 體現架構的代碼風格能讓你給代碼的閱讀者留下提示,幫助他們正確地推斷出設計意圖,”
—George Fairbanks(https://links.jianshu.com/go?to=https%3A%2F%2Fresources.sei.cmu.edu%2Fasset_files%2FPresentation%2F2013_017_001_48651.pdf)
第一種思路是通過代碼制品的名字(類、變數、模塊…)來傳達領域和架構的含義,因此,如果一個類是處理收據(Invoice)物體的倉庫(Repository),我們就應該將它命名成InvoiceRepository,從這個名字我們就可以看出,它處理的是收據領域的概念,而它在架構中被當做一個倉庫,這可以幫助我們理解它應該放在哪個地方,何時使用它以及如何使用它,但是,我認為代碼倉庫中并不是每個代碼制品都需要這樣做,例如,我覺得不必為每個物體(Entity)都加上后綴Entity,這樣做就有些畫蛇添足,徒增噪音,
“[…] 代碼應該體現架構,換句話說,我一看到代碼,就應該能夠清晰地區分出各種組件[…]”
—Simon Brown(https://links.jianshu.com/go?to=http%3A%2F%2Fwww.codingthearchitecture.com%2F2014%2F06%2F01%2Fan_architecturally_evident_coding_style.html)
第二種思路是讓代碼倉庫中的頂級制品明確地區分出各個子域,即領域維度的模塊,也就是組件,
第一種思路應該很清楚,無需贅述,但第二種思路有點兒微妙,我們得深入探討一下,
2.3 讓架構清晰的展現出來
在我的第一張圖里,我們已經看到,在最粗粒度的層級上,我們只有三種不同用途的代碼:
- 用戶界面,這里的代碼就是為了適配某個用例的傳達機制;
- 應用核心,這里的代碼就是用例和領域邏輯;
- 基礎設施,這里的代碼就是為了適配應用核心所需的工具/庫,
因此,在源代碼的根目錄下我們可以創建三個檔案夾來體現這三類代碼,一個檔案夾對應一個類別的代碼,這三個檔案夾表示三個命名空間,稍后我們甚至可以創建測驗來斷言核心對用戶界面和基礎設施可見,反過來卻不可見,也就是說,我們可以測驗由外向內的依賴方向,
2.3.1 用戶界面
一個 Web 企業應用通常擁有多套 API,例如,一套給客戶端使用的 REST API,還有一套給第三方應用使用的 web-hook, 業務還有一套需要維護的遺留 SOAP API,或者還有一套給全新移動應用使用的 GraphQL API…
這樣的應該通常還有一些 CLI 命令,用于定時作業(Cron Job)或按需的維護操作,
當然,還有普通用戶可以使用的網站本身,但也許還有另一個供應用管理員使用的網站,
這些全都是同一個應用的不同視圖,全都是同一個應用的不同用戶界面,
實際上我們的應用可能擁有多個用戶界面,其中有些還是供非人類用戶(第三方應用)使用的,我們通過檔案/命名空間來區分并隔離這些用戶界面,來展現出這一點,
用戶界面主要有三類:API、CLI 和網站,所以我們在UserInterface根命名空間里為每個類別創建一個檔案夾,將不同界面的型別清晰地區分開來,
下一步,如果有必要的話,我們還可以繼續深入每種型別的命名空間,再創建更細分類的用戶界面的命名空間(CLI 可能不需要再細分了),
2.3.2 基礎設施
和用戶界面一樣,我們的應用使用了多種工具(庫和第三方應用),例如 ORM、訊息佇列、SMS 提供商,
此外,上述每一種工具都可以有不同的實作,例如,考慮一家公司業務擴張到另一個國家的情況,由于價格的因素,不同的國家最好采用不同的 SMS 提供商:我們需要埠相同的配接器的不同實作,這樣使用時可以互相替換,另一個例子是對資料庫 Schema 進行重構或者切換資料庫引擎,需要(或決定要)切換 ORM 時:我們會在應用中注入兩種 ORM 配接器,
因此,在Infrastructure命名空間來說,我們先給每一種工具型別創建一個命名空間(ORM、MessageQueue、SmsClient),然后再每一種工具型別內部為每一種用到的供應商(Doctrine、Propel、MessageBird、Twilio…)的配接器在創建一個命名空間,
2.3.3 核心
在Core命名空間下,可以按照最粗粒度的層級劃分出三類代碼: 組件(Component)、共享內核(Shared Kernel) 和 埠(Port),為這三個類別創建檔案夾/命名空間,
1.組件
在 Component 命名空間下,我們為每個組件創一個命名空間,然后在每個組件命名空間下,我們再分別為應用(Application)層和領域(Domain)層分別創建一個命名空間, 在 Application 和 Domain 命名空間下,我們先將全部類放在一起,隨著類的數量不斷增加,再來考慮必要的分組(我覺得一個檔案夾下就放一個類有些矯枉過正,所以我寧愿在必要時再進行分組),
這是我們就要考慮是按照業務主題(收據、交易…)分組還是按照技術作用(倉庫、服務、值物件…)分組,但我覺得無論怎樣分組影響都不大,因為這已經是整個代碼組織樹的葉子節點了,如果需要,在整個組織結構的最底端進行調整也很簡單,不會影響代碼倉庫的其它部分,
2.埠
和 Infrastructure 命名空間一樣,Port 命名空間里核心使用的每一種工具都有一個命名空間,核心通過這些代碼才能使用底層的這些工具,
這些代碼還會被配接器使用,它們的作用就是埠和真正工具之間的轉換,這種形式簡單得不能再簡單了,埠就是一個介面,但很多時候它還需要值物件、DTO、服務、構建起、查詢物件甚至是倉庫,
3.共享內核
我們把在組件之間共享的代碼放到 Shared Kernel 命名空間下,嘗試了幾種不同的共享內核內部結構之后,我無法找到一種適用于所有情況的結構,有些代碼和Core\Component一樣按組件劃分很合理(例如 Entity ID 顯然屬于一個組件),有些代碼這樣劃分卻不合適(例如,事件可能被多個組件觸發或監聽),也許要結合使用兩種劃分的思路,
2.3.4 用戶區里的編程語言擴展
最后,我們還有一些自己對編程語言的擴展,這個系列中前面一篇文章已經討論過,這些代碼本可以放在編程語言中,卻因為某些原因沒有,比如,在 PHP 中我們可以想到的是 DateTime 類,它基于 PHP 提供的類擴展,提供了一些額外的方法,另一個例子是 UUID 類,盡管 PHP 沒有提供,但是這個類天然就是純粹的、對領域無感,因此可以在任意專案中使用,并且不依賴任何領域,
這些代碼用起來和編程語言自己的提供的功能沒啥區別,因此我們要完全掌控這些代碼,然而,這并不是意味著我們不能使用第三方庫,我們能用而且應該用,只要合理,但是這些庫應該用我們自己的實作包裝起來(這樣的話我們可以方便的切換背后的第三方庫),而應用代碼應該直接使用這些包裝代碼,最終,這些代碼可以自成專案,使用自己的 CVS 倉庫,被多個專案使用,
3 通過檔案描述架構
我們有哪些可供選擇的檔案工具來表達整個應用的構建塊以及應用如何作業?!
UML
4+1 架構視圖模型
架構決策記錄
C4 模型
依賴圖
應用地圖
3.1 C4 模型
C4 模型是 Simon Brown 發明的,是我目前看到的關于軟體架構檔案的最好思路,我會快速地用自己的語言來闡述主要的思路,但使用的還是他的圖例,
其思路是用四種不同粒度(或者“縮放”)層級來記錄軟體的架構:
第一級:系統背景關系圖
第二級:容器圖
第三級:組件圖
第四級:代碼圖
3.1.1 第一級:系統背景關系圖
這是最粗粒度的圖,它的細節很少但其主要目標是描述應用所處的背景關系,因此,這幅圖中只有一個方塊代表整個應用,其它圍繞著應用的方塊代表了應用要進行交付的外部系統和用戶,
3.1.2 第二級:容器圖
現在,我們將應用放大,也就是上一級圖中的藍色方塊,在這一級它對應的是下圖中的虛線框,
在這個粒度級別,我們將看到應用得容器,一個容器就是一個應用中技術上獨立的一小部分,例如一個移動 App,一個 API 或者一個資料庫,它還描述了應用使用的主要技術和容器之間的通信方式,
3.1.3 第三級:組件圖
組件圖展示的是一個容器內的組件,在 C4 模型背景關系里,每個組件就是應用的一個模塊,不光是領域維度的模塊(如賬單、用戶…)還包括純粹的功能模塊(如 email、sms…),因此這個層級的圖向我們展示了一個容器的主要齒輪和齒輪之間的嚙合關系,
3.1.4 第四級:代碼圖
這是最細粒度的圖,目的是描述一個組件內部的代碼結構,在這個層級,我們使用的是表示類級別制品的 UML 圖,
4 總結
清晰架構集百家之長,天然有很多優勢:
- 從外向內,越向內越偏核心原則,核心原則相對穩定,核心原則就是常規的領域層,提供核心能力
- 外層基于核心原則適配不同的業務場景,組裝內層的能力,這里的外層就是常規的介面層到應用層,主要使用主動配接器模式,重點關注 BFF(BackendsForFrontends) 及對內層能力的聚合
- 內層不依賴外層,不受業務變化而變化,關注能力的擴展,完成核心策略實作
- 邊界明顯,尤其是領域層與應用層之間
- CQRS 機制,耦合度低,通過外層組裝內層能力動態適配業務變化,擴展性高
這只是一份指南!應用才是你的疆域,現實情況和具體用例才是運用這些知識的地方,它們才能勾勒出實際架構的輪廓!
我們需要理解所有這些模式,但我們還時常需要思考和理解我們的應用需要什么,我們應該在追求解耦和內聚的道路上走多遠,這個決定可能受到許多因素的影響,包括專案的功能需求,也包括構建應用的時間期限,應用壽命,開發團隊的體驗等等因素,
應用遵循某種領域結構組成,也遵循某種技術結構(即架構)組成,這兩種結構才是一個應用的與眾不同之處,而不是它使用的工具、庫或者傳達機制,如果我們想讓一個應用可以長時間的維護,這兩種結構都要清晰的體現在代碼倉庫中,這樣開發者才能知道、理解、遵循,并在需要時改進,
這種清晰度讓我們可以在編碼的同時理解邊界,這能反過來幫助我們保持應用的模塊化設計,做到高內聚低耦合,
附錄:
- 軟體架構編年史(譯):https://www.jianshu.com/p/b477b2cc6cfa
- The Software Architecture Chronicles:https://herbertograca.com/2017/07/03/the-software-architecture-chronicles/
- 技術案例—基于 DDD 思想的技術架構戰略調整:https://www.6aiq.com/article/1648170246451
- 中文圖
作者:京東物流 李國梁
來源:京東云開發者社區 自猿其說Tech
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/557162.html
標籤:架構設計
上一篇:資料權限解決方案
下一篇:返回列表