六边形架构理论

六边形架构理论

无需使用UI或数据库即可创建应用程序,以便您可以针对应用程序运行自动化回归测试,在数据库变得不可用时进行工作,并在没有任何用户参与的情况下将应用程序链接在一起。

模式:端口和适配器(“对象结构”)

替代名称:''端口和适配器''

替代名称:''六角形建筑''

意图

允许应用程序同样由用户,程序,自动化测试或批处理脚本驱动,并与其最终运行时设备和数据库隔离开发和测试。

当事件从外部到达一个端口时,一个特定于技术的适配器将其转换为可用的过程调用或消息并将其传递给应用程序。该应用程序对输入设备的性质非常无知。当应用程序需要发送内容时,它通过端口将其发送到适配器,从而创建接收技术(人工或自动)所需的适当信号。应用程序与适配器在其各个方面都有语义上的良好交互,但实际上并不知道适配器另一端的事物的性质。

动机

多年来,软件应用程序的一大优点是业务逻辑渗透到用户界面代码中。这导致的问题有三个:

  • 首先,系统无法用自动化测试套件进行整齐测试,因为需要测试的部分逻辑取决于经常变化的视觉细节,如字段大小和按钮位置;
  • 出于完全相同的原因,从人为驱动的系统使用转变为批处理系统变得不可能。
  • 出于同样的原因,当变得具有吸引力时,允许程序由另一程序驱动变得困难或不可能。

 

尝试的解决方案在许多组织中重复出现,即在该体系结构中创建一个新层,承诺在这一次,真正而真实的情况下,不会将业务逻辑放入新层。然而,几年之后,该组织发现没有机制来检测何时发生了违反该承诺的情况,因此新的层面混杂着业务逻辑,旧的问题再次出现。

现在想象一下,应用程序提供的“每一个”功能都可以通过API(应用程序编程接口)或函数调用来获得。在这种情况下,测试或QA部门可以针对应用程序运行自动测试脚本,以检测任何新编码何时会破坏以前的工作功能。在GUI细节完成之前,业务专家可以创建自动化测试用例,告诉程序员他们何时完成了他们的工作(这些测试成为测试部门运行的测试)。应用程序可以以“无头”模式进行部署,因此只有API可用,和其他程序可以利用其功能 - 这简化了复杂应用程序套件的整体设计,并且还允许企业对企业服务应用程序彼此使用而无需人工干预网络。最后,自动化函数回归测试检测到任何违反承诺的情况,以将业务逻辑从表示层中排除出去。该组织可以检测并纠正逻辑泄漏。

在通常被认为是应用程序的“另一方”的应用程序逻辑与外部数据库或其他服务绑定时存在一个有趣的类似问题。当数据库服务器出现故障或进行大量返工或更换时,程序员无法工作,因为他们的工作与数据库的存在有关。这会造成延迟成本,而且往往会造成人们之间的不愉快感

这两个问题并不明显是相关的,但它们之间存在对称性,表现为解决方案的性质。

解决方案的性质

用户端和服务器端问题实际上都是由设计和编程中的相同错误引起的 - 业务逻辑和与外部实体交互的纠缠。利用的不对称性不在应用程序的“左侧”和“右侧”之间,而是在应用程序的“内部”和“外部”之间。遵守的规则是与“内部”部分有关的代码不应泄漏到“外部”部分。

暂时消除任何左右或上下不对称,我们看到应用程序通过“端口”与外部代理进行通信。“端口”这个词应该是在操作系统中唤起“端口”的想法,任何符合端口协议的设备都可以插入其中; 和电子产品上的“端口”,再次,任何符合机械和电气协议的设备都可以插入。

  • 端口的协议由两个设备之间的对话目的给出。

该协议采用应用程序接口(API)的形式。

每个外部设备都有一个“适配器”,可将API定义转换为该设备所需的信号,反之亦然。图形用户界面或GUI是适配器的示例,其将人员的移动映射到端口的API。适合相同端口的其他适配器是自动化测试工具,如FIT或Fitnesse,批处理驱动程序以及在整个企业或网络中的应用程序之间进行通信所需的任何代码。

