使用六边形架构解耦技术代码与业务逻辑 - Julien Topçu


在我工作过的一家公司中,我的团队被要求将旧应用程序移植到全新的堆栈上(例如从EAR / SQL应用程序迁移到独立的/ NoSQL应用程序)。通过研究,我们很快意识到我们必须重做整个基础架构……新框架与十年前所使用的框架有很大不同。实际上,唯一不需要更改的就是业务逻辑。因此重用它是有意义的,对吧?

经过更深入的研究,名为model的Maven模块只是POJO,完全是贫血的 …尽管还有服务模块,但是业务逻辑在所有层之间都是共享的,淹没在许多技术代码(DAO创建,序列化,池管理)中等)。业务的某些部分依赖于我们尝试删除的旧框架的技术。在技术代码和业务逻辑之间没有明确的区分。

关注点分离
我们寻求一种方法来实现这种分离,即如果有一天我们必须再次更改技术堆栈,则可以完全重用业务逻辑。我们的一位同事向我们介绍了六角形架构。该体系结构的关键概念之一是将所有业务模型/逻辑放在一个地方。因此,如果我回顾以前的架构,我们将得到以下内容:


另一个关键概念是领域只依赖于自身 ; 这是确保业务逻辑与技术层分离的唯一方法。我们如何在先前的架构上实现这一目标?领域显然取决于持久层!
通过使用一种模式:控制反转IOC。如果我们以此来重做以前的模式,并将领域称为Hexagon,因为Alistair Cockburn(HexArch的创建者)喜欢这种六边形形状:

好的,控制反转非常神奇,稍后我们将看到其工作原理。但是现在您了解了什么是六角结构:

  • 只有两个世界,在六边里面是所有 的业务模式/逻辑;六边外:是基础设施,代表你的所有技术规范。
  • 依赖关系始终从外部进入内部,这确保了业务域的隔离(因此,如果您以后更改基础架构,业务逻辑可以重用!)。
  • 一个必然的结果是,六边形内一切一定不能依赖任何技术框架,包括诸如Jackson或JPA之类的外部注释。要确保使用maven,可以使用执行器插件

让我为您解释最后一点,这是非常重要的一点。在另一个经验中,我的团队不得不将应用程序从“传统” Spring框架移植到Spring Boot。Spring Boot为您简化了很多事情;这意味着它有时可能与Spring框架有很大的不同。我们遇到的主要(痛苦)问题是,我们在Spring Integration Tests中大量利用其来验证我们的功能,而这些功能在很大程度上依赖于Spring。而且因为我们没有意识到Spring Boot与我们正在使用的其他框架有特殊的集成,所以我们第一次集成它时,所有这些测试都失败了。我们无法说出我们是在某个地方破坏了业务逻辑还是源于纯粹的技术问题。

通过确保六边形不依赖任何框架,即使您决定更改堆栈,也可以确保您的业​​务域可以重用。您还将增加域的可测试性,因为您不再将其与集成问题混在一起,因此您将进行实际的功能测试。这样,这些测试将直接与六边形交互,并且仅与六边形交互。我稍后再讨论。

六边形魔术
还记得控制权的倒置吗?为了确保六边形的隔离,对下游层的依赖性已被反转。如您所见,该技巧实际上非常简单:

六边形(基础结构)的外部分为两个虚拟部分,左侧和右侧。在左侧,您可以查询域的所有内容(控制器,REST层等),在右侧,您可以为域提供一些信息/服务的所有内容(持久层,第三方服务等) )。为了让外部与域进行交互,Hexagon提供了分为两类的业务接口:

  • API收集所有查询域所需的所有接口。这些接口由六边形实现。
  • SPI(服务提供者接口)收集所有由领域检索信息或从第三方获得某些服务所需的接口。这些接口在六边形中定义,并在基础结构的右侧实现。我们将看到,在某些情况下,六边形也可以实现SPI。

这里有两个重要事实:
  • API和SPI是六边形的一部分。
  • API和SPI仅操作六边形的域对象,以确保隔离。

在经典的分层架构中,业务对象或服务通常会创建DAO实现持久层。在六边形中,域仅处理域对象。持久层负责将域对象转换为要持久化的任何技术对象(通过DAO),如带注释的JPA POJO或其他方式)。

你能感觉到力量吗?
六边形体系结构也称为端口和适配器体系结构。它来自于此体系结构的模块化功能。因为一切都是分离的,所以您可以在领域的前面同时拥有一个SOAP,REST和JMS层,而不会对其产生任何影响。在SPI方面,如果需要,您可以从MongoDB驱动程序实现更改为Cassandra。由于更改持久性模块不会更改SPI,因此其余软件不会受到影响。API和SPI是端口,使用或实现它们的基础结构模块是适配器。

