莫希特·古普塔(Mohit Gupta)
8分钟阅读
自从我从事软件开发事业以来,我经历了各种设计原则和模式。但是,一个原则非常突出,以至于发明了许多新的应用程序开发方法,模式和结构来与之保持一致。这就是PoSR的“单一职责原则”。而且为什么不这样,它对代码的结构,可读性,可维护性,发布,团队负责以及端到端所有权产生了巨大影响。
我已经看到了各种形式和形状的实现。正如上面提到的文章中提到的那样,此原则是关于由一个代码单位承担一项责任。“代码单元”取决于用户是谁,因此,取决于“谁是客户”,应用程序,模块,程序包,类,功能都可以称为代码单元。无论单元是什么,方向都是使实现更具模块化,避免横切关注点,减小尺寸,并明确意图以易于阅读和可维护性,并实现独立开发(并可能在某个时间点部署)。
从代码结构,功能和部署的角度来看,我们开发了许多产品,从基本的整体应用程序到管理得很好的大型整体程序,再到高度分布式的应用程序。这是这些概念演变的故事,整理了过去二十年的经验。
最初,通常努力改进代码,因此在PoSR之后的系统结构周围定义了更小的功能和类,因此这些可以携带清晰的意图。这有助于使代码易于阅读和管理,并打开重用的可能性。我记得围绕类和方法大小讨论了很多讨论,正确的代码行数。最终的学习是它不是线条的数量,但更多关于意图,任何逻辑思想都可以在第一眼假设和理解的内容。
*学习-将大小保持“小”非常有用。但是,对于代码行,没有单一的最佳数量。它应该基于代码单位的意图。当我们看到另一个明确的意图来自代码时,我们应该将其分开。请注意,不要仅仅为了减小代码长度而破坏代码。例如,如果我们仅盲目地遵循size参数,那么通过创建更多的类或接口,这实际上也会破坏结构。最好寻找单独的明确定义的意图。
另一个结构上的改进是通过采用正确的程序包结构和命名约定来支持的,它们可以清楚地传达意图,层次或单个实施领域。这似乎是一个显而易见的选择,但是许多整体应用程序经过多年发展成为高度交叉引用的结构,其代码库的形状没有比意大利面条结构更好。我记得各种大型项目,我们曾挑战过以正确的程序包结构重构遗留代码。这听起来听起来很乏味。由于这些项目经过多年的发展,因此许多代码被交叉引用,而没有模块或层的明确界限。更改一个代码将需要多年的系统经验或一定的魔杖。
*学习-正确的结构有助于轻松地在正确的位置生成更清晰的代码,并遵守模块的边界,甚至鼓励您尽早使用正确的接口和交互协议。因此,自开始以来定义和关注结构非常重要。重构是开发人员的生活方式,但是正确识别正确的结构可能会对代码质量和体系结构产生重大影响。
制作代码可读性非常重要,并包含具有清晰边界的所有相关代码,因此开发人员知道在哪里进行任何更改,而不是将代码放在哪里。它有助于提高开发人员效率很多,然后再为部署等等,我们将在下一段中讨论。
继PoSR之后并使系统结构模块化和更好的另一个工具。
第二个是实用程序和组件,旨在提高代码的可重用性,这有助于将可重用的代码保留在一个位置,以方便重用并减少返工(DRY)。
实用程序类:提供通用实现的一组通用可重用函数。可重用,无状态-仅行为无数据。可重用性的最简单形式。
组件:可重用实现的下一个分组级别,但是可以维护状态以及行为。因此,任何人都可以仅通过使用主要组件类来使用功能,这隐藏了所有复杂性。
以上两个对以更好的可读性,可维护性形式来结构化代码有很大帮助,并且减少了返工。通过更好的界限,它还通过将一组实用程序或组件分配给一个开发人员或团队而为端到端所有权指明了方向。
但是,仍然存在一个挑战,那就是根据业务功能/关注点分离代码(我们称其为模块)。实用程序/组件/框架有助于将可重复使用的样板代码分离开来,但是,业务功能的实现仍然留给软件包用于结构化。
我记得当我们的一个产品代码库膨胀了很多,并且变得难以管理时。将整个代码库打包到一个Jar / War中会由于诸如构建时间,二进制文件大小等明显挑战而引起问题。我们通过实现自定义构建来打包基于包结构的模块的相关代码,从而解决了这一问题。它奏效了,但工作繁琐而复杂。结构或软件包的任何更改都可能破坏整个构建。
逐渐地,我们开始将所有实用程序,组件和模块的代码库划分到单独的项目中。代码仍然耦合。但是,它有助于建立更好的发展结构。
但是,一个挑战仍然完好无损,即代码紧密结合在一起。我们仍然需要将整个系统构建和部署为一个单元。这意味着,整个系统需要与数据库更改和依赖性一起进行测试,验证,部署。
关键的交叉依赖关系是数据和数据库。下一个实验是从前端,控制器,业务逻辑到单独项目中的数据存储(仍在分层体系结构中)开始,端到端地破坏模块(域功能)。通过为游戏论坛每个模块定义单独的文凭模式,我们将单独的项目进一步进行了一步。通过接口定义了模块之间针对任何数据或操作的交互(EJB时代,但是可以是像Spring这样的任何技术)。我们还定义了模块的层次结构以避免依赖关系循环,从而使某些模块可以用于所有其他模块使用的通用数据。
通过以上分离,可以分别构建和部署单独的模块。它有助于实现独立的开发,部署和发布能力。
独立有代价。这就是我们很快击中的目标。当我们使用不同的模块时,通过远程接口实现的跨模块交互会导致性能问题。跨模块事务管理是另一个挑战,可以通过分布式事务解决或最终的一致性模式来解决。两者都不是可行或简单的选择。通过使用本地接口在EJB域中完成了一个简单的解决方法,这意味着打破了服务的界限,并将交叉模块称为本地引用而不是服务。
这不是一种完美的微服务解决方案。但是,通过混合使用可重用的组件和各种面向服务的结构(SOA),它帮助我们在模块化,性能和独立开发与部署之间取得了平衡,并在更大程度上满足了十年后的预期用例。
但是,它并没有解决独立的可扩展性和技术独立性的使用情况。可扩展性仍与整个部署程序包相关,整个部署程序包使用本地引用相互依赖。技术独立性也是不可能的,因为整个系统都紧密地基于一个技术生态系统,但是,由于计划将整个产品都放在同一技术堆栈上,因此这并不是一个立即的挑战。
我们很少使用Web服务堆栈(Axis,UDDI,WSDL)来实现新的实现,而是将更多的精力转向面向服务的实现方向。但是,很快我们就受到分布式数据一致性和性能问题的打击。因此,我们将实现方式限制为独立的模块,或者不需要跨模块数据依赖性的产品,例如工作流引擎,其中使用Web服务在系统中添加任何新功能而无需跨架构事务或共享状态。Web服务与技术无关,只要我们遵守SOAP / WSDL协议,无论我们采用何种技术,Web服务都使我们能够支持工作流中的任何新实现。
总结系统结构和设计历程的演变,以支持独立的开发/部署以及更好的可读性/可维护性,同时为团队中跨职能的端到端所有权和责任制注入种子,以下是旅程的重点:
较小的函数,具有良好命名约定的类更好的包装清楚地定义了层实用程序,组件,框架,锅炉板代码使用正确的包装结构和命名来分离模块单独的模块项目,以帮助开发人员在重点领域开展工作基于单独模块的项目,包括跨层的整个代码库,包括单独的数据存储,促进独立的开发和部署(在某种程度上,仍然相互依赖以进行交叉引用的数据更新)Web服务(符合SOA)用于完全独立的服务或面向客户端的界面
经过所有这些演变,我们正处于类似于微服务模式的阶段,减去可扩展性的完全独立性以及与技术无关的结构。
基础技术并不重要。我们使用EJB,Spring等多种技术对上述旅程和原理进行了体验和试验。而且,对于大多数人来说,此旅程不是连续的,而是可以从多个点并行进行的任何点开始进行演化。
这使我们进入了“独立原则”
独立的Codebase,拥有它与独立数据库的行为,拥有其状态独立的技术堆栈,可以独立发展技术独立打包/发布,可以独立构建和部署独立所有权,推动端到端问责制和自主团队结构
顺便说一句,没有这样的原则:)。它只是许多其他软件体系结构原理的结合,整理了整个软件开发人员社区都在争取的所有用例。
关键要解决的是,随着时间的推移,有多少产品和团队不断发展以支持更好的代码,产品和团队结构。这些原则和需求中的许多都是古老的,渴望获得更好的可维护代码,减少返工并促进重用,拥有更好的责任制和高效的团队结构以及促进独立性以实现高效执行。
这些在每个时代中使用各种可用协议和构造进行管理。单一责任的原则是在设计许多这些结构的同时是技术和人员的核心。
随着技术的进步,新模式和工具套件的发明,满足这些要求的效率越来越高。微服务模式就是这样一个很好的补充,它不是每一个挑战的灵丹妙药,但是提供了一个很好定义的结构和技术堆栈来支持以上所有功能。
要阅读系统设计演进过程中的下一个故事,请在此处阅读下一篇文章,重点介绍Web服务和微服务。