六邊形架構或六角架構是Alistair Cockburn在2005年提出,解決了傳統的分層架構所帶來的問題,實際上它也是一種分層架構,只不過不是上下或左右,而是變成了內部和外部。在領域驅動設計(DDD)和微服務架構中都出現了六邊形架構的身影,在《實現領域驅動設計》一書中,做者將六邊形架構應用到領域驅動設計的實現,六邊形的內部表明了application和domain層,而在Chris Richardson對微服務架構模式的定義中,每一個微服務使用六邊形架構設計,足見六邊形架構的重要性。那麼讓咱們一探究竟,它爲什麼如此受青睞。html
傳統的分層架構具備普遍的應用,例如經典的三層架構,把系統分爲表示層、業務邏輯層、數據訪問層。在Martin Fowler的《企業應用架構模式》一書中作過深刻闡述,本書04年出版,時至今日分層架構仍然是經常使用的設計方法,分層架構能夠下降耦合、提升複用、分而治之,但同時也仍是存在一些問題:ios
六邊形架構又稱爲端口-適配器,這個名字更容器理解。六邊形架構將系統分爲內部(內部六邊形)和外部,內部表明了應用的業務邏輯,外部表明應用的驅動邏輯、基礎設施或其餘應用。內部經過端口和外部系統通訊,端口表明了必定協議,以API呈現。一個端口可能對應多個外部系統,不一樣的外部系統須要使用不一樣的適配器,適配器負責對協議進行轉換。這樣就使得應用程序可以以一致的方式被用戶、程序、自動化測試、批處理腳本所驅動,而且,能夠在與實際運行的設備和數據庫相隔離的狀況下開發和測試。git
六邊形架構的重點體如今如下幾個方面:github
關注點
對於分層架構中層次的界定,Martin Fowler給出了一個斷定的方法,就是若是把表示層換成其餘實現,若是和原來的表示層有重複實現的內容,那麼這部份內容就應該放到業務邏輯層。那麼如何讓開發人員在系統設計過程當中始終保持這種視角,傳統的分層架構是難以作到的。六邊形架構有一個明確的關注點,從一開始就強調把重心放在業務邏輯上,外部的驅動邏輯或被驅動邏輯存在可變性、可替換性,依賴具體技術細節。而業務邏輯相對更加穩定,體現應用的核心價值,須要被詳盡的測試。數據庫
外部可替換
一個端口對應多個適配器,是對一類外部系統的概括,它體現了對外部的抽象。應用經過端口爲外界提供服務,這些端口須要被良好的設計和測試。內部不關心外部如何使用端口,從一開始就要假定外部使用者是可替換的。六邊形的六並無實質意義,只是爲了留足夠的空間放置端口和適配器,通常端口數不會超過4個。適配器能夠分爲2類,「主」、「從」適配器,也可稱爲「驅動者」和「被驅動者」。架構
自動測試
在六邊形架構中,自動化測試和用戶具備同等的地位,在實現用戶界面的同時就須要考慮自動化測試。它們對應相同的端口。六邊形架構不只讓自動化測試這件事情成爲設計第一要素,同時自動化測試也保證應用邏輯不會泄露到用戶界面,在技術上保證了層次的分界。app
依賴倒置
六邊形架構必須遵循以下規則:內部相關的代碼不能泄露到外部。所謂的泄露是指不能出現內部依賴外部的狀況,只能外部依賴內部,這樣才能保證外部是能夠替換的。對於驅動者適配器,就是外部依賴內部的。可是對於被驅動者適配器,實際是內部依賴外部,這時須要使用依賴倒置,由驅動者適配器將被驅動者適配器注入到應用內部,這時端口的定義在應用內部,可是實現是由適配器實現。dom
https://github.com/zhongpan/hexagonal-architecture-sample微服務
app.h單元測試
#pragma once namespace app { class RateRepository { public: virtual double getRate(double amount) = 0; }; class Discounter { public: Discounter(RateRepository* rateRepository) : _rateRepository(rateRepository) {} double discount(double amount); private: RateRepository* _rateRepository; }; }
app.cpp
#include "app.h" namespace app { double Discounter::discount(double amount) { if (amount <= 0) return 0; double rate = _rateRepository->getRate(amount); return amount * rate; } }
cmd.cpp
#include "app.h" #include "repo.h" #include <iostream> using namespace app; using namespace repo; int main(int argc, char* argv[]) { double amount = 0.0; std::cout << "please enter amount:"; std::cin >> amount; MockRateRepository repo; Discounter app(&repo); std::cout << "discount is:" << app.discount(amount) << std::endl; }
test.cpp
#define BOOST_TEST_MAIN #include "app.h" #include <boost/test/unit_test.hpp> #include <boost/smart_ptr.hpp> using namespace boost; class MockConstRateRepository : public app::RateRepository { public: MockConstRateRepository(double rate) : _rate(rate) {} double getRate(double amount) { return _rate; } private: double _rate; }; BOOST_AUTO_TEST_SUITE(s_discount) BOOST_AUTO_TEST_CASE(t_discount) { double RATE_0point01 = 0.01; MockConstRateRepository repo(RATE_0point01); app::Discounter app(&repo); BOOST_CHECK_EQUAL(app.discount(100), 100*RATE_0point01); BOOST_CHECK_EQUAL(app.discount(0), 0); BOOST_CHECK_EQUAL(app.discount(-100), 0); } BOOST_AUTO_TEST_SUITE_END()
repo.h
#pragma once #include "app.h" namespace repo { class MockRateRepository : public app::RateRepository { public: double getRate(double amount); }; }
repo.cpp
#include "repo.h" namespace repo { double MockRateRepository::getRate(double amount) { if (amount <= 100) return 0.01; if (amount <= 1000) return 0.02; return 0.05; } }