六边形架构(端口与适配器)
在六边形架构中,提出了一种具有对称性特征的架构风格。在这种架构中,不同的客户通过“平等”的方式与系统交互。需要新的客户吗?不是问题。只需要添加一个新的适配器将客户输入转化成能被系统API所理解的参数就行了。同时,系统输出,比如图形界面、持久化和消息等都可以通过不同方式实现,并且是可互换的。这是可能的,因为对于每种特定的输出,都有一个新建的适配器负责完成相应的转化功能。
至此,我们有充足的理由认为,这将是一种具有持久生命力的架构。
现在,很多声称使用分层架构的团队实际上使用的就是六边形架构。这是因为很多项目都使用了某种形式的依赖注入。并不是说依赖注入天生就是六边形架构,而是说使用依赖注入的架构自然地具有了端口和适配器风格。我们将对此做详尽的解释。
我们通常将客户与系统交互的地方称为“前端”;同样,我们将系统中获取、存储持久化数据和发送输出数据的地方称为“后端”。但是,六边形架构提倡用一种新的视角来看待整个系统。该架构中存在两个区域,分别是“外部区域”和“内部区域”。在外部区域中,不同的客户均可以提交输入;而内部的系统则用于获取持久化数据,并对程序输出进行存储(比如数据库),或者在中途将输出转发到另外的地方(比如消息,转发到消息队列)。
每种类型的客户都有自己的适配器,该适配器用于将客户输入转化为程序内部API所理解的输入。六边形每条不同的边代表了不同种类型的端口,端口要么处理输入,要么处理输出。图中有3个客户请求均抵达相同的输入端口(适配器A、B和C),另一个客户请求使用了适配器D。可能前3个请求使用了HTTP协议(浏览器、REST和SOAP等),而后一个请求使用了MSMQ的协议。端口并没有明确的定义,它是一个非常灵活的概念。无论采用哪种方式对端口进行划分,当客户请求到达时,都应该有相应的适配器对输入进行转化,然后适配器将调用应用程序的某个操作或者向应用程序发送一个事件,控制权由此交给内部区域。
我们不必自己实现端口
通常来说,我们都不用自己实现端口。我们可以将端口想成是HTTP,而将适配器想成是.NET的HTTP请求处理管道。或者,我们可以为MSMQ创建消息监听器,在这种情况下,端口是消息机制,而适配器则是消息监听器,因为消息监听器将负责从消息中提取数据,并将数据转化为应用层API(领域模型的直接客户)所能理解的参数。
按照功能需求来设计内部区域中的应用程序
在使用六边形架构时,我们应该根据用例来设计应用程序,而不是根据需要支持的客户数目业设计。任何客户都可能向不同的端口发出请求,但是所有的适配器都将使用相同的API。
应用程序通过公共API接收客户请求。应用程序边界,即内部六边形,也是用例(或用户故事)边界。换句话说,我们应该根据应用程序的功能需求来创建用例,而不是客户数量或输出机制。当应用程序通过API接收到请求时,它将使用领域模型来处理请求,其中便包括对业务逻辑的执行。因此,应用层API通过应用服务的方式展现给外部。再次提醒大家,这里的应用服务是领域模型的直接客户,就像在分层架构中一样。
以下代码表示通过WebAPI发布的RESTful资源。当请求到达HTTP的输入端口时,相应的适配器将对请求的处理委派给应用服务:
public class ProductControl
{
private ProductService productService;
[HttpGet]
public HttpResponseMessage GetProduct(string tenantId, string productId)
{
HttpResponseMessage rm;
Product product = productService.Product(tenantId, productId);
if(product == null)
{
rm = Request.CreateResponse(HttpStatusCode.NotFound);
}
else
{
rm = Request.CreateResponse<Product>(HttpStatusCode.OK,product);
}
return rm;
}
}
WebAPI路由构成了适配器的大部分功能,它们负责解析资源路径,并将资源参数转化为string类型参数。ProductService(一个应用服务)实例是注入进来的,请求便是通过该ProductService将处理委派到应用程序内部的。之后,Product对象将被序列化成json字符串,然后放在Response中,再由HTTP输出端口发出。
WebAPI并不是我们的关注点
WebAPI只是使用应用程序和领域模型的一种方式。在这里,WebAPI并不重要,我们完全可以使用其它框架来完成相同的功能。但不管采用哪种方式,不同的适配器都会将输入委派给相同的API。
图中右侧的端口和适配器,我们应该如何看待呢?我们可以将资源库的实现看作是持久化适配器,该适配器用于访问先前存储的聚合实例,或者保存新的聚合实例。正如图中的适配器E、F和G所展示的,我们可以通过不同的方式实现资源库,比如关系型数据库、基于文档的存储、分布式缓存和内存存储等。如果应用程序向外界发送领域事件消息,我们将使用适配器H进行处理。该适配器处理消息输出,而刚才提到的处理MSMQ消息的适配器则是消息输入的,因此应该使用不同的端口(即选择六边形的另外一条边)。
六边形架构的一大好处在于,我们可以轻易地开发用于测试的适配器。整个应用程序和领域模型可以在没有客户和存储机制的条件下进行设计开发。在测试时,我们可以方便地对ProductService进行替换,而无须考虑它是应该支持HTTP/REST呢,还是SOAP,或者是消息端口。任何测试客户都可以在用户界面还未完成之前进行开发。在选择持久化机制之前,我们可以在测试中采用内存资源库来模拟持久化。更多的内存持久人实现细节,请参考资源库。如此一来,我们可以在核心领域上进行持续开发,而不需要考虑那些支撑性的技术组件。
如果你采用的是严格分层架构,那么你应该考虑推平这种架构(推平严格分层架构),然后开始采用端口与适配器。如果设计得当,内部六边形——也即应用程序和领域模型——是不会泄漏到外部区域的,这样也有助于形成一种清晰的应用程序边界。在外部区域,不同的适配器可以支持自动化测试和真实的客户请求,还有存储、消息和其他输出机制等。
六边形架构的功能如此强大,以致于它可以用来支持系统中的其它架构。比如,我们可能采用SOA架构、REST或者事件驱动架构;也有可能采用CQRS;或者数据网织或基于风格的分布式缓存;还有可能采用Map-Reduce这种分布式并行处理方式。六边形架构为这些架构提供了坚实的支撑基础。当然,能够提供这种基础的不只是六边形架构,但是一般默认使用这种架构,辅助使用其它架构。
![](https://files.cnblogs.com/files/sad-bird/红包码.bmp)