如何执行呢?
这里还有一个规则:总是从六边形的内部开始。这将为您带来很多好处:

  • 关注功能而不是技术细节。因为只有功能才能为您的公司带来价值。在另一个业务领域工作的开发人员可以放置一个Spring Controller。但是,除非他在一家会计公司工作,否则双倍余额递减法对他来说听起来就像Wookiee一样。
  • 延迟技术实施的选择。有时很难知道您真正需要哪种技术实现(例如Spring Data,JPA或Mongo,Cassandra,Redis等)。延迟此选择可以帮助您专注于为公司带来主要价值的要素-功能。此外,在实现了业务逻辑之后,一些新元素可以帮助您做出有关基础结构的最佳选择(例如,读取多于写入,或者域比预期多相关)。
  • 一个必然的结论是,它确保Hexagon是独立的,我已经在隔离方面谈论了很多,现在这很简单。由于您切勿在没有测试的情况下编写代码,这意味着Hexagon是经过自我测试的。此外,我们在这里获得了仅针对业务的真实功能/验收测试。

我的建议是首先以BDD / ATDD方式编写您的功能/验收方案。然后编写API接口,它将成为功能的入口点。实施您的测试(赢取TDD!)以及实施您的业务逻辑之后。您可能需要定义一个SPI(例如从数据库中检索一些数据)就可以了。并且由于尚未实现右侧,因此请在六边形内部创建SPI的存根实现(例如,使用HashMap的内存数据库)。

您可以选择将存根实现保留在应用程序的测试范围内,也可以根据需要临时提供。例如,一旦我们在六边形上实现了第一个功能,就需要对外部第三方服务和数据库进行存根。因为我们的客户需要我们提供接口合同,所以我们接下来通过REST控制器导出了域。因此,我们在基础架构的右侧发布了带有存根数据的第一个版本,但是客户端能够看到我们的数据结构以及该功能的预期行为。而且,与手动创建功能的输入和输出的一些JSON示例相比,它要可靠得多,因为它实际上处理了实际的业务约束。
下一步通常是先打开左侧。这样,您可以对功能进行一些集成测试。目前,您可以提供一些实时文档,并确保与客户的接口合同。


最后,通过利用您进行的集成测试来实现功能的SPI,在右侧打开。我强烈建议您的测试是独立的,以避免在构建期间出现任何不稳定情况。您应该始终使用Wiremock等外部服务或Fongo模拟MongoDB来模拟第三方。


为您的其他功能循环同样的方法。
有关更多信息,可在GitLab上获取六角结构测试策略

总结的六边形架构
将业务逻辑与技术代码脱钩确实有好处。就技术的不断发展而言,它确保您的业务领域持久耐用。
六边形形体系结构为您提供了一种实现此目标的真正方法:

  • 将所有业务模型/逻辑放在一个地方。
  • 域(六边形的内部)是隔离的,并且与技术部分(六边形外部的基础结构)无关,因为它仅依赖于自身。这就是为什么依赖项总是从六边形的外部传播到内部的原因。
  • 六边形是一个独立的模块。通过编写无需处理技术问题的实际功能测试,它可以提高您域的可测试性。
  • 该体系结构提供了强大的模块化功能,可帮助您编写所需数量的适配器,而对其余软件的影响很小。而且由于该域在堆栈中是不可知的,因此可以在不影响业务的情况下更改堆栈。
  • 通过始终从六边形的内部开始,您可以通过专注于功能开发来确保快速为公司创造价值。这样,您可以延迟技术实施的选择,以便在正确的时间做出最佳选择。

六边形结构不应在所有情况下使用。就像DDD(并且六边形与之非常匹配)一样,如果您拥有真正的业务领域,则这确实适用。对于将数据转换为另一种格式的应用程序来说,这可能是过大的选择。
为此,在采用新技术时始终要务实。如前所述,六边形一定不能依赖任何技术框架,但是您可以例外。例如,在我们的案例中,六边形有三个例外:Apache Commons Lang3(StringUtils),SLF4J和Findbugs的JSR305。因为我们不想再制造轮子,所以我们认为那些框架对领域的影响很小。六边形的一个很好的副作用是您在集成新框架之前一直挑战自己。在六边形之前,我们已经获得了该域的大约五十个依赖关系,并且已将其减少到其中的三个或四个。从安全角度来看,这是非常好的。

想看一些代码吗?在GitLab上检出使用六角结构构建的Kotlin / SpringBoot演示应用程序。

spi 是不是就类似  dao层的 接口