架構整潔之道

這篇文章是翻譯(Uncle Bob Martin)的文章。這篇文章作者嘗試使用簡單的觀點將各種架構的共通之處和最終目標說清楚。全文要說清楚的就是一件事“如何寫出整潔的架構”。作者希望在架構系統的時候只需要秉持最簡單的兩個觀點(分層和依賴規則)開發,就能開發出乾淨整潔的系統架構。

以下是譯文

clip_image001

 

過去幾年間有許多關於系統架構的觀點。比如:

1 六角架構(Hexagonal Architecture )。 這種架構是由Alistair Cockburn提出的,並由Steve Freeman和Nat Pryce在他們的書《Growing Object Oriented Software 》中提到。

2 洋蔥架構(Onion Architecture )。提出者是Jeffrey Palermo。

3 尖叫架構(Screaming Architecture)。提出者是Uncle Bob(就是這篇文章的作者)。

4 DCI 架構。提出者是James Coplien和Trygve Reenskaug。

5 BCE 架構。提出者是Ivar Jacobson。在他的書《Object Oriented Software Engineering: A Use-Case Driven Approach》中有大量提及對這種架構的說明。

 

雖然這些架構在細節處都有一些變化,但是實際上,它們是非常相似的。它們的目標是一樣的,將各種實體間的關係進行分離。它們分離操作的方法也是一樣的,採用軟件分層的方式。它們分的層中至少有一個業務邏輯層,並且有其他的接口層。

 

每一種架構一定能在寫系統的業務邏輯的時候有以下特徵:

1 與框架的分離。

框架決不能不依賴一些有限制特徵的庫。這樣就能保證你能像使用手邊的工具一樣簡單地使用這些框架,而不會讓你的系統業務邏輯在使用前就有一些強制性的約束。

2 可測試性。

業務邏輯必須能獨立測試,不需要UI,數據庫,Web服務器或者一些其他的外部條件。

3 與UI的分離。

UI必須能非常容易獨立地修改。而不能在改變UI的同時需要改變系統其他部分。比如當把系統的UI從Web UI改成控制檯UI,你並不需要改變任何業務邏輯的代碼。

4 與數據庫的分離。

能很方便地在Oracle,SQL Server,Mongo DB,BigTable,CouchDB或者其他數據庫中進行切換和改變。業務邏輯決不能依賴這些數據庫。

5 與外部結構的分離。

系統的業務邏輯並不需要知道任何外部的結構。

 

文章最上面的圖就是將所有這些架構進行統一概括,提取出統一的觀點。

依賴規則(The Dependency Rule)

同心圓代表的是不同層級的軟件代碼。通常當你更深一步思考構造你的系統的時候,你的系統就會在更高的層級。最外層的圈代表的是機制級別的系統。最內層的代表的是策略級別的系統。

最重要的一條規則是依賴規則(The Dependency Rule)。這條規則說的是:代碼依賴只能使由外向內。換句話說,內層結構的代碼不能包含有任何外層結構的信息。尤其是一些外層結構的名稱不應該被內層結構的代碼提到,比如函數名,類名,變量名,或者其他的系統實體的名稱。

同樣的,外層的數據結構不應該被內層代碼使用,特別是那些由外部框架生成的數據結構。我們並不希望外部結構的任何東西會影響到內部結構。

實體層(Entities)

實體是用來封裝公司的業務規則的。一個實體可以是一個帶方法的對象,也可以是一些數據結構和函數。只要實體能被公司的不同業務邏輯部件使用,實體的具體表現形式是無所謂的。

或許你並不是想寫公司級的架構,而只是想寫一個簡單的應用,那麼這裏實體就是指的應用的業務邏輯對象。它們封裝了最通用的規則,並且當外部環境變化的時候,這些實體是最不需要被變化的。舉例來說,比如在增加翻頁需求或者是安全需求的時候,這些實體是最不應該被改變的。沒有任何具體的應用需要改變實體層。

用戶實例層(Use Cases)

這一層的軟件結構包含了具體的應用業務邏輯。它實現了所有的用戶實例。這些用戶的實例由流入實體的數據流和流出實體的數據流實現,這些用戶實例使得內層的實體能依靠實體內定義的業務邏輯規則來完成系統的用戶需求。

我們不希望用戶實例層的任何改變會影響到實體層。我們同樣也不希望用戶實例層會被外部的結構層,比如UI、數據庫或者任何公共的框架,的改變而影響。這層應該是獨立於這些概念的。