在应用程序的另一端,应用程序与外部实体通信以获取数据。该协议通常是数据库协议。从应用程序的角度来看,如果数据库从SQL数据库移动到平面文件或任何其他类型的数据库,跨API的对话不应改变。因此,同一端口的其他适配器包括一个SQL适配器,一个平面文件适配器,最重要的是一个适用于“模拟”数据库的适配器,它位于内存中,根本不依赖真实数据库。

许多应用程序只有两个端口:用户端对话框和数据库端对话框。这使它们具有不对称的外观,这使得在一维,三层,四层或五层堆叠架构中构建应用程序似乎很自然。

这些图纸有两个问题。首先也是最糟糕的是,人们往往不认真对待分层图中的“线条”。他们让应用程序逻辑泄漏到层边界,导致上述问题。其次,应用程序可能有两个以上的端口,因此该体系结构不适合一维图层。

六边形或端口和适配器架构通过注意这种情况下的对称性来解决这些问题:内部应用程序通过一些端口与外部事物进行通信。应用程序外的项目可以对称处理。

六边形旨在突出显示

(a)内外不对称和港口的类似性质,以摆脱一维层次图片和所有令人想起的内容,以及

(b)定义数量的不同端口的存在 - 两个,三个或四个(迄今为止我遇到过的最多四个)。

六边形不是六边形,因为六边形很重要,而是让绘图人员有空间根据需要插入端口和适配器,而不受一维分层绘图的限制。“六角形建筑”这个术语来自这种视觉效果。

术语“端口和适配器”表示图纸各部分的“目的”。一个端口标识一个有目的的对话。对于任何一个端口,通常会有多个适配器,用于可能插入该端口的各种技术。通常,这些可能包括电话应答机,人声,按键式电话,图形人机界面,测试设备,批驱动程序,http界面,直接程序到程序界面,模拟(在-memory)数据库,一个真实的数据库(可能是用于开发,测试和实际使用的不同数据库)。

在应用笔记中,左右不对称将再次出现。然而,这种模式的主要目的是关注内外部的不对称,简单地假装从应用的角度来看,所有外部项目都是相同的。

结构体

图2:使用adapters.gif的六边形体系结构 

 

图2显示了一个应用程序,每个端口有两个活动端口和几个适配器。这两个端口是应用程序控制端和数据检索端。此图显示应用程序可以由自动化的系统级回归测试套件,由人类用户,由远程http应用程序或由另一本地应用程序同等驱动。在数据方面,可以将应用程序配置为使用内存中的oracle或“mock”数据库替换从外部数据库解耦; 或者它可以针对测试或运行时数据库运行。应用程序的功能规范(可能在使用情况下)是针对内六角形的界面制作的,而不是针对任何可能使用的外部技术。

图3:六角建筑谷仓门image.gif

 

图3显示了映射到三层体系结构图的相同应用程序。为简化绘图,每个端口只显示两个适配器。本图旨在说明多个适配器如何安装在顶层和底层,以及系统开发过程中各种适配器的使用顺序。编号的箭头显示了团队开发和使用应用程序的顺序:

    1. 使用FIT测试工具来驱动应用程序并使用模拟(内存中)数据库替换真实数据库;
    2. 向应用程序添加一个GUI,仍然运行模拟数据库;
    3. 在集成测试中,使用自动化测试脚本(例如来自Cruise Control的应用程序)驱动应用程序,使其与包含测试数据
    4. 在实际使用中,与使用该应用程序的人访问实时数据库。

示例代码

幸运的是,展示端口和适配器的最简单的应用程序随FIT文档一起提供。这是一个简单的折扣计算应用程序:

discount(amount) = amount * rate(amount);

在我们的改编中,金额将来自用户,费率将来自数据库,因此将有两个端口。我们分阶段实施它们:

  • 通过测试,但以恒定的速率而不是模拟数据库,
  • 然后用GUI,
  • 然后用模拟数据库,可以换出一个真正的数据库。
阶段1:FIT 应用 恒定的,模拟数据库

首先,我们将测试用例创建为HTML表格(请参阅适用于此的FIT文档):

TestDiscounter 
amountdiscount()
1005
20010

 

请注意,列名将成为我们程序中的类和函数名称。FIT包含摆脱这种“节目清单”的方法,但是对于本文而言,放置它们更容易。

知道测试数据是什么,我们创建了用户端适配器,随附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); } 
}

这实际上就是适配器的全部功能。到目前为止,测试从命令行运行(请参阅FIT手册,了解您需要的路径)。我们使用了这一个:

