The Hexagonal (Ports & Adapters) Architecture 六边形(端口与适配器)架构
HaT Technical Report
2005.02
Date: 2005-09-04 (v 0.9 to be updated after reader comments)HaT 技术报告
2005.02
日期:2005-09-04(v 0.9,将在读者评论后更新)
Alistair Cockburn, acockburn@aol.comAlistair Cockburn, acockburn@aol.com
Create your
application to work without either a UI or a database so you can run automated
regression-tests against the application, work when the database becomes
unavailable, and link applications together without any user involvement.
The Pattern: Ports and Adapters (Object Structural)模式:端口与适配器(对象结构)
Alternative name: Hexagonal
Architecture
Intent意图
Allow an application to equally be driven by users, programs, automated test or batch scripts, and to be developed and tested in isolation from its eventual run-time devices and databases. 让应用能够同样地被用户、程序、自动化测试或批处理脚本驱动,并且能够在与最终运行时设备和数据库隔离的情况下进行开发和测试。
As events arrive from the outside world at a port, a technology-specific adapter converts it into a usable procedure call or message and passes it to the application. The application is blissfully ignorant of the nature of the input device. When the application has something to send out, it sends it out through a port to an adapter, which creates the appropriate signals needed by the receiving technology (human or automated). The application has a semantically sound interaction with the adapters on all sides of it, without actually knowing the nature of the things on the other side of the adapters. 当事件从外部世界到达端口时,特定于技术的适配器会将其转换为可用的过程调用或消息,并传递给应用。应用对输入设备的性质一无所知,也无需关心。当应用需要发送内容时,它通过端口将内容发送给适配器,适配器会为接收方技术(人类或自动化)生成所需的适当信号。应用与其各侧的适配器进行语义合理的交互,而实际上并不了解适配器另一侧事物的本质。
<![if !vml]>
<![endif]>
Figure 1<![if !vml]><![endif]>
图 1
Motivation动机
One of the great bugaboos of software applications over the years has been infiltration of business logic into the user interface code. The problem this causes is threefold: First, the system can't neatly be tested with automated test suites because part of the logic needing to be tested is dependent on oft-changing visual details such as field size and button placement; For the exact same reason, it becomes impossible to shift from a human-driven use of the system to a batch-run system; For still the same reason, it becomes difficult or impossible to allow the program to be driven by another program when that becomes attractive. 多年来,软件应用的一大顽疾就是业务逻辑渗入用户界面代码。这会带来三个问题:首先,系统无法通过自动化测试套件进行简洁的测试,因为需要测试的部分逻辑依赖于经常变化的视觉细节,比如字段大小和按钮位置;同样的原因,使得从人工驱动的系统转向批处理系统成为不可能;还是同样的原因,当程序希望被另一个程序驱动时,会变得困难甚至不可能。
The attempted solution, repeated in many organizations, is to create a new layer in the architecture, with the promise that this time, really and truly, no business logic will be put into the new layer. However, having no mechanism to detect when a violation of that promise occurs, the organization finds a few years later that the new layer is cluttered with business logic and the old problem has reappeared.许多组织反复尝试的解决方案是在架构中创建一个新层,并承诺这一次真正地不会在新层中放入任何业务逻辑。然而,由于没有机制来检测这种承诺何时被违反,几年后组织发现新层已充斥业务逻辑,旧问题又重新出现。
Imagine now that every piece of functionality the application offers were available through an API (application programmed interface) or function call. In this situation, the test or QA department can run automated test scripts against the application to detect when any new coding breaks a previously working function. The business experts can create automated test cases, before the GUI details are finalized, that tells the programmers when they have done their work correctly (and these tests become the ones run by the test department). The application can be deployed in headless mode, so only the API is available, and other programs can make use of its functionality -- this simplifies the overall design of complex application suites and also permits business-to-business service applications to use each other without human intervention over the web. Finally, the automated function regression tests detect any violation of the promise to keep business logic out of the presentation layer. The organization can detect, and then correct, the logic leak.现在设想一下,应用提供的每一项功能都通过 API(应用程序编程接口)或函数调用来实现。在这种情况下,测试或 QA 部门可以对应用运行自动化测试脚本,以检测任何新编码是否破坏了先前工作的功能。业务专家可以在 GUI 细节确定之前创建自动化测试用例,告诉程序员他们何时正确完成了工作(这些测试也成为测试部门运行的测试)。应用可以以无头模式部署,这样只有 API 可用,其他程序可以利用其功能——这简化了复杂应用套件的整体设计,也允许企业对企业的服务应用在网络上无需人工干预地相互使用。最后,自动化功能回归测试可以检测到任何违反“将业务逻辑排除在表示层之外”这一承诺的行为。组织可以检测并纠正逻辑泄漏。
An interesting similar problem exists on what is normally considered "the other side" of the application, where the application logic gets tied to an external database or other service. When the database server goes down or undergoes significant rework or replacement, the programmers can't work because their work is tied to the presence of the database. This causes delay costs and often bad feelings between the people. 在通常被认为应用“另一边”的地方,存在一个有趣的类似问题:应用逻辑与外部数据库或其他服务绑定。当数据库服务器宕机或进行重大改造、更换时,程序员无法工作,因为他们的工作依赖于数据库的存在。这会导致延误成本,并经常在人员之间产生不快。
It is not obvious that the two problems are related, but there is a symmetry between them that shows up in the nature of the solution.这两个问题之间是否相关并不显而易见,但它们之间存在一种对称性,这种对称性体现在解决方案的本质中。
Nature of the Solution解决方案的本质
Both the user-side and the server-side problems actually are caused by the same error in design and programming -- the entanglement between the business logic and the interaction with external entities. The asymmetry to exploit is not that between left and right sides of the application but between inside and outside of the application. The rule to obey is that code pertaining to the inside part should not leak into the outside part.用户侧和服务器侧的问题实际上都由同一个设计和编程错误引起——业务逻辑与外部实体交互之间的纠缠。需要利用的不对称不是应用的左右两侧,而是应用的内外之间。应遵守的规则是:属于内部部分的代码不应泄漏到外部部分。
Removing any left-right or up-down asymmetry for a moment, we see that the application communicates over ports to external agencies. The word "port" is supposed to evoke thoughts of ports in an operating system, where any device that adheres to the protocols of a port can be plugged into it; and ports on electronics gadgets, where again, any device that fits the mechanical and electrical protocols can be plugged in. The protocol for a port is given by the purpose of the conversation between the two devices. The protocol takes the form of an application program interface (API). 暂时忽略左右或上下的不对称,我们可以看到应用通过端口与外部机构通信。“端口”一词应让人联想到操作系统中的端口,任何符合端口协议的设备都可以插入其中;也让人联想到电子设备上的端口,同样,任何符合机械和电气协议的设备都可以插入。端口的协议由两个设备之间对话的目的决定。协议采用应用程序接口(API)的形式。
For each external device there is an adapter that converts the API definition to the signals needed by that device and vice versa. A graphical user interface or GUI is an example of an adapter that maps the movements of a person to the API of the port. Other adapters that fit the same port are automated test harnesses such as FIT or Fitnesse, batch drivers, and any code needed for communication between applications across the enterprise or net. 对于每个外部设备,都有一个适配器将 API 定义转换为该设备所需的信号,反之亦然。图形用户界面(GUI)就是一个适配器的例子,它将人的操作映射为端口的 API。适合同一端口的其他适配器包括自动测试工具(如 FIT 或 Fitnesse)、批处理驱动程序,以及用于企业或网络间应用通信所需的任何代码。
On another side of the application, the application communicates with an external entity to get data. The protocol is typically a database protocol. From the application's perspective, if the database is moved from a SQL database to a flat file or any other kind of database, the conversation across the API should not change. Additional adapters for the same port thus include an SQL adapter, a flat file adapter, and most importantly, an adapter to a "mock" database, one that sits in memory and doesn't depend on the presence of the real database at all. 在应用的另一侧,应用与外部实体通信以获取数据。协议通常是数据库协议。从应用的角度来看,如果数据库从 SQL 数据库改为平面文件或任何其他类型的数据库,跨 API 的对话不应改变。因此,同一端口的附加适配器包括 SQL 适配器、平面文件适配器,以及最重要的“模拟”数据库适配器——它位于内存中,完全不依赖真实数据库的存在。
Many applications have only two ports: the user-side dialog and the database-side dialog. This gives them an asymmetric appearance, which makes it seem natural to build the application in a one-dimensional, three-, four-, or five-layer stacked architecture. 许多应用只有两个端口:用户侧对话框和数据库侧对话框。这使得它们呈现出不对称的外观,从而让人很自然地将其构建成一维的三层、四层或五层堆叠架构。
There are two problems with these drawings. First and worst, people tend not to take the "lines" in the layered drawing seriously. They let the application logic leak across the layer boundaries, causing the problems mentioned above. Secondly, there may be more than two ports to the application, so that the architecture does not fit into the one-dimensional layer drawing.这些图示存在两个问题。首先也是最糟糕的,人们往往不认真对待分层图中的“线条”,让应用逻辑跨越层边界泄漏,导致上述问题。其次,应用可能有多个端口,因此架构无法适应一维的分层图示。
The hexagonal, or ports and adapters, architecture solves these problems by noting the symmetry in the situation: there is an application on the inside communicating over some number of ports with things on the outside. The items outside the application can be dealt with symmetrically.六边形架构,即端口与适配器架构,通过注意到情况的对称性来解决这些问题:内部有一个应用,通过若干端口与外部的实体通信。应用外部的事物可以对称地处理。
The hexagon is intended to visually highlight 六边形旨在直观地强调
(a) the inside-outside asymmetry and the similar nature of ports, to get away from the one-dimensional layered picture and all that evokes, and (a)内外的非对称性以及端口的相似性,以摆脱一维分层图示及其所带来的影响,以及
(b) the presence of a defined number of different ports - two, three, or four (four is most I have encountered to date). (b)存在确定数量的不同端口——两个、三个或四个(四个是我迄今为止遇到最多的)。
The hexagon is not a hexagon because the number six is important, but rather to allow the people doing the drawing to have room to insert ports and adapters as they need, not being constrained by a one-dimensional layered drawing. The term hexagonal architecture comes from this visual effect.六边形之所以是六边形,不是因为数字六很重要,而是为了让绘制者有余地根据需要插入端口和适配器,不受一维分层图示的约束。六边形架构一词就来自这种视觉效果。
The term "port and adapters" picks up the purposes of the parts of the drawing. A port identifies a purposeful conversation. There will typically be multiple adapters for any one port, for various technologies that may plug into that port. Typically, these might include a phone answering machine, a human voice, a touch-tone phone, a graphical human interface, a test harness, a batch driver, an http interface, a direct program-to-program interface, a mock (in-memory) database, a real database (perhaps different databases for development, test, and real use).“端口和适配器”一词体现了图示各部分的目的。端口标识一个有目的的对话。对于任何一个端口,通常有多个适配器,对应可能插入该端口的不同技术。通常,这些可能包括电话答录机、人声、按键电话、图形人机界面、测试工具、批处理驱动程序、HTTP 接口、直接的程序间接口、模拟(内存)数据库、真实数据库(也许开发、测试和实际使用使用不同的数据库)。
In the Application Notes, the left-right asymmetry will be brought up again. However, the primary purpose of this pattern is to focus on the inside-outside asymmetry, pretending briefly that all external items are identical from the perspective of the application.在应用说明中,将再次提到左右不对称性。然而,此模式的主要目的是关注内外不对称性,暂时假装从应用的角度来看,所有外部事物都是相同的。
Structure 结构
<![if !vml]>
<![endif]>
Figure 2<![if !vml]><![endif]>
图 2
Figure 2 shows an application having two active ports and several adapters for each port. The two ports are the application-controlling side and the data-retrieval side. This drawing shows that the application can be equally driven by an automated, system-level regression test suite, by a human user, by a remote http application, or by another local application. On the data side, the application can be configured to run decoupled from external databases using an in-memory oracle, or mock, database replacement; or it can run against the test- or run-time database. The functional specification of the application, perhaps in use cases, is made against the inner hexagon's interface and not against any one of the external technologies that might be used.图 2 展示了一个具有两个活动端口和每个端口多个适配器的应用。这两个端口是控制应用侧和数据检索侧。该图显示,应用可以由自动化系统级回归测试套件、人类用户、远程 HTTP 应用或其他本地应用同等驱动。在数据侧,应用可以配置为使用内存中的 oracle 或模拟数据库替代品来与外部数据库解耦运行;也可以针对测试或运行时数据库运行。应用的功能规范(也许以用例形式)是针对内部六边形的接口制定的,而不是针对可能使用的任何一种外部技术。
<![if !vml]>
<![endif]>
Figure 3<![if !vml]><![endif]>
图 3
Figure 3 shows the same application mapped to a three-layer architectural drawing. To simplify the drawing only two adapters are shown for each port. This drawing is intended to show how multiple adapters fit in the top and bottom layers, and the sequence in which the various adapters are used during system development. The numbered arrows show the order in which a team might develop and use the application: 图 3 将同一应用映射为三层架构图。为简化图示,每个端口仅显示两个适配器。此图旨在展示多个适配器如何适应顶层和底层,以及系统开发过程中各适配器的使用顺序。编号箭头显示了团队可能开发和使用的顺序:
<![if !supportLists]>1. <![endif]>With a FIT test harness driving the application and using the mock (in-memory) database substituting for the real database; <![if !supportLists]>1. <![endif]>使用 FIT 测试工具驱动应用,并使用模拟(内存)数据库替代真实数据库;
<![if !supportLists]>2. <![endif]>Adding a GUI to the application, still running off the mock database; <![if !supportLists]>2. <![endif]>为应用添加 GUI,仍然基于模拟数据库运行;
<![if !supportLists]>3. <![endif]>In integration testing, with automated test scripts (e.g., from Cruise Control) driving the application against a real database containing test data; <![if !supportLists]>3. <![endif]>在集成测试中,使用自动化测试脚本(例如来自 Cruise Control)驱动应用,针对包含测试数据的真实数据库运行;
<![if !supportLists]>4. <![endif]>In real use, with a person using the application to access a live database.<![if !supportLists]>4. <![endif]>在实际使用中,用户使用应用访问实时数据库。
Sample Code示例代码
The simplest application that demonstrates the ports & adapters fortunately comes with the FIT documentation. It is a simple discount computing application:幸运的是,展示端口与适配器的最简单应用随 FIT 文档附带。这是一个简单的折扣计算应用:
|
discount(amount) = amount * rate(amount); |
In our adaptation, the amount will come from the user and the rate will come from a database, so there will be two ports. We implement them in stages: 在我们的改编中,金额来自用户,费率来自数据库,因此将有两个端口。我们分阶段实现:
<![if !supportLists]>· <![endif]>With tests but with a constant rate instead of a mock database, <![if !supportLists]>· <![endif]>带测试,但使用常量费率而不是模拟数据库,
<![if !supportLists]>· <![endif]>then with the GUI, <![if !supportLists]>· <![endif]>然后使用 GUI,
<![if !supportLists]>· <![endif]>then with a mock database that can be swapped out for a real database.<![if !supportLists]>· <![endif]>然后使用可替换为真实数据库的模拟数据库。
Thanks to Gyan Sharma at IHC for providing the code for this example.感谢 IHC 的 Gyan Sharma 为此示例提供代码。
Stage 1: FIT + App + constant-as-mock-database阶段 1:FIT + 应用 + 常量作为模拟数据库
First we create the test cases as an HTML table (see the FIT documentation for this):首先,我们以 HTML 表格形式创建测试用例(参见 FIT 文档):
|
TestDiscounter |
|
|
amount |
discount() |
|
100 |
5 |
|
200 |
10 |
Note that the column names will become class and function names in our program. FIT contains ways to get rid of this "programmerese", but for this article it is easier just to leave them in. 请注意,列名将变为我们程序中的类名和函数名。FIT 提供了去掉这种“程序员语言”的方法,但为了本文便于说明,直接保留它们更容易。
Knowing what the test data will be, we create the user-side adapter, the ColumnFixture that comes with FIT as shipped:知道了测试数据后,我们创建用户侧适配器,即 FIT 随附的 ColumnFixture:
|
import fit.ColumnFixture; public class TestDiscounter extends ColumnFixture { private Discounter app = new Discounter(); public double amount;
public double discount() {
return app.discount(amount);
} } |
That's actually all there is to the adapter. So far, the tests run from the command line (see the FIT book for the path you'll need). We used this one:实际上,适配器就是这样。到目前为止,测试从命令行运行(所需路径请参见 FIT 书籍)。我们使用了以下命令:
|
set
FIT_HOME=/FIT/FitLibraryForFit15Feb2005 |
FIT produces an output file with colors showing us what passed (or failed, in case we made a typo somewhere along the way).FIT 生成一个带颜色的输出文件,显示哪些通过(或失败,如果我们在某处打错字的话)。
At this point the code is ready to check in, hook into Cruise Control or your automated build machine, and include in the build-and-test suite.此时,代码已准备好提交,接入 Cruise Control 或你的自动化构建机器,并包含在构建和测试套件中。
Stage 2: UI + App + constant-as-mock-database阶段 2:UI + 应用 + 常量作为模拟数据库
I'm going to let you create your own UI and have it drive the Discounter application, since the code is a bit long to include here. Some of the key lines in the code are these: 我将让你自行创建 UI,并让它驱动 Discounter 应用,因为代码有点长,不便在此完整列出。代码中的一些关键行如下:
|
... public void
actionPerformed(ActionEvent event) { |
At this point the application can be both demoed and regression tested. The user-side adapters are both running. 此时,应用既可以演示,也可以进行回归测试。用户侧的两个适配器都在运行。
Stage 3: (FIT or UI) + App + mock database阶段 3:(FIT 或 UI)+ 应用 + 模拟数据库
To create a replaceable adapter for the database side, we create an interface to a repository, a RepositoryFactory that will produce either the mock database or the real service object, and the in-memory mock for the database.为了为数据库侧创建可替换的适配器,我们创建一个存储库接口、一个 RepositoryFactory(它将产生模拟数据库或真实服务对象),以及数据库的内存模拟。
|
public interface RateRepository { |
|
public class RepositoryFactory {
public static RateRepository getMockRateRepository() { } |
|
public class MockRateRepository
implements RateRepository { |
To hook this adapter into the Discounter application, we need to update the application itself to accept a repository adapter to use, and the have the (FIT or UI) user-side adapter pass the repository to use (real or mock) into the constructor of the application itself. Here is the updated application and a FIT adapter that passes in a mock repository (the FIT adapter code to choose whether to pass in the mock or real repository's adapter is longer without adding much new information, so I omit that version here).要将此适配器接入 Discounter 应用,我们需要更新应用本身,使其接受要使用的存储库适配器,并让(FIT 或 UI)用户侧适配器将要使用的存储库(真实或模拟)传入应用自身的构造函数。以下是更新后的应用和一个传入模拟存储库的 FIT 适配器(用于选择传入模拟还是真实存储库适配器的 FIT 适配器代码较长,且没有增加太多新信息,因此我在此省略该版本)。
|
import repository.RepositoryFactory; public class Discounter {
public Discounter(RateRepository r) {
public double discount(double amount) { |
|
import app.Discounter; public class TestDiscounter extends
ColumnFixture {
public double discount() { |
That concludes implementation of the simplest version of the hexagonal architecture.这样就完成了六边形架构最简单版本的实现。
Application Notes应用说明
The Left-Right Asymmetry左右不对称性
The ports and adapters pattern is deliberately written pretending that all ports are fundamentally similar. That pretense is useful at the architectural level. In implementation, ports and adapters show up in two flavors, which I'll call primary and secondary, for soon-to-be-obvious reasons. They could be also called driving adapters and driven adapters.端口与适配器模式是故意写得好像所有端口在根本上相似的。这种假装在架构层面是有用的。在实现中,端口和适配器以两种形式出现,出于即将显而易见的原因,我将它们称为主要(primary)和次要(secondary)。它们也可以称为驱动适配器(driving adapters)和被驱动适配器(driven adapters)。
The alert reader will have noticed that in all the examples given, FIT fixtures are used on the left-side ports and mocks on the right. In the three-layer architecture, FIT sits in the top layer and the mock sits in the bottom layer. 细心的读者会注意到,在给出的所有示例中,左侧端口使用 FIT fixture,右侧使用模拟。在三层架构中,FIT 位于顶层,模拟位于底层。
This is related to the idea from use cases of "primary actors" and "secondary actors". A primary actor is an actor that drives the application (takes it out of quiescent state to perform one of its advertised functions). A secondary actor is one that the application drives, either to get answers from or to merely notify. The distinction between primary and secondary lies in who triggers or is in charge of the conversation.这与用例中“主要参与者”和“次要参与者”的概念相关。主要参与者是驱动应用的参与者(使其从静止状态进入执行某项所宣称的功能)。次要参与者是应用所驱动的参与者,要么从它那里获取答案,要么仅仅通知它。主要与次要之间的区别在于谁触发或主导对话。
The natural test adapter to substitute for a primary actor is FIT, since that framework is designed to read a script and drive the application. The natural test adapter to substitute for a secondary actor such as a database is a mock, since that is designed to answer queries or record events from the application. 替代主要参与者的自然测试适配器是 FIT,因为该框架旨在读取脚本并驱动应用。替代次要参与者(如数据库)的自然测试适配器是模拟,因为它旨在回答查询或记录来自应用的事件。
These observations lead us to follow the system's use case context diagram and draw the primary ports and primary adapters on the left side (or top) of the hexagon, and the secondary ports and secondary adapters on the right (or bottom) side of the hexagon. 这些观察使我们遵循系统的用例上下文图,将主要端口和主要适配器画在六边形的左侧(或顶部),将次要端口和次要适配器画在六边形的右侧(或底部)。
The relationship between primary and secondary ports/adapters and their respective implementation in FIT and mocks is useful to keep in mind, but it should be used as a consequence of using the ports and adapters architecture, not to short-circuit it. The ultimate benefit of a ports and adapters implementation is the ability to run the application in a fully isolated mode.主要和次要端口/适配器与其在 FIT 和模拟中的各自实现之间的关系值得牢记,但它应作为使用端口与适配器架构的结果来使用,而不是简化为该架构。端口与适配器实现的最终好处是能够在完全隔离的模式下运行应用。
Use Cases And The Application Boundary用例与应用边界
It is useful to use the hexagonal architecture pattern to reinforce the preferred way of writing use cases. 使用六边形架构模式来加强编写用例的首选方式是有用的。
A common mistake is to write use cases to contain intimate knowledge of the technology sitting outside each port. These use cases have earned a justifiably bad name in the industry for being long, hard-to-read, boring, brittle, and expensive to maintain.一个常见错误是编写包含每个端口外部技术内部知识的用例。这些用例在行业中名声不佳是有道理的,因为它们冗长、难以阅读、枯燥、脆弱且维护成本高。
Understanding the ports and adapters architecture, we can see that the use cases should generally be written at the application boundary (the inner hexagon), to specify the functions and events supported by the application, regardless of external technology. These use cases are shorter, easier to read, less expensive to maintain, and more stable over time. 理解端口与适配器架构后,我们可以看出,用例通常应写在应用边界(内部六边形)处,以指定应用支持的功能和事件,而与外部技术无关。这些用例更短、更容易阅读、维护成本更低,并且随着时间的推移更加稳定。
How Many Ports?多少个端口?
What exactly a port is and isn't is largely a matter of taste. At the one extreme, every use case could be given its own port, producing hundreds of ports for many applications. Alternatively, one could imagine merging all primary ports and all secondary ports so there are only two ports, a left side and a right side. 什么算端口,什么不算,在很大程度上是见仁见智的问题。一个极端是,每个用例都有自己的端口,这样许多应用就会有数百个端口。另一种极端是,可以设想合并所有主要端口和所有次要端口,这样只有两个端口,一个左侧一个右侧。
Neither extreme appears optimal. 这两种极端似乎都不是最优的。
The weather system described in the Known Uses has four natural ports: the weather feed, the administrator, the notified subscribers, the subscriber database. A coffee machine controller has four natural ports: the user, the database containing the recipes and prices, the dispensers, and the coin box. A hospital medication system might have three: one for the nurse, one for the prescription database, and one for the computer-controller medication dispensers.在“已知用途”中描述的天气系统有四个自然端口:天气数据源、管理员、被通知的订阅者、订阅者数据库。咖啡机控制器有四个自然端口:用户、包含配方和价格的数据库、出料器、硬币盒。医院用药系统可能有三个端口:一个用于护士,一个用于处方数据库,一个用于计算机控制的配药器。
It doesn't appear that there is any particular damage in choosing the "wrong" number of ports, so that remains a matter of intuition. My selection tends to favor a small number, two, three or four ports, as described above and in the Known Uses.选择“错误”的端口数量似乎不会带来特别大的伤害,因此这仍然是一个直觉问题。我的选择倾向于少数端口,即两个、三个或四个,如上文和“已知用途”中所述。
Known Uses已知用途
<![if !vml]>
<![endif]>
Figure 4<![if !vml]><![endif]>
图 4
Figure 4 shows an application with four ports and several adapters at each port. This was derived from an application that listened for alerts from the national weather service about earthquakes, tornadoes, fires and floods, and notified people on their telephones or telephone answering machines. At the time we discussed this system, the system's interfaces were identified and discussed by technology, linked to purpose. There was an interface for trigger-data arriving over a wire feed, one for notification data to be sent to answering machines, an administrative interface implemented in a GUI, and a database interface to get their subscriber data. 图 4 展示了一个具有四个端口和每个端口多个适配器的应用。这源自一个监听国家气象局关于地震、龙卷风、火灾和洪水的警报,并通过电话或电话答录机通知人们的应用。在讨论该系统时,系统的接口是按技术来识别和讨论的,并与目的相关联。有一个用于通过有线数据源到达的触发数据的接口,一个用于发送到答录机的通知数据的接口,一个用 GUI 实现的管理接口,以及一个用于获取订阅者数据的数据库接口。
The people were struggling because they needed to add an http interface from the weather service, an email interface to their subscribers, and they had to find a way to bundle and unbundle their growing application suite for different customer purchasing preferences. They feared they were staring at a maintenance and testing nightmare as they had to implement, test and maintain separate versions for all combinations and permutations.当时人们很挣扎,因为他们需要添加来自天气服务的 HTTP 接口、面向订阅者的电子邮件接口,而且他们必须找到一种方法来为不同客户的购买偏好捆绑和解绑他们不断增长的应用套件。他们担心自己正面临维护和测试的噩梦,因为他们必须为所有组合和排列实现、测试和维护单独的版本。
Their shift in design was to architect the system's interfaces by purpose rather than by technology, and to have the technologies be substitutable (on all sides) by adapters. They immediately picked up the ability to include the http feed and the email notification (the new adapters are shown in the drawing with dashed lines). By making each application executable in headless mode through APIs, they could add an app-to-add adapter and unbundle the application suite, connecting the sub-applications on demand. Finally, by making each application executable completely in isolation, with test and mock adapters in place, they gained the ability to regression test their applications with stand-alone automated test scripts.他们在设计上的转变是:按目的而不是按技术来架构系统接口,并让技术(在所有侧)都可以通过适配器进行替换。他们立即获得了包含 http 数据源和电子邮件通知的能力(新适配器在图中以虚线显示)。通过让每个应用通过 API 以无头模式执行,他们可以添加应用间适配器并解绑应用套件,按需连接子应用。最后,通过让每个应用在测试和模拟适配器就位的情况下完全隔离地执行,他们获得了使用独立自动化测试脚本对应用进行回归测试的能力。
Mac, Windows, Google, Flickr, Web 2.0Mac、Windows、Google、Flickr、Web 2.0
In the early 1990s, MacIntosh applications such as word processor applications were required to have API-drivable interfaces, so that applications and user-written scripts could access all the functions of the applications. Windows desktop applications have evolved the same ability (I don't have the historical knowledge to say which came first, nor is that relevant to the point).在 1990 年代初,MacIntosh 应用(如文字处理软件)被要求具有可被 API 驱动的接口,以便应用和用户编写的脚本能够访问应用的所有功能。Windows 桌面应用也发展出了同样的能力(我不知道历史上谁先谁后,但这与本文观点无关)。
The current (2005) trend in web applications is to publish an API and let other web applications access those APIs directly. Thus, it is possible to publish local crime data over a Google map, or create web applications that include Flickr's photo archiving and annotating abilities.当前(2005 年)Web 应用的趋势是发布 API,让其他 Web 应用直接访问这些 API。因此,可以在 Google 地图上发布本地犯罪数据,或创建包含 Flickr 照片归档和标注能力的 Web 应用。
All of these examples are about making the primary ports' APIs visible. We see no information here about the secondary ports.所有这些例子都是关于让主要端口的 API 可见的。我们在这里没有看到关于次要端口的信息。
Stored Outputs存储输出
This example written by Willem Bogaerts on the C2 wiki: 这个例子由 Willem Bogaerts 写在 C2 wiki 上:
"I encountered something similar, but mainly because my application layer had a strong tendency to become a telephone switchboard that managed things it should not do. My application generated output, showed it to the user and then had some possibility to store it as well. My main problem was that you did not need to store it always. So my application generated output, had to buffer it and present it to the user. Then, when the user decided that he wanted to store the output, the application retrieved the buffer and stored it for real. “我遇到过类似的情况,但主要是因为我的应用层有很强的倾向变成一个电话总机,管理着它不应该管理的事情。我的应用生成输出,展示给用户,然后有时还可能将其存储。我的主要问题是,你不一定总是需要存储它。所以我的应用生成输出后,必须缓冲它并呈现给用户。然后,当用户决定要存储输出时,应用检索缓冲区并真正存储它。
I did not like this at all. Then I came up with a solution: Have a presentation control with storage facilities. Now the application no longer channels the output in different directions, but it simply outputs it to the presentation control. It's the presentation control that buffers the answer and gives the user the possibility to store it. 我一点都不喜欢这样。然后我想出了一个解决方案:让表现控件具备存储功能。现在应用不再将输出导向不同方向,而只是将其输出到表现控件。由表现控件缓冲答案,并给用户存储它的可能性。
The traditional layered architecture stresses "UI" and "storage" to be different. The Ports and Adapters Architecture can reduce output to being simply "output" again. "传统的分层架构强调“UI”和“存储”是不同的。端口与适配器架构可以将输出重新简化为仅仅是“输出”。”
Anonymous example from the C2-wiki来自 C2 wiki 的匿名示例
"In one project I worked on, we used the SystemMetaphor
of a component stereo system. Each component has defined interfaces, each of
which has a specific purpose. We can then connect components together in almost
unlimited ways using simple cables and adapters."
Distributed, Large-Team Development分布式、大型团队开发
This one is still in trial use and so does not properly count as a use of the pattern. However, it is interesting to consider.这个仍在试用中,因此不能算作该模式的实际用途。不过,考虑一下也很有趣。
Teams in different locations all build to the Hexagonal architecture, using FIT and mocks so the applications or components can be tested in standalone mode. The CruiseControl build runs every half hour and runs all the applications using the FIT+mock combination. As application subsystem and databases get completed, the mocks are replaced with test databases.不同地点的团队都按照六边形架构进行构建,使用 FIT 和模拟,以便应用或组件可以以独立模式进行测试。CruiseControl 构建每半小时运行一次,并使用 FIT+模拟组合运行所有应用。随着应用子系统和数据库的完成,模拟会被测试数据库替代。
Separating Development of UI and Application Logic 分离 UI 和应用逻辑的开发
This one is still in early trial use and so does not count as a use of the pattern. However, it is interesting to consider.这仍处于早期试用阶段,因此不能算作该模式的实际用途。不过,考虑一下也很有趣。
The UI design is unstable, as they haven't decided on a driving technology or a metaphor yet. The back-end services architecture hasn't been decided, and in fact will probably change several times over the next six months. Nonetheless, the project has officially started and time is ticking by. UI 设计还不稳定,因为他们尚未决定驱动技术或隐喻。后端服务架构尚未确定,实际上可能在未来六个月内多次变化。尽管如此,项目已经正式开始,时间正在流逝。
The application team creates FIT tests and mocks to isolate their application, and creates testable, demonstrable functionality to show their users. When the UI and back-end services decisions finally get met, it "should be straightforward" to add those elements the application. Stay tuned to learn how this works out (or try it yourself and write me to let me know).应用团队创建 FIT 测试和模拟来隔离他们的应用,并创建可测试、可演示的功能展示给用户。当 UI 和后端服务的决策最终确定后,将这些元素添加到应用中“应该很简单”。敬请期待结果如何(或你自己尝试一下,并写信告诉我结果)。
Related Patterns相关模式
Adapter适配器
The Design Patterns book contains a description of the generic Adapter pattern: "Convert the interface of a class into another interace clients expect." The ports-and-adapters pattern is a particular use of the Adapter pattern.《设计模式》一书包含了对通用适配器模式的描述:“将一个类的接口转换成客户期望的另一个接口。”端口与适配器模式是适配器模式的一种特定用法。
Model-View-Controller 模型-视图-控制器(MVC)
The MVC pattern was implemented as early as 1974 in the Smalltalk project. It has been given, over the early, many variations, such as Model-Interactor and Model-View-Presenter. Each of these implements the idea of ports-and-adapters on the primary ports, not the secondary ports.MVC 模式早在 1974 年就在 Smalltalk 项目中实现了。在早期,它又有了许多变体,例如 Model-Interactor 和 Model-View-Presenter。这些变体都在主要端口上实现了端口与适配器的思想,而不是在次要端口上。
Mock Objects and Loopback模拟对象与回环(Mock Objects and Loopback)
"A mock object is a "double agent" used to test the behaviour of other objects. First, a mock object acts as a faux implementation of an interface or class that mimics the external behaviour of a true implementation. Second, a mock object observes how other objects interact with its methods and compares actual behaviour with preset expectations. When a discrepancy occurs, a mock object can interrupt the test and report the anomaly. If the discrepancy cannot be noted during the test, a verification method called by the tester ensures that all expectations have been met or failures reported." -- From http://MockObjects.com“模拟对象是用于测试其他对象行为的‘双重间谍’。首先,模拟对象作为接口或类的虚假实现,模仿真实实现的外部行为。其次,模拟对象观察其他对象如何与其方法交互,并将实际行为与预设期望进行比较。当出现不一致时,模拟对象可以中断测试并报告异常。如果测试期间无法注意到这种不一致,由测试人员调用的验证方法将确保所有期望均已满足或报告失败。”——来自 http://MockObjects.com
Fully miplemented according to the mock-object agenda, mock objects are used throughout an application, not just at the external interface The primary thrust of the mock object movement is conformance to specified protocol at the individual class and object level. I borrow their word "mock" as the best short description of an in-memory substitute for an external secondary actor.按照模拟对象的目标全面实施,模拟对象用于整个应用,而不仅仅是外部接口。模拟对象运动的主要推动力是在单个类和对象级别上符合指定协议。我借用他们的“mock”一词,作为外部次要参与者的内存替代品的最佳简短描述。
The Loopback pattern is an explicit pattern for creating an internal replacement for an external device. 回环模式是一种明确的模式,用于为外部设备创建内部替代品。
Pedestals 基座(Pedestals)
In "Patterns for Generating a Layered Architecture", Barry Rubel describes a pattern about creating an axis of symmetry in control software that is very similar to ports and adapters. The Pedestal pattern calls for implementing an object representing each hardware device within the system, and linking those objects together in a control layer. The Pedestal pattern can be used to describe either side of the hexagonal architecture, but does not yet stress the similarity across adapters. Also, being written for a mechanical control environment, it is not so easy to see how to apply the pattern to IT applications.在《Patterns for Generating a Layered Architecture》中,Barry Rubel 描述了一种在控制软件中创建对称轴的模式,与端口和适配器非常相似。基座模式要求在系统中为每个硬件设备实现一个对象,并将这些对象在控制层中连接起来。基座模式可以用来描述六边形架构的任一侧,但还没有强调跨适配器的相似性。此外,由于它是为机械控制环境编写的,不太容易看出如何将该模式应用于 IT 应用。
Checks校验(Checks)
Ward Cunningham's pattern language for detecting and handling user input errors, is good for error handling across the inner hexagon boundaries.Ward Cunningham 的用于检测和处理用户输入错误的模式语言,很适合用于跨内部六边形边界的错误处理。
Dependency Inversion (Dependency Injection) and SPRING 依赖倒置(依赖注入)与 SPRING
Bob Martin's Dependency Inversion Principle (also called Dependency Injection by Martin Fowler) states that "High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions." The Dependency Injection pattern by Martin Fowler gives some implementations. These show how to create swappable secondary actor adapters. The code can be typed in directly, as done in the sample code in the article, or using configuration files and having the SPRING framework generate the equivalent code.Bob Martin 的依赖倒置原则(Martin Fowler 称之为依赖注入)指出:“高层模块不应依赖低层模块。两者都应依赖抽象。抽象不应依赖细节。细节应依赖抽象。”Martin Fowler 的依赖注入模式给出了一些实现。这些实现展示了如何创建可替换的次要参与者适配器。代码可以直接输入,如文章中的示例代码那样,也可以使用配置文件,让 SPRING 框架生成等效代码。
Acknowledgements致谢
Thanks to Gyan Sharma at Intermountain Health Care for providing the sample code used here. Thanks to Rebecca Wirfs-Brock for her book Object Design, which when read together with the Adapter pattern from the Design Patterns book, helped me to understand what the hexagon was about. Thanks also to the people on Ward's wiki, who provided comments about this pattern over the years (e.g., particularly Kevin Rutherfold's http://silkandspinach.net/blog/2004/07/hexagonal_soup.html).感谢 Intermountain Health Care 的 Gyan Sharma 提供本文使用的示例代码。感谢 Rebecca Wirfs-Brock 的著作《Object Design》,它与《设计模式》一书中的适配器模式一起阅读,帮助我理解了六边形的含义。还要感谢 Ward 的 wiki 上的人们,多年来他们提供了关于这一模式的评论(例如,特别是 Kevin Rutherfold 的 http://silkandspinach.net/blog/2004/07/hexagonal_soup.html)。
References and Related Reading参考文献与相关阅读
FIT, A Framework for Integrating Testing: Cunningham, W., online at http://fit.c2.com, and Mugridge, R. and Cunningham, W., Fit for Developing Software, Prentice-Hall PTR, 2005.FIT,一个用于集成测试的框架:Cunningham, W.,在线 http://fit.c2.com,以及 Mugridge, R. 和 Cunningham, W.,《Fit for Developing Software》,Prentice-Hall PTR, 2005。
The Adapter pattern: in Gamma, E., Helm, R., Johnson, R., Vlissides, J., Design Patterns, Addison-Wesley, 1995, pp. 139-150.适配器模式:见 Gamma, E., Helm, R., Johnson, R., Vlissides, J.,《Design Patterns》,Addison-Wesley, 1995,第 139-150 页。
The Pedestal pattern: in Rubel, B., "Patterns for Generating a Layered Architecture", in Coplien, J., Schmidt, D., PatternLanguages of Program Design, Addison-Wesley, 1995, pp. 119-150.基座模式:见 Rubel, B.,《Patterns for Generating a Layered Architecture》,收录于 Coplien, J., Schmidt, D. 编,《Pattern Languages of Program Design》,Addison-Wesley, 1995,第 119-150 页。
The Checks pattern: by Cunningham, W., online at http://c2.com/ppr/checks.html校验模式:Cunningham, W.,在线 http://c2.com/ppr/checks.html
The Dependency Inversion Principle: Martin, R., in Agile Software Development Principles Patterns and Practices, Prentice Hall, 2003, Chapter 11: "The Dependency-Inversion Principle", and online at http://www.objectmentor.com/resources/articles/dip.pdf依赖倒置原则:Martin, R.,收录于《Agile Software Development Principles Patterns and Practices》,Prentice Hall, 2003,第 11 章:“The Dependency-Inversion Principle”,在线 http://www.objectmentor.com/resources/articles/dip.pdf
The Dependency Injection pattern: Fowler, M., online at http://www.martinfowler.com/articles/injection.html依赖注入模式:Fowler, M.,在线 http://www.martinfowler.com/articles/injection.html
The Mock Object pattern: Freeman, S. online at http://MockObjects.com模拟对象模式:Freeman, S.,在线 http://MockObjects.com
The Loopback pattern: Cockburn, A., online at http://c2.com/cgi/wiki?LoopBack回环模式:Cockburn, A.,在线 http://c2.com/cgi/wiki?LoopBack
Use cases: Cockburn, A., Writing Effective Use Cases, Addison-Wesley, 2001, and Cockburn, A., "Structuring Use Cases with Goals", online at http://alistair.cockburn.us/crystal/articles/sucwg/structuringucswithgoals.htm用例:Cockburn, A.,《Writing Effective Use Cases》,Addison-Wesley, 2001,以及 Cockburn, A.,《Structuring Use Cases with Goals》,在线 http://alistair.cockburn.us/crystal/articles/sucwg/structuringucswithgoals.htm