构建完整的架构边界的成本是很高的。需要为系统设计双向多态的边界接口、输入和输出数据结构以及所有必要的依赖管理,以便将两个部分隔离为可独立编译和部署的组件。这涉及大量的工作和后期维护。
许多情况下,一个好的架构师可能会认为设置这样的边界的开支太高,但仍然希望为以后可能需要的边界留下位置。
这种预判性设计常常被敏捷社区中的许多人指责为违反 YAGNI(你不会需要它)原则。然而,架构师可能会看到问题并想到:“是啊,但是我可能会需要’在这种情况下,他们可能会实现一个不完全边界。
一、跳到最后一步
构造不完全边界的一种方法是将系统分割成一系列可以独立编译和部署的组件,之后再把它们组合成一个组件。换言之,将相应的接口和输入/输出数据结构都设置好,并且一切都已准备妥当,但我们仍选择将这些组件编译和部署为单个组件。
显然,这种不完全边界需要与完全边界同样数量的代码和设计工作。但是它不需要管理多个组件。没有版本号跟踪或发布管理负担。这一差异不容忽视。这是 FitNesse 的早期策略。FitNesse 的网络服务器组件被设计为可与 FitNesse的 wiki 和测试部分分离。这样,我们就可以使用该网络组件创建其他基于 Web 的应用程序。同时,我们不希望用户下载两个组件。请记住,我们的一个设计目标是“下载并运行”。我们的意图是用户下载一个jar 文件并执行它,而不必寻找其他 jar 文件,解决版本兼容性等问题。
FitNesse 的经历也揭示了这种方式的其中一个危害。随着时间的推移,人们逐渐认识到不再需要单独的 Web 组件,Web 组件和 wiki 组件之间的分离开始变得脆弱。依赖关系开始朝着错误的方向交叉。现在,重新分离它们可能会是一项烦琐的任务。
二、单向边界
完全成熟的架构边界使用双向边界接口来保持双向隔离。维护这种双向的隔离性,通常不会是一次性的工作,需要我们持续投入资源。
图 24.1 显示了一个更简单的结构,用于暂时保留以后扩展为完全边界的位置它展示了传统的策略模式。serviceBoundary(服务边界)接口由客户端使用并由 serviceImpl(服务具体实现)类实现。
上述设计显然为未来的架构边界打下了基础。为了将 Client(客户端)与ServiceImpl隔离开来,必须进行必要的依赖反转。同时,我们清楚看到分离可能会相当迅速地降级,如图示中的恶意虚线所示。由于没有双向接口,除了依赖开发人员和架构师的勤勉和自律,没有任何事物可以防止这种反向通道的出现。
三、外观
一个更简单的边界是外观模式,如图24.2所示。在这种情况下,不需要使用依赖反转。该边界仅由 Facade(外观)类定义,该类将所有服务列为方法,并将服务调用部署到客户端不应访问的类中。
请注意,客户端对所有这些服务类都具有传递性依赖。在静态语言中,对其中一个服务类源代码的更改将强制客户端重新编译。此外,你可以想象使用此结构创建反向通道是多么容易。
我们已经看到了部分实现不完全边界的三种简单方法。当然,还有很多其他方法。这三种策略仅作为示例提供。
这些方法中的每一种都有其自己的成本和收益。在特定的情况下,每种方法都是适宜的,作为一个最终完全边界的替代方案。如果该边界从未实现,则每种方法都可能会受到损害。