set FIT_HOME=/FIT/FitLibraryForFit15Feb2005
java -cp %FIT_HOME%/lib/javaFit1.1b.jar;%FIT_HOME%/dist/fitLibraryForFit.jar;src;bin
fit.FileRunner test/Discounter.html TestDiscount_Output.html

FIT会生成一个输出文件,其中显示颜色通过的输入文件(或者失败,如果我们在路上发生错字)。

此时,代码已准备就绪,可以挂载到Cruise Control或自动构建机器中,并包含在构建和测试套件中。

阶段2:用户界面 应用 恒定的,模拟数据库

我打算让你创建你自己的用户界面,并让它驱动Discounter应用程序,因为代码有点长,可以包含在这里。代码中的一些关键行是这些:

...
 Discounter app = new Discounter();
public void actionPerformed(ActionEvent event) 
{
    ...
   String amountStr = text1.getText();
   double amount = Double.parseDouble(amountStr);
   discount = app.discount(amount));
   text3.setText( "" + discount );
   ...

此时应用程序可以进行演示和回归测试。用户端适配器都在运行

阶段3:(FIT或UI) 应用 模拟数据库

为了为数据库端创建一个可替换的适配器,我们为一个存储库创建一个“接口”,一个将生成模拟数据库或实际服务对象的“RepositoryFactory”,以及数据库的内存模拟。

public interface RateRepository 
{
   double getRate(double amount);
 }
public class RepositoryFactory 
{
   public RepositoryFactory() {  super(); }
   public static RateRepository getMockRateRepository() 
   {
      return new MockRateRepository();
   }
}
public class MockRateRepository implements RateRepository 
{
   public double getRate(double amount) 
   {
      if(amount <= 100) return 0.01;
      if(amount <= 1000) return 0.02;
      return 0.05;
    }
 }

要将此适配器挂接到Discounter应用程序,我们需要更新应用程序本身以接受要使用的存储库适配器,并让(FIT或UI)用户端适配器将存储库传递给构造函数(真实或模拟)应用程序本身。这里是更新的应用程序和一个传入模拟存储库的FIT适配器(FIT适配器代码选择是否传入模拟或真实存储库的适配器的时间较长,但没有添加太多新信息,因此我在此省略此版本)。

import repository.RepositoryFactory;
import repository.RateRepository;
public class Discounter 
{
   private RateRepository rateRepository;
   public Discounter(RateRepository r) 
   {
      super();
      rateRepository = r;
    }
   public double discount(double amount) 
   {
      double rate = rateRepository.getRate( amount ); 
      return amount * rate;
    }
}
import app.Discounter;
import fit.ColumnFixture;
public class TestDiscounter extends ColumnFixture 
{
   private Discounter app = 
       new Discounter(RepositoryFactory.getMockRateRepository());
   public double amount;
   public double discount() 
   {
      return app.discount( amount );
   }
}

这就结束了最简单的六角形结构的实现。

对于不同的实现,使用Ruby和Rack进行浏览器使用,请参阅https://github.com/totheralistair/SmallerWebHexagon

应用笔记

左右不对称

端口和适配器模式是故意编写的,假设所有端口基本相似。这种伪装在架构层面很有用。在实施过程中,端口和适配器有两种口味,我将其称为“主要”和“次要”,这是因为很明显的原因。它们也可以被称为“驱动”适配器和“驱动”适配器。

警示读者会注意到,在所有的例子中,FIT灯具用于左侧的端口和右侧的嘲笑。在三层架构中,FIT位于顶层,模拟位于底层。

这与来自“主要参与者”和“次要参与者”用例的想法有关。一个“主要演员”是一个驱动应用程序的演员(从静止状态中执行它的一个广告功能)。一个“辅助演员”是应用程序驱动的一个人,要么从中获得答案,要么只是通知。“主要”与“次要”之间的区别在于谁触发或负责对话。

用于替代“主要”参与者的自然测试适配器是FIT,因为该框架旨在读取脚本并驱动应用程序。自然的测试适配器可以替代数据库等“次要”角色,这是一种模拟,因为它被设计用来回答应用程序中的查询或记录事件。

这些观察导致我们遵循系统的用例上下文图,并在六边形的左侧(或顶部)绘制“主要端口”和“主要适配器”,以及“次要端口”和“ '六边形右侧(或底部)侧的“辅助适配器”。

