时间: 2020-09-03 00:08:26 人气: 2275 评论: 0
在某些时候,工程师必须绘制一些方框和箭头来描述软件系统的顶层设计。但是,这些方框和箭头叫什么?我们经常使用诸如微服务,实体,REST或事件驱动之类的术语,这些又是什么?
作为研究论文的一部分,我一直在阅读有关软件体系结构概念和定义的知识,并且在整篇文章中,我将解释其中的一些概念,这些概念适用于我在研究生期间也一直在从事的项目:JSON- RPC Playground控制台。
我将使用Roy Fielding(HTTP规范的主要作者之一和REST风格的创建者)在其博士学位论文中给出的定义(如果您对Software Architecture感兴趣,我推荐您看看这篇论文)。
软件体系结构是软件系统在其操作的某个阶段的运行时元素的抽象。一个系统可能由许多抽象级别和许多操作组成,每个阶段都有自己的软件体系结构。
在描述体系结构时,可以对实现细节进行抽象,以简化设计。如用Elixir或Java编写身份验证服务有关系吗,显然这不是在架构层面要考虑的事情?还是它在系统中扮演的角色-验证用户-我们应该关注什么?
源代码结构不是系统的体系结构。当系统处于活动状态时,不同的应用程序可以共享公共库或模块,但彼此之间完全断开连接,这个是低耦合的设计。我们专注于处理数据以及如何移动数据。软件架构一般用高内聚、低耦合方式构建。
并非每个组件都会始终发挥作用或成为各个流程的一部分。系统关闭时涉及的组件及其配置可能与系统正常操作模式中涉及的组件及其配置完全不同。
在抽象元素实现时,我们将忽略与当前架构无关的细节。如果我们将重点转移到更深层次,则将出现具有其自身的元素和配置集的新体系结构。我们将找到许多嵌套的体系结构,直到元素足够简单以至于无法分解为止,实现原子性构建。
一系列决策的结果是创建了一个软件体系结构,每个决策都带来了一组属性和约束。无论它们在图表中是明确指出的,还是仅存在于架构师和开发人员的思想中,对于这些决定和约束应该都有一种理解或意图。随着系统的发展,这些最初的决定很可能与架构的实际情况不符。如果这些差异是不希望的或偶然的,那么我们说该体系结构已被侵蚀,造成了体系结构的“技术负债”。
我们的客户平台之一是由数十个JVM微服务组成,它们使用JSON-RPC 2.0协议相互通信。每个服务使用一组Java接口声明其RPC API,这些Java接口作为“ Service-API”库(JAR)发布在公共存储库中。想要与服务进行交互的客户端只需将其API JAR声明为项目依赖项即可。平台库将生成实现此类接口的对象,并通过依赖注入提供实现代码类。从代码角度来看,您只是在调用常规方法,但是在后台,平台库正在执行RPC调用并为您处理所有涉及的管道。这在编写代码时极大地提高了工作效率!
但是,要手动测试所有这些RPC方法(例如,使用诸如Postman或curl的工具),就必须找到正确的代码库,手动检查服务接口,其方法和参数(可能具有许多嵌套对象级别) ),然后手动构建所需的JSON payout以执行API调用。一般而言,API文档有帮助,但是很难保持最新,这是一个问题。
这里,我创建一个GUI应用程序,该应用程序会自动生成可轻松填充的表单,以调用服务公开的任何RPC方法。这些表单是通过与JSON-RPC 2.0兼容的服务描述文件生成的,该文件是通过分析Service-API JAR库创建的。通过使用与生产中运行的实际代码相同的源,可以确保它们保持最新。
构架系统意味着要做出一系列决定,这些决定可以塑造构成系统的不同元素(组件,连接器和数据)的配置。
组件是软件指令和内部状态的抽象单元,它通过接口提供转换或执行数据计算。组件是由它们向其他组件提供的服务定义的,而不是由它们的界面后面的实现定义的。如果其他组件无法识别某些行为,则该行为不是体系结构的一部分。
请注意,就此体系结构的观点而言,在定义RPC Server组件时,我们对RPC Server提供的特定功能不感兴趣,因为它与其余组件无关。我们甚至将这个组件的许多不同实例均等地分组,即使实际上它们在功能上会有很大不同。如,一个可能是Users服务,而另一个可能是Books服务。
连接器可实现不同组件之间的通信和数据传输。他们不转换数据,而是通过界面在不同组件之间对数据进行移动。但是在内部,当查看一个特定连接器的体系结构时,我们可能会发现它实际上是由一个子系统组成的,这些子系统接收数据,将其转换为更好的格式以进行传输,将其发送到另一端,然后反转转换,然后再传递给系统的其余部分。由于这些转换对系统的其余部分不可见,因此我们可以将它们抽象化为更高的层次。
在示例中:
对于AWS Library和Gradle Library而言,我们不直接负责这些数据传输的方式。然后,我们可以使用连接器的视图,而忽略其实现的细节。
许多软件体系结构定义没有将数据作为核心概念提及,我认为这并不完整。数据是系统存在的原因,有时甚至是驱动系统配置的主要因素。数据定义为通过连接器从一个组件传输到另一组件的信息。
在示例中:
架构风格是架构设计决策的命名集合,当在特定上下文中应用时对应不同的系统元素,它们的配置以及它们之间的关联方式施加约束,进而生成具有众所周知架构解决方案。
样式是一种用于对体系结构进行分类并定义其共同特征的机制。每种样式都为组件的交互提供了抽象,通过忽略架构其余部分的细节来捕获交互模式的本质。样式可以仅关注体系结构的某些方面,甚至可以将它们组合以生成更复杂的样式或混合样式。
客户端-服务器,微服务,Monolithic甚至是REST都是不同的体系结构样式,您很可能已将其应用于数十种异构系统。
如果您熟悉诸如Swagger的REST API之类的工具,您可能会注意到我的JSON-RPC项目与之相似。虽然我的控制台使用了针对基于JSON-RPC的服务量身定制的服务描述作为输入,但是REST API具有OpenAPI标准。从服务的源代码生成规范格式是一种强大的模式,可用于创建许多不同的使用者工具:文档导航器,客户端代码生成器,模拟服务器等。
让我们尝试为该工具系列定义通用的体系结构样式,该样式可以应用于任何其他协议以获得相同的好处:我将其称为“服务描述”样式。
让我们开始定义架构的不同元素
数据元素:
组件:
连接器(connectors):
必须从源代码创建服务描述。客户端需要始终保持最新的服务说明才能正常运行,因为除非服务说明中包含客户信息,否则他们对目标服务的具体情况一无所知。主要来源是代码,如果流程不是自动化的,则很可能会出现服务描述过时且客户端损坏的风险。这并不意味着不能手动构建服务描述。这样做有很多有效的用例,例如,如果您想在实际实现之前拥有一个模拟服务器。但是,依赖于手动任务的系统将不被视为该体系结构样式的实现。
请注意,我们对生成器如何使用源代码没有任何限制。实际上,生成器甚至可以作为目标构建过程中的一个步骤来实现(例如,使用Maven插件)。服务描述应遵循协议标准。该体系结构的主要优点之一是客户端可针对使用同一协议的许多不同目标服务进行重用,因此,服务描述无法了解仅适用于一项特定服务的特定实现细节。客户端提供的功能不属于体系结构的一部分:客户端可以与目标服务(例如,用于Playground控制台)进行交互,或者根本不进行交互(对于静态文档而言)。客户背后的主要限制是,除了服务描述所包含的信息外,他们还应该对目标服务的实施细节一无所知。连接器的定义非常宽松,因为我们对信息的传输方式没有任何限制。
-深蓝源码网-www.69shenlan.com