36

在阅读了有关端口和适配器架构的不同来源(包括 Alistair Cockburn 的原始文章)之后,我仍然不确定术语“端口”和“适配器”的明确含义——尤其是在将这些概念映射到实现工件时。

一些消息来源(例如这篇文章)暗示这种架构模式中的端口是最外部的工件,然后是中间层中的适配器,它们在端口和核心应用程序之间进行转换。

然而,在 Cockburn 的原始文章中,端口出现在适配器层的外部和内部,具体取决于通信方向:

  • 入站通信:“当事件从外部世界到达端口时,特定于技术的适配器将其转换为可用的过程调用或消息,并将其传递给应用程序。
  • 出站通信:“当应用程序有东西要发送时,它会通过端口将其发送到适配器,适配器会创建接收技术(人工或自动)所需的适当信号。

实际上,对我来说,“所有外部”方法和“内部和外部”方法都没有意义 - 我将端口视为始终放置在应用程序旁边的工件,无论通信方向如何。Imo 这也与端口适配器的隐喻一致:例如。有一个带串口的设备,要将另一个没有串口的设备连接到这个设备上,我需要一个适配器,从我的设备的角度来适应入站和出站通信。

谈到这个架构的实现,我会看到端口的定义,而不是我的应用程序的一部分,我会看到不同的适配器在我的应用程序“外部”。例如。单个端口的实现可以由一个facade(由适配器调用以进行入站通信)和一个interface(由适配器实现以进行出站通信)组成。

术语端口适配器的正确含义是什么?如何将这些概念映射到实现工件?

更新:

发现这篇文章与我的理解相似。问题仍然存在,是否存在某种共同协议。

4

4 回答 4

23

inf3rno 给出了一个很好的答案,它澄清了原始问题,但强调端口和适配器的其他一些用途可能会很有用。

据我了解,端口是您界面的一种表达方式

港口:

  • 定义核心功能的公开(对于“传入”端口)
  • 定义核心对外部世界的看法(对于“传出”端口)

适配器:

  • 位于组件外部(六边形)
  • 用于确保端口和目标之间的传输以满足与端口接口的约定的方式发生
  • 是你替换(使用依赖注入)来测试六边形

端口应该接受适配器并确保适配器实现了接口。然后它应该只调用适配器上的适当方法/函数。

该端口应包含在通信测试中。在这种情况下,“模拟”的是两个相邻六边形(或一个六边形和一个服务)的核心,并测试端口/适配器/适配器/端口组件。

有关更多信息,您可以查看 James Gardner 和我在 2014 年 7 月伦敦的 Skillsmatter 微服务聚会上发表的关于六角微服务的演讲。

于 2014-09-11T21:17:38.937 回答
18

我认为这是一个非常简单的概念。你有应用程序核心,它不依赖于它之外的任何东西。例如,它不依赖于 HTTP 框架、数据库驱动程序、邮件框架等等……这个核心有一个非常具体的接口,取决于您的问题域,因此您的应用程序核心的代码只有在您的问题域发生变化时才应该更改.

例如,您有博客文章,并且您想为它们添加类别。因此,类别应该流经整个系统,从 HTTP 通信到数据库,通过写入,反之亦然。

现在如果你想用 MongoDB 替换你的 MySQL 数据库,为什么不呢。它不应该影响核心,因为应用程序仍然做同样的事情:它存储您的博客文章和 ofc。他们的类别并按需返回。因此,在这种情况下,您只需要一个 MondogDB 适配器,您可以使用它来代替 MySQL 适配器。

例如,如果您想要一个 REST API,而不仅仅是一个简单的 HTML 页面,该怎么办?它仍然不会影响您的应用程序核心,因此您需要另一个用于 REST 通信的适配器。

因此,在我看来,您的适配器应该在您的应用程序核心中定义特定的接口,并且端口应该使用这些适配器与应用程序核心进行通信。所以在我看来,端口不必作为类存在,只是适配器及其接口。这个概念是有道理的,因为您的应用程序核心不会与您要使用的端口紧密耦合,而只会与您定义的适配器接口紧密耦合。(顺便说一下,有多种类似的架构。比如干净的架构、洋葱架构,它们使用相同的概念和不同的词汇。)

于 2014-09-01T23:57:31.837 回答
9

我工作中的某个人对这个架构做了一个很棒的内部演示。最后,在提问时间,另一位同事问道:

这不只是一个名称不同、绘制方式不同的分层架构吗?

而且,老实说,这在很大程度上是正确的。对于许多应用程序,六边形架构的结构与分层架构相同,但有一些具体细节:

  • 在定义每一层(端口)之间的接口方面有更多的纪律,而不是调用 impl 到 impl。
  • 将“核心”(业务逻辑)视为最重要的层,而所有其他层(适配器)都被视为从属。
  • 专注于从核心的角度定义接口可以防止适配器的语言泄漏到核心中。例如,如果您将持久性(例如 Hibernate)放入适配器中,那么您的核心中不应该有任何 @Entity 类。

你可以看到,即使做了所有这些事情,它仍然只是一个分层的架构,只是层与层之间的边界非常严格,并且侧重于中心层。

因此,要具体回答这个问题,您可以通过认识到端口是进出核心的接口来理解端口和适配器,而适配器只是不是核心的实现层。

于 2015-04-11T22:08:17.980 回答
4

从我的角度来看,在阅读了原始文章并观看了 Alistair Cockurn 的一些演讲(“Alistair in the Hexagone”)之后,我认为正确的方法是您所说的“all inside”,即在呼入和呼出通信中,端口在适配器“内部”。适配器位于与应用程序交互的外部参与者和端口之间。端口属于应用程序。

对于入站通信,actor(驱动actor)使用驱动适配器触发通信。此适配器调用应用程序的驱动程序端口,请求应用程序执行某些操作。

对于出站通信,应用程序通过定义和调用驱动端口来触发与驱动参与者的通信。此端口是应用程序在用途方面所需内容的合同(通常是接口)。此端口由适配器实现,该适配器与外部参与者通信。

依赖项应该是:

Driver Actor --> Driver Adapter --> Hexagon <-- 驱动适配器 <-- 驱动Actor

Ports 属于 Hexagon:

驱动程序端口是 Hexagon 提供给驱动程序适配器的 API。

Driven Ports 是 Hexagon 所需的 SPI,由 Driven Adapters 实现。

你提到的这句话我也很挣扎,它出现在原始文章中:

“当事件从外部世界到达端口时,特定于技术的适配器将其转换为可用的过程调用或消息,并将其传递给应用程序。”

据说驱动程序端口是“外部”驱动程序适配器。但是读整篇文章,看谈话,我认为不是这样的。这句话所谓的“端口”,只是外部驱动actor和适配器之间的交互。“端口”应该是适配器和应用程序之间的交互(“...将其传递给应用程序”)。

于 2018-04-14T10:34:28.817 回答