主要和次要端口/适配器之间的关系及其在FIT和mock中的相应实现有助于记住,但应该将其作为使用端口和适配器架构的结果,而不是将其短路。端口和适配器实现的最终好处是能够以完全隔离模式运行应用程序。

用例与应用边界

使用六边形体系结构模式来强化编写用例的首选方法非常有用。

一个常见的错误是编写用例来包含对每个端口以外的技术的深入了解。这些使用案例在行业中因为冗长,难以阅读,枯燥,易碎和维护成本高而赢得了一个合理的坏名声。

了解端口和适配器体系结构,我们可以看到,通常应该在应用程序边界(内部六边形)编写用例,以指定应用程序支持的功能和事件,而不考虑外部技术。这些用例更短,更易于阅读,维护成本更低,并且随着时间推移更稳定。

多少个端口?

究竟港口是什么,不是港口的味道。在极端情况下,每个用例都可以拥有自己的端口,为许多应用程序生成数百个端口。或者,可以想象合并所有主要端口和所有次要端口,因此只有两个端口,即左侧和右侧。

这两种极端都不是最佳的。

已知用途中描述的天气系统有四个天然端口:天气馈送,管理员,通知用户和用户数据库。咖啡机控制器有四个自然端口:用户,包含食谱和价格的数据库,分配器和硬币盒。医院用药系统可能有三个:一个用于护士,一个用于处方数据库,另一个用于计算机控制器药物分配器。

在选择“错误”的端口数量时似乎没有任何特别的损害,所以这仍然是一个直觉问题。我的选择倾向于如上所述和已知用途中的少量,两个,三个或四个端口。

已知用途

图4:六角形体系结构complex example.gif

六角形建筑群complex.gif

图4显示了每个端口上有四个端口和几个适配器的应用程序。这是从一个应用程序获得的,该应用程序倾听来自国家气象部门关于地震,龙卷风,火灾和洪水的警报,并通过电话或电话答录机通知人们。在我们讨论这个系统的时候,系统的接口被“技术链接到目的”所识别和讨论。有一个触发数据通过导线馈送到达的接口,一个用于通知数据发送到应答机,一个在GUI中实现的管理接口和一个数据库接口,用于获取用户数据。

人们都在苦苦挣扎,因为他们需要从天气服务中添加一个http界面,这是一个给用户的电子邮件界面,他们必须找到一种方法来捆绑和解开不断增长的应用程序套件,以满足不同的客户购买偏好。他们担心他们正在盯着维护和测试的噩梦,因为他们必须实施,测试和维护所有组合和排列的单独版本。

他们在设计上的转变是“按照目的”而不是技术构建系统接口,并且通过适配器使这些技术可以被替代(全方位)。他们立即拿起了包含http提要和电子邮件通知的能力(新的适配器在图中以虚线显示)。通过使每个应用程序都可以通过API在无头模式下执行,他们可以添加一个应用程序添加适配器并解开应用程序套件,按需连接子应用程序。最后,通过将每个应用程序的可执行程序完全隔离开来,使用测试和模拟适配器,他们就能够使用独立的自动化测试脚本对其应用程序进行回归测试。

Mac,Windows,Google,Flickr,Web 2.0

在20世纪90年代早期,MacIntosh应用程序(如文字处理器应用程序)需要具有API可驱动接口,以便应用程序和用户编写的脚本可以访问应用程序的所有功能。Windows桌面应用程序已经发展出同样的能力(我没有历史知识来说明哪个先出现,也没有关于这一点)。

当前(2005年)Web应用程序的趋势是发布API,并让其他Web应用程序直接访问这些API。因此,可以通过Google地图发布本地犯罪数据,或创建包含Flickr照片归档和注释功能的网络应用程序。

所有这些示例都是关于使“主要”“端口”API可见的。我们在这里看不到有关次要端口的信息。

存储的输出

这个例子由Willem Bogaerts在C2维基上撰写:

“我遇到过类似的情况,但主要是因为我的应用层很有可能成为电话交换机,管理不应该做的事情。我的应用程序生成的输出,显示给用户,然后有一些可能性来存储它。我的主要问题是你不需要总是存储它。所以我的应用程序生成输出,不得不缓冲它并将其呈现给用户。然后,当用户决定要存储输出时,应用程序检索缓冲区并将其存储为真实。

