六边形架构或六角架构是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; } }