自从我开始在一家构建新企业应用程序的公司工作已经六个月了。我的新团队遵循一些敏捷实践,例如结对编程和测试驱动开发 (TDD),我真诚地感到阳光在照耀我们!
嗯,差不多。
在构建可靠的企业应用程序这一主题上,我现在和过去的行业经验都面临着一些问题,我想在本文中向您解释。
此外,我还将提出一种简单的基于测试的方法来构建企业应用程序,以增强团队沟通并促进更快的高质量代码交付。
事不宜迟,让我们开始吧!
敏捷实践对于软件的快速原型设计非常有益。 TDD 位于此类实践的核心,为软件提供了最重要的属性:稳健性。预先编写测试迫使开发人员考虑他们构建的软件组件的预期和异常行为,提高代码质量并确保满足功能需求。
此外,TDD 是一种强大的实践,它使开发人员不必害怕修复、清理和调整他们的代码以适应功能需求更新。这一切都很棒。然而,将 TDD 付诸实践并不容易。
在构建任何新的企业应用程序之初,我们(开发人员)收集了几个功能需求。然后,我们推导出一组满足这些功能需求的用例。之后,我们开发位于不同层的软件组件,从最高到最低,深入到应用程序的核心,即它的域模型。这就是自上而下的软件开发过程的定义。
然而,自下而上的软件开发过程更适合 TDD。与自顶向下的替代方案相比,自底向上是一种更实用的方法,因为它使我们这些开发人员能够从最基本的(即领域模型)开始逐步覆盖所有间接级别,并逐渐向更高层次的抽象。它允许产生更健全的应用程序代码基础,这反过来又使我们对我们的工作充满信心。然而,要以自上而下的方法应用 TDD,首先应该为位于较高层的软件组件编写测试。这样,开发人员不需要模拟对任何低层组件的任何依赖项,因为它们根本不存在。
创建这种依赖关系的需求已经是一个问题,因为模拟低层组件并不总是可能的,或者在最好的情况下,感觉违反直觉,例如,想象自己必须模拟域对象的逻辑才能产生一个服务组件测试。
此外,我个人怀疑它是否会带来任何价值,因为我认为中间组件验证应该始终执行所有依赖关系,除了可以模拟的外部服务(例如数据库)的依赖关系。更重要的是,由于将非平凡的功能需求实现为代码的内在复杂性,直到在领域模型的实现过程中开始对它们进行推理时,人们才能完全理解某些给定的功能需求对领域模型的技术影响。
再一次,开始编写中间软件组件的测试并没有带来太多价值,因为一旦真正实现了低层软件工件,这些测试中的许多(如果不是全部的话)很可能会被扔进垃圾箱。
此外,我看到软件开发人员(尤其是初级团队成员)放弃并为手头的用例实现了一些概念证明,而没有编写任何验证逻辑。这是使用代码优先的做法,这违背了 TDD 的目的。此外,如果不遵循正确的持续交付实践,最终将未经验证的代码推送到我们的版本控制存储库的风险很高。
那么,在给定一组功能需求的情况下,我们如何才能有效地将 TDD 应用到企业应用程序的生产中呢?
互联网上有大量关于六边形架构的文献。我特别推荐阅读 Alistair Cockburn 撰写的有关该主题的白皮书。
出于本文的目的,请允许我告诉您一个真实的短篇故事,旨在简要说明六边形架构的动机和主要好处: 在我开发企业应用程序的多年中,我见过很多人(包括我自己)开始专注于其他主题而不是我们的真正使命的新项目。这样的使命包括为我们工作的公司提供实际价值。该值在我们应用程序的域逻辑上。
用鲍勃叔叔在他的《清洁架构》一书中的话说,其他的一切都是一种干扰,一个可以(并且应该)推迟的实现细节,理想情况下推迟到开发结束。实现细节的示例是数据库技术、控制器逻辑或前端技术。甚至后端框架也是一个实现细节,如果我们真的需要,我们可以在开发过程的后期选择。六边形架构,也称为端口和适配器,是一种架构模式,旨在将软件应用程序核心逻辑与外部实现细节分离。
我们开发人员应该专注于企业应用程序的核心逻辑,并推迟与外部服务通信所需的逻辑的实现。为了实现这个目标,我们真正需要做的就是编写一些接口(所谓的端口)并模拟与外部服务实际通信的组件(所谓的适配器)。因此,适配器实现可以在开发过程的后期进行。而且它们来得越晚越好,因为我们在生成核心逻辑时获得的所有见解在决定选择哪些技术时非常有用。
下图是六角建筑的众多现有插图之一:
考虑构成内六边形的元素。除了领域模型,还有一层应用服务。这些软件组件不指定任何域逻辑。相反,它们是适配器和域模型逻辑的简单协调器。应用程序服务实现了一个用例,该用例负责处理企业应用程序的功能需求子集。这是接下来要记住的重要数据。
正如我之前所说,在遵循自下而上的软件开发过程时,应用 TDD 会更容易。然而,我们中的许多人发现遵循自上而下的方法更容易推理系统设计。虽然看起来我们正在引发利益冲突,但这很好,因为我们可以开始设计(即,用伪代码或一些 UML 图绘制)我们的应用程序服务自顶向下,而无需为它们编写一行代码;直到我们完成领域模型的实现。
在开始编码之前,我们可以将应用程序服务解释为一些软件设计指南,以执行我们要构建的企业应用程序的垂直切片。每个垂直切片代表用例的整个执行路径,从上游外部服务或用户在 UI 中执行的操作到在下游外部服务上执行的任何操作。当我们完成应用程序服务的设计时,我们已经确定了需要实现哪些适配器和域组件。现在我们已经了解了域模型,接下来我们可以通过应用 TDD 来实现其基本组件。
然后,我们可以按照测试优先的方法实现应用程序服务,为任何外部服务适配器创建一个端口并模拟其实际实现。当我们完成应用服务和相关领域模型的实现时,我们可以确定这样的实现是有效的,即没有错误并且符合其功能需求。最后,我们可以实现适配器逻辑,也可以应用 TDD。
这种方法可以实现企业应用程序的快速和渐进式实施,使开发人员能够对他们创建的所有组件的有效性充满信心,而无需放弃任何测试。此外,它对功能需求更新没有任何限制。
以下流程图描述了编写一个用例的逻辑的方法。请注意,我不讨论具体的测试类型,因为这是一个颇具争议的主题,尽管我建议遵循The Practical Test Pyramid 文章中使用的约定和术语。
提议的方法简化了团队任务分配,无论他们是单独工作还是成对工作。它的一些步骤可以并行完成,例如,一个队友/对可以构建核心逻辑来模拟对外部依赖项的任何引用,而另一个可以开发具有这种外部依赖项的集成代码,因为这两个部分是解耦,从而缩短交货时间。他们需要做的就是为实现为端口的外部依赖传递一些 API。 mock 和实际的外部依赖都需要实现上述端口接口。同样,如果企业应用程序需要,另一个团队/队友/对可以为用例实现前端部分。
另一方面,由于其结构化的性质,在同行之间交流具体用例的开发状态很容易。在移交用例开发时,离职实体可以简单地将新实体指向他们正在设计或实施的当前应用程序服务。然后,新实体可以简单地跟踪代码库中已在适配器或域逻辑上创建的任何内容。
关于实现细节的最后一点说明:我们可以通过编写数据库技术的注释/装饰器来更新我们的域模型以指定其中的一些细节,并从最适合我们应用程序性质的底层框架输入数据验证资源。但是,我建议不要这样做,因为这会将实现细节泄漏到我们的域模型中,这不是最佳实践,因为实现细节和域模型往往会因不同的原因和频率而改变。另一方面,正如 Vaughn Vernon 在他的《实现领域驱动设计 (DDD)》一书中所解释的,六边形架构与 DDD 密切相关。但是,您不需要遵循一组 DDD 实践和模式来构建基于 Hexagonal 架构的复杂企业应用程序,尽管我强烈建议您这样做。但毕竟,这些决定完全取决于你。
测试驱动开发是构建健壮企业应用程序的强大实践。 TDD 最好在自下而上的软件开发过程之后应用。但是,由于开发人员习惯于按照自上而下的方法推理此类应用程序,因此在实践中应用它具有挑战性。本文公开了一种简单的方法,以帮助开发人员在企业应用程序的开发中有效地应用 TDD。