當然,必然發生的是應用的業務邏輯被修改會影響到用戶實例層的代碼和結構。如果用戶的需求改變了,這層的部分當然會被修改。

接口適配層(Interface Adapters)

這一層的軟件結構的目的就是進行數據的轉換,將便於用戶實例和實體層操作的數據結構變化成爲最便於外部結構(比如數據庫或者Web)操作的數據結構。比如GUI的MVC結構,表現器、視圖器、控制器都是屬於這個結構的。這層很可能是通過控制器將數據結構傳給用戶實例層,並且返回數據給表現器,視圖器。

數據在這層會被轉換,將便於實體層和用戶實例層使用的數據轉化成爲持久層能使用的數據,比如數據庫。這一層的代碼並不需要知道任何數據庫的信息。如果數據庫是SQL數據庫,那麼,所有的SQL語言應當在這層被限制使用,特別是在這一層中與數據庫有交互的代碼部分。

當一些外部的服務需要與用戶實例層和實體層進行交互的時候,這時候需要的數據轉換也理所當然地放在這一層了。

框架和驅動層(Frameworks and Drivers)

最外層是由框架和使用工具組成的。比如數據庫,Web框架等。通常你並不需要寫很多代碼就能達到與內層進行交互的行爲。

這層表達的是所有的數據應該具體最終到達的地方。Web是數據的最終到達地,數據庫也是數據的最終到達地。我們把這些東西放在最外層,它們幾乎對整個系統的架構造不成什麼影響。

 

只有四層?

答案是否定的。這個分層模型是綱要性的。在具體實現中,你會發現你可能需要到比四層更多的層級。並沒有任何規定說你必須只能實現這四個層級。但是,依賴規則(The Dependency Rule)是必須遵守的:代碼依賴只能使由外向內。越向內,抽象程度越高,越向外,細節信息越多。當你越向內設計的時候,你需要設計越高層面的策略。內部層級比外部層級更抽象。

跨層調用

圖片的右下角是一個如何跨層調用的例子。它演示了控制器和表現器如何和用戶實例層進行交互。注意下流向,從控制器開始,通過用戶實例層,然後向上執行到表現器。這裏也需要注意它們的代碼依賴,必須是向內依賴的。

我們通常使用依賴倒置原則(Dependency Inversion Principle)來處理這種情況。比如在JAVA中,我們會使用接口或者繼承關係來保證代碼會在制定的地方依賴倒置。

舉例說明,假設用戶實例需要調用表現器。但是這種調用是違背了依賴規則的:外部的邏輯名稱不應該被內部提到。所以我們讓用戶實例層調用一個接口(這裏叫做用戶實例輸出端口Use Case Output Port),然後讓表現層在外部實現這個接口。

相同的方法可以使用在架構的所有跨層調用上。因此實際上我們能使用多種形式的代碼依賴達到我們能讓控制流按照依賴規則的反方向流動。

什麼數據能跨層調用?

一般跨層調用的數據是簡單的數據結構。你可以使用數據結構或者是簡單的數據傳輸流,又或者可以通過函數的參數來進行傳遞。你也可以將數據封裝到一個hashmap結構,或者一個對象中。數據傳輸最重要的事情是無依賴,簡單。我們並不希望跨層傳遞的數據是實體,或者是數據表的行數據,理由是我們不希望數據有任何形式的違反依賴規則。

例如,許多數據庫框架對query查詢返回行結構,我們叫它RowStructure。我們並不希望將這個RowStructure傳遞給內層結構,因爲這個違反了依賴規則,它會強制讓內層結構瞭解外層的結構。

總結

遵守這些簡單的規則並不難,但是它們會解決你很多頭疼的問題。將系統分層,遵守依賴規則,你就會自然寫出可測試的系統了,所有附帶的好處也會實現了。當任何外部的系統是獨立的,比如數據庫、web框架,你就能以最小的代價將它們進行替換。

作者簡介

clip_image002

Robert C. Martin (Uncle Bob) , 8th Light公司的Master Craftsman, 獲獎作家,著名的演說家,uber software geek。

它的著作有:

Designing Object Oriented c++ Applications using the Booch Method

Patterns Languages of Program Design 3(程序設計的模式語言)

More c++ Gems

Extreme Programming in Practice(解析極限編程)

Agile Software Development: Principles, Patterns, and Practices(敏捷軟件開發:原則、模式與實踐)

uml for java Programmers(UML:JAVA程序員指南)

Clean Code (代碼整潔之道)

The Clean Coder(編碼整潔之道:專業程序員的行爲準則)

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