我根本不喜欢这个。然后我想出了一个解决方案:使用存储设施进行演示控制。现在,应用程序不再以不同方向输出输出,但它只是将其输出到演示文稿控件。这是演示文稿控件缓冲答案并给用户存储它的可能性。

传统的分层架构强调“UI”和“存储”是不同的。端口和适配器架构可以减少输出再次简单地“输出”。”

来自C2维基的匿名例子

“在我工作的一个项目中,我们使用了组件立体声系统的SystemMetaphor。每个组件都有定义的接口,每个接口都有特定的用途。然后,我们可以使用简单的电缆和适配器以几乎无限制的方式将组件连接在一起。

分布式的大型团队开发

这个仍然在试用中,所以不适合作为模式的使用。但是,考虑一下很有趣。

不同地点的团队都使用FIT和Mock构建六边形架构,以便应用程序或组件可以在独立模式下进行测试。CruiseControl构建每半小时运行一次,并使用FIT +模拟组合运行所有应用程序。随着应用程序子系统和数据库的完成,mock被替换为测试数据库。

分离UI和应用程序逻辑的开发

这一项目仍处于早期试用阶段,因此不算作该模式的使用。但是,考虑一下很有趣。

UI设计不稳定,因为他们尚未决定驾驶技术或隐喻。后端服务架构尚未确定,实际上在未来六个月内可能会多次改变。尽管如此,该项目已正式启动,时间正在流逝。

应用程序团队创建FIT测试和模拟来隔离他们的应用程序,并创建可测试的,可显示的功能来向用户展示。当用户界面和后端服务决策最终得到满足时,“添加应用程序的这些元素应该很简单”。请继续关注如何解决这个问题(或者亲自尝试并写信给我,让我知道)。

相关模式

适配器

“设计模式”一书包含了对通用“适配器”模式的描述:“将一个类的接口转换为另一个interace客户端所期望的”。端口和适配器模式是“适配器''模式。

模型 - 视图 - 控制器

MVC模式早在1974年就在Smalltalk项目中实现。多年来,它已被赋予许多变体,例如模型交互器和模型视图演示器。其中每个都实现了主端口上的端口和适配器的概念,而不是次端口。

模拟对象和环回

“模拟对象是用于测试其他对象行为的”双重代理“。首先,模拟对象充当模仿真实实现的外部行为的接口或类的虚拟实现。其次,模拟对象观察其他对象如何与其方法进行交互,并将实际行为与预设的期望进行比较。当出现差异时,模拟对象可以中断测试并报告异常情况。如果在测试过程中无法注意到差异,则测试人员所称的验证方法可确保满足所有期望或报告失败。“ - 来自http://MockObjects.com

根据模拟对象议程完全实现,模拟对象在整个应用程序中使用,而不仅仅在外部接口上。模拟对象移动的主要推动力是在特定的类和对象级别符合指定的协议。我借用他们的“模拟”一词作为对外部辅助演员的内存替代品的最佳简短描述。

环回模式是用于为外部设备创建内部替换的明确模式。

基座

在“用于生成分层架构的模式”中,Barry Rubel描述了一种模式,用于在控制软件中创建一个与端口和适配器非常相似的对称轴。“Pedestal”模式要求实现一个代表系统内每个硬件设备的对象,并将这些对象连接在一个控制层中。“基座”模式可用于描述六边形结构的任何一侧,但并未强调适配器间的相似性。另外,为机械控制环境编写代码,要了解如何将这种模式应用于IT应用程序并不那么容易。

检查

Ward Cunningham用于检测和处理用户输入错误的模式语言,适用于内部六边形边界的错误处理。

依赖倒置(依赖注入)和SPRING

Bob Martin的依赖倒置原则(也称为Martin Fowler的依赖注入)指出:“高级模块不应该依赖于低级模块。两者都应该依赖于抽象。抽象不应该依赖细节。细节应该取决于抽象。“Martin Fowler的'依赖注入'模式给出了一些实现。这些展示了如何创建可交换的辅助角色适配器。代码可以直接输入,如文章中的示例代码所示,也可以使用配置文件并使SPRING框架生成等效代码。

 

转载于:https://www.cnblogs.com/chenxygx/p/8867857.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值