第8章 基于组件的思想

在第3章中,我们将模块作为相关代码的集合进行了讨论。但是,架构师通常从组件的角度考虑模块的物理表现形式。

开发人员实际上以不同的方式打包模块,有时取决于他们的开发平台。我们称模块组件为物理包装。大多数语言也支持物理打包:JAVA中的jar文件 ,.NET中的dll文件, Ruby中的gem文件等。在本章中,我们讨论围绕组件的架构注意事项,ranging from scope to discovery.

组件范围

开发人员发现基于大量因素细分组件的概念很有用,其中一些因素出现在图8-1中。

组件提供了一种特定于语言的机制来将工件分组在一起,通常将它们嵌套产生分层。如图8-1所示,最简单的组件分类(或函数,非面向对象的语言)更高的模块化级别包装代码。这个简单的包装器通常称为,它倾向于在与调用代码相同的内存地址中运行,并通过语言函数调用机制进行通信。库通常是编译时依赖项(有很多例外,例如动态链接库[DLL]多年来一直是Windows用户的烦恼)。


8-1不同种类的组件

组件还作为架构中的子系统或层出现,成为许多事件处理器的可部署工作单元。另一类组件,即服务,往往在其自己的地址空间中运行,并通过低级网络协议(如TCP / IP)或更高级别的格式(如REST或消息队列)进行通信,从而在微服务等架构中形成独立的可部署单元。

不需要架构师就可以使用组件—碰巧这种情况是,拥有比该语言提供的最低级别更高的模块化级别通常很有用。例如,在微服务架构中,简单性是架构原则之一。因此,服务可能包含足够的代码以保证组件,或者可能足够简单以仅包含少量代码,如图8-2所示。

组件构成了架构中的基本模块化构建块,使它们成为建筑师的重要考虑因素。实际上,架构师必须做出的主要决定之一涉及架构中组件的顶级分区。


8-2微服务可能只有很少的代码,因此不需要组件

架构师角色

通常,架构师定义,完善和管理架构中的组件。软件架构师与业务分析师,业务专家,开发人员,质量保证工程师,运营和企业架构师合作,创建了软件的初始设计,并结合了第4章中讨论的架构特性和软件系统的要求。

实际上,我们在本书中涵盖的所有细节都独立于团队使用的软件开发过程而存在:架构与开发过程无关。该规则的主要例外是在各种敏捷软件开发风格中率先采用的工程实践,尤其是在部署和自动化治理领域。但是,通常软件架构与流程是分开存在的。因此,架构师最终不在乎需求源自何处:正式的联合应用程序设计(JAD)流程,冗长的瀑布式分析和设计,敏捷故事卡……或这些的任何混合变体。

通常该组件是架构师直接与之交互的软件系统的最低级别,第6章中讨论的许多代码质量指标会全面影响代码库,但该标准除外。组件由类或函数(取决于实现平台)组成,其类设计由技术负责人或开发人员负责。并不是说架构师不应该让自己参与类设计(尤其是在发现或应用设计模式时),而是应该避免对系统中从上到下的每个决策进行微观管理。如果架构师从不让其他角色来做出决定,那么组织将在赋予下一代架构师权力方面陷入困境。

架构师必须将组件标识为新项目中的首要任务之一。但是在架构师可以识别组件之前,他们必须知道如何对架构进行分区。

架构分区

《软件架构第一定律》指出,软件中的所有事物都是一个权衡,包括架构师如何在架构中创建组件。由于组件代表一种通用的集装箱运输机制,因此架构师可以构建所需的任何类型的分区。存在几种常见的样式,具有不同的取舍方式。在第二部分中,我们将深入讨论建筑风格。在这里,我们讨论样式的重要方面,即架构中的顶级分区

考虑图8-3中所示的两种类型的架构样式。


8-3两种顶级架构分区:分层模块化

在图8-3中,一种类型许多人都熟悉的架构是分层的整体结构(将在第10章中详细讨论)。另一种是西蒙·布朗(Simon Brown)流行的架构样式,称为模块化整体,与数据库关联并围绕域而不是技术功能进行分区的单个部署单元。这两种样式表示对架构进行顶层分区的不同方法。请注意,在每个变体中,每个顶级组件(层或组件)都可能嵌入了其他组件。顶层分区特别受架构师的关注,因为它定义了基本的架构样式和分区代码的方式。

基于技术功能(例如分层架构)组织架构代表了技术顶级分区。图8-4显示了它的通用版本。


8-4架构中的两种顶级分区

在图8-4中,架构师将系统的功能划分为技术能力:表示,业务规则,服务,持久性等。这种组织代码库的方式当然很有意义。所有持久性代码都驻留在架构的一个层中,从而使开发人员可以轻松找到与持久性相关的代码。尽管分层架构的基本概念要早于数十年,但Model-View-Controller设计模式与该架构模式相匹配,从而使开发人员易于理解。因此,它通常是许多组织中的默认架构。

分层架构优势的一个有趣的副作用与公司如何担当不同项目角色有关。使用分层架构时,将所有后端开发人员放在一起放在一个部门中,将DBA放在另一个部门中,将演示团队放在另一个部门中,这是有意义的。由于康威定律,这在那些组织中是有道理的。

康威定律

早在20世纪60年代末,梅尔文·康威的一项观察被称为康威定律

设计系统……的组织必须被约束来生成设计,这些设计是这些组织的通信结构的副本。

释义,该法则建议,当一群人设计一些技术工件时,人与人之间的通信结构最终会在设计中复制。各个组织的人们都看到该定律在起作用,有时他们会据此制定决策。例如,组织通常根据技术能力对员工进行划分,这从纯粹的组织意义上来说是有意义的,但由于人为地将共同关注的问题分离,因此阻碍了彼此协作。

ThoughtWorks的Jonny Leroy提出的一个相关观察结果是“ 反向康威操纵”,它建议不断发展的团队和组织结构共同促进所需的架构。

图8-4中的另一个架构变体表示领域分区,其灵感来自于Eric Evan的《领域驱动设计》一书,这是一种用于分解复杂软件系统的建模技术。在DDD中,架构师确定相互独立且相互分离的领域或工作流。微服务架构风格(在第17章中讨论)基于这种哲学。在模块化的整体中,架构师将架构划分为领域或工作流,而不是技术能力。由于组件之间经常相互嵌套,因此域分区中的图8-4中的每个组件(例如,CatalogCheckout)都可以使用持久性库并具有单独的业务规则层,但是顶级分区围绕领域进行。

不同架构模式之间的根本区别之一是每种支持哪种类型的顶级分区,我们将针对每种单独的模式进行介绍。它还对架构师如何决定如何初始识别组件产生巨大影响-架构师是否要按技术或按领域划分事物?

使用技术分区的架构师通过技术能力来组织系统的组件:演示,业务规则,持久性等。因此,该架构的组织原则之一是技术关注点的分离。反过来,这会创建有用的解耦级别:如果服务层仅连接到下面的持久层和上面的业务规则层,那么持久性的更改将仅潜在地影响这些层。这种分区方式提供了一种解耦技术,从而减少了相关组件上的涟漪效应。在第10章的分层架构模式中,我们将详细介绍这种架构样式。使用技术分区组织系统当然是合乎逻辑的,但是,就像软件架构中的所有事物一样,这只是为了一些权衡。

通过技术分区实施的分离使开发人员能够快速找到代码库的某些类别,因为它是按功能组织的。但是,大多数现实的软件系统都需要跨技术能力的工作流。考虑CatalogCheckout的常见业务工作流程。该代码来处理CatalogCheckout在技术分层的架构出现在所有层中,如图8-5所示。

8-5工作流出现在技术和领域划分的架构中的位置

在图8-5中,在经过技术分区的架构中,CatalogCheckout出现在所有层中。该域遍布整个技术层。将其与域分区相比,域分区使用顶级分区,该顶级分区按域而不是技术功能来组织组件。在图8-5中,设计域划分架构的架构师围绕工作流和领域构建顶级组件。领域分区中的每个组件都可能具有子组件,包括层,但是顶级分区集中于域,这更好地反映了项目中最常发生的更改类型。

这两种风格都不比另一种更正确-请参阅《软件架构第一定律》。也就是说,在过去的几年中,我们已经观察到行业朝着整体和分布式(例如微服务)架构进行领域划分的趋势。但是,这是架构师必须做出的第一个决策。

案例研究:硅三明治:分区

考虑我们的示例kata之一的案例,“案例研究:硅三明治”。推导组件的时候,架构师面临的基本决策之一是顶层分区。考虑硅三明治的两种不同可能性中的第一种,即领域划分,如图8-6所示。


8-6硅三明治的域划分设计

在图8-6中,架构师设计围绕域(工作流),用于创建离散组件Purchase,Promotion,MakeOrder,ManageInventory,Recipes,Delivery和Location。在许多这些组件中都包含一个子组件,用于处理所需的各种类型的定制,涵盖了常见和本地的变化。

一种替代设计将common部分和local部分隔离到各自的分区中,如图8-7所示。Common和Local代表顶层成分,具有Purchase与Delivery剩余的处理的工作流。

哪个更好?这取决于!每个分区都有不同的优点和缺点。


8-7硅三明治的技术分区设计

领域分区

领域划分的架构按工作流和领域将顶级组件分开。

优点

.针对业务功能而不是实现细节进行更紧密的建模

.更容易利用逆航道策略围绕领域建立跨职能团队

.与模块化单片和微服务架构样式更加紧密地结合

.消息流匹配问题域

.易于将数据和组件迁移到分布式架构

缺点

.自定义代码出现在多个位置

技术分区

技术上划分的架构是根据技术功能而不是离散的工作流来分离顶级组件的。这可能表现为受Model-View-Controller分离或某些其他临时技术分区启发的图层。图8-7根据定制将组件分离。

优点

.明确分隔定制代码。

.与分层架构模式更紧密地结合。

缺点

 .更高程度的全局耦合。Common或Local组件的更改可能会影响所有其他组件。

.开发人员可能必须在层common和local层中重复领域概念。

.通常在数据级别更高的耦合。在这样的系统中,应用程序和数据架构师可能会协作以创建单个数据库,包括定制领域。如果架构师后来想将这种架构迁移到分布式系统,那么反过来,将难以解决数据关系。

许多其他因素也有助于架构师决定采用哪种架构风格。在第二部分中介绍了他们的设计基础。

开发人员角色

开发人员通常会采用与架构师角色共同设计组件,然后将它们进一步细分为类,功能或子组件。 通常,类和功能设计是架构师,技术负责人和开发人员的共同责任,其中绝大部分是开发人员角色。

开发人员永远不要把架构师设计的组件作为硬道理。所有软件设计都受益于迭代。相反,应将初始设计视为初稿,其中的实现将揭示更多细节和改进。

组件识别流程

组件识别最适合作为迭代过程,通过反馈产生候选者和提炼,如图8-8所示。


8-8组件识别周期

该周期描述了通用架构的展示周期。某些专业领域可能会在此过程中插入其他步骤或完全更改它。例如,在某些域中,某些代码在此过程中必须经过安全性或审核步骤。以下各节中将显示图8-8中每个步骤的说明。

识别初始组件

在为软件项目提供任何代码之前,架构师必须以某种方式基于他们选择的顶级分区类型来确定要使用哪些顶级组件。除此之外,架构师还可以自由地构建所需的任何组件,然后将域功能映射到它们以查看行为应驻留的位置。尽管这听起来有些武断,但如果架构师从头开始设计系统,那么很难从更具体的内容开始。从最初的一组组件中获得良好设计的可能性简直令人难以置信,这就是为什么架构师必须迭代组件设计以对其进行改进的原因。

将需求分配给组件

架构师确定了初始组件后,下一步将需求(或用户案例)与这些组件相结合,以了解它们的适合程度。这可能需要创建新组件,合并现有组件或将组件分开,因为它们的职责过多。这种映射不一定是精确的-架构师正在尝试寻找一种良好的粗粒度基材,以允许架构师,技术负责人和开发人员进行进一步的设计和改进。

分析角色和职责

在为组件分配故事时,架构师还将查看在需求期间阐明的角色和职责,以确保粒度匹配。 考虑应用程序必须支持的角色和行为,可以使架构师调整组件和域的粒度。架构师面临的最大挑战之一是发现组件的正确粒度,这鼓励了此处介绍的迭代方法。

分析架构特性

在向组件分配需求时,架构师还应该研究架构较早发现的特性,以考虑它们如何影响组件的划分和粒度。例如,虽然系统的两个部分可能处理用户输入,但是处理数百个并发用户的部分将需要与仅需要支持少数几个部分的另一部分具有不同的架构特征。因此,尽管纯粹的组件设计功能视图可能会产生一个单独的组件来处理用户交互,但是分析架构特征将导致细分。

重组组件

反馈对于软件设计至关重要。 因此,架构师必须与开发人员不断地迭代其组件设计。设计软件会带来各种意想不到的困难-没有人能预见到在软件项目期间通常会发生的所有未知问题。因此,组件设计的迭代方法至关重要。首先,几乎不可能考虑所有会鼓励重新设计的发现和边缘案例。其次,随着架构和开发人员更深入地研究应用程序的构建,他们对行为和角色的位置有了更细致的了解。

组件粒度

为组件找到合适的粒度是架构师最困难的任务之一。 组件设计的粒度太细会导致组件之间的交流过多,从而无法获得结果。太粗的组件会导致内部耦合度很高,这会导致部署性和可测试性以及与模块性相关的负面副作用方面的困难。

组件设计

设计组件没有公认的“正确”方法。而是,存在各种各样的技术,所有这些技术都具有各种折衷。在所有过程中,架构师都要满足要求,并尝试确定哪些粗粒度的构建基块将构成应用程序。存在许多不同的技术,所有这些技术都有不同的权衡因素,并且与团队和组织使用的软件开发过程相关。在这里,我们讨论了一些发现组件和陷阱的常规方法。

发现组件

架构师通常与其他角色(例如开发人员,业务分析师和主题专家)合作,根据系统的一般知识以及他们基于技术或领域划分的方式选择分解系统来创建初始组件设计。团队目标是一个初始设计,该设计将问题空间划分为粗略的块,并考虑了不同的架构特性。

实体陷阱

尽管没有一种确定组件的真正方法,但常见的反模式潜伏着:实体陷阱。假设有一个架构师正在为我们的kata Going,Going,Gone设计组件,最后得到类似于图8-9的设计。


8-9将架构构建为对象关系映射

在图8-9中,架构师基本上采用了需求中标识的每个实体,并Manager基于该实体制作了一个组件。这不是架构;这是框架到数据库的组件关系映射。换句话说,如果系统仅需要简单的数据库CRUD操作(创建,读取,更新,删除),那么架构师可以下载框架以直接从数据库创建用户界面。

裸对象和类似框架

十多年前,出现了一系列框架,这些框架使构建简单的CRUD应用程序变得不那么容易了, 以Naked Objects为例(此后分为两个项目,一个仍称为NakedObjects的.NET版本,以及一个以Isis名称移至Apache开源基金会的Java版本)。这些框架的前提是在数据库实体上构建用户界面前端。 例如,在Naked Objects中,开发人员将框架指向数据库表,然后框架根据表及其定义的关系构建用户界面。

存在其他几种流行的框架,这些框架基本上提供了基于数据库表结构的默认用户界面:Ruby on Rails框架的脚手架功能提供了从网站到数据库的相同类型的默认映射(具有许多选项,可以扩展最终应用程序并将其添加到此应用程序中)。

如果架构师的需求仅需要从数据库到用户界面的简单映射,那么就不需要完整的架构。这些框架之一就足够了。

当架构师错误地将数据库关系标识为应用程序中的工作流时,就会出现实体陷阱反模式,这种对应关系在现实世界中很少出现。相反,此反模式通常表明对应用程序的实际工作流程缺乏思考。使用实体陷阱创建的组件也往往过于粗粒度,就源代码的打包和整体结构而言,没有为开发团队提供任何指导。

行为者/动作方法

演员/行动的做法是,架构师用它来映射要求组件的流行方式。在最初由Rational

Unified Process定义的这种方法中,架构师确定了与应用程序一起执行活动的参与者以及这些参与者可能执行的动作。它提供了一种用于发现系统的典型用户以及他们可能会对系统执行哪些操作的技术。

行为者/动作方法与特定的软件开发过程结合在一起变得很流行,特别是那些更倾向于前期设计的更正式的过程。当需求具有不同的角色和可以执行的各种操作时,它仍然很流行并且效果很好。这种类型的组件分解风格适用于所有类型的整体式或分布式系统。

事件风暴

事件风暴作为一种组件发现技术,来自领域驱动设计(DDD),并且在微服务中也很受欢迎,而微服务也受到DDD的严重影响。在突发事件中,架构师假设项目将使用消息和事件在各个组件之间进行通信。为此,团队尝试根据需求和确定的角色确定系统中发生了哪些事件,并围绕这些事件和消息处理程序构建组件。这在使用事件和消息的微服务之类的分布式架构中效果很好,因为它可以帮助架构师定义最终系统中使用的消息。

工作流程方法

事件风暴的替代方法为不使用DDD或消息传递的架构师提供了一种更通用的方法。该工作流方法模型围绕工作流程组件,很像事件风暴,但没有构建基于信息系统的明确限制。工作流方法识别关键角色,确定这些角色从事的工作流类型,并围绕所识别的活动构建组件。

这些技术没有一个优于其他技术。所有这些都提供了不同的权衡。如果团队使用瀑布式方法或其他较旧的软件开发流程,则他们可能更喜欢行为者/动作方法,因为它很通用。当使用DDD和相应的架构(如微服务)时,事件风暴与软件开发过程完全匹配。

案例研究:Going, Going, Gone:发现组件

如果团队没有特殊约束,并且正在寻找良好的通用组件分解方法,则Actor / Actions方法可以很好地用作通用解决方案。这是我们在案例研究中使用的Going,Going,Gone。

在第7章中,我们介绍了Going,Going,Gone(GGG)的架构kata,并发现了该系统的架构特性。该系统具有三个明显的角色:投标人拍卖人以及该建模技术的频繁参与者内部系统。角色与应用程序交互(由系统在此处表示),该角色标识应用程序何时启动事件,而不是角色之一。例如,在GGG中,拍卖完成后,系统会触发付款系统处理付款。

我们还可以为这些角色中的每个角色确定一组初始操作:

Bidder

查看实时视频流,查看实时出价流,出价

Auctioneer

将实时出价输入系统,接收在线出价,将商品标记为已售

System

开始拍卖,付款,跟踪竞标者活动

有了这些动作,我们可以为GGG迭代地构建一组启动器组件。一种这样的解决方案出现在图8-10中。


8-10用于GoingGoingGone的初始组件集

在图8-10中,每个角色和动作都映射到一个组件,而组件又可能需要在信息上进行协作。这些是我们为此解决方案确定的组件:

VideoStreamer

向用户流式传输实时拍卖。

BidStreamer

流式传输出价给用户。双方VideoStreamer并BidStreamer提供只读视图拍卖的投标人。

BidCapture

该组件捕获来自拍卖商和投标人的投标。

BidTracker

跟踪出价并充当记录系统。

AuctionSession

开始和停止拍卖。当投标人结束拍卖时,执行付款和解决步骤,包括通知投标人结束。

Payment

第三方付款处理器,用于信用卡付款。

参考图8-8中的组件标识流程图,在对组件进行初始标识之后,架构师接下来将分析架构特征,以确定这是否会改变设计。对于该系统,架构师可以肯定地标识出不同的架构特征集。例如,当前的设计具有一个BidCapture组件来捕获来自投标人和拍卖人的投标,这在功能上是有意义的:从任何人那里获取投标都可以被相同地处理。但是,围绕出价捕获的架构特征又如何呢?拍卖师不需要像潜在的成千上万的竞标者那样的可伸缩性或弹性级别。 同样,架构师必须确保拍卖人的可靠性(连接不会掉线)和可用性(系统正常)等架构特征可能高于系统的其他部分。 例如,如果竞标者无法登录网站或连接中断而对业务不利,那么如果这两种情况发生在拍卖师身上,对拍卖来说都是灾难性的。

因为它们具有不同级别的架构特征,所以架构师决定将Bid Capture组件拆分为Bid Capture,Auctioneer Capture以便两个组件中的每个组件都可以支持不同的架构特性。更新后的设计如图8-11所示。

架构师创建了一个新组件,Auctioneer

Capture并更新了两者的信息链接Bid

Streamer(以便在线竞标者看到实时竞标)和Bid

Tracker,后者管理着投标流。请注意,Bid

Tracker现在该组件将统一两个截然不同的信息流:来自拍卖师的单个信息流和来自投标人的多个信息流。


8-11将架构特征整合到GGG组件设计中

图8-11中所示的设计不太可能是最终设计。必须发现更多的要求(人们如何注册,有关付款的管理职能等等)。但是,此示例提供了一个很好的起点,可以开始对设计进行进一步的迭代。

这是解决GGG问题的一组可能的组件,但不一定正确,也不是唯一的。很少有软件系统只有开发人员可以实现它们的一种方法。每个设计都有不同的权衡。作为一名建筑师,不要着迷于找到一个真正的设计,因为许多设计就足够了(而且不太可能过度设计)。相反,尝试客观地评估不同设计决策之间的权衡,并选择权衡最差的决策。

Quantum Redux架构:单体架构与分布式架构之间的选择

回顾“架构量子和粒度”中定义架构量子的讨论,架构量子定义架构特性的范围。反过来,这又导致架构师在完成初始组件设计时做出了重要决定:架构是整体的还是分布式的?

单体架构通常具有单个可部署单元,包括可在流程中运行的系统的所有功能,通常连接到单个数据库。。单体架构的类型包括分层的和模块化的整体,将在第10章中进行全面讨论。一个分布式架构是相反的应用程序包含在自己的生态系统运行多种服务,通过网络协议进行通信。分布式架构可能具有更细粒度的部署模型,其中每个服务根据开发团队及其优先级可能具有自己的发布节奏和工程实践。

每种架构样式都提供了各种权衡,这在第二部分中进行了介绍。但是,基本的决定取决于架构在设计过程中发现多少个量子。如果系统可以使用单个量子进行管理(换句话说,就是一组架构特征),那么单体架构将提供许多优势。另一方面,如GGG组件分析所示,组件的不同架构特征要求分布式架构来适应不同的架构特性。例如,VideoStreamer和BidStreamer两者都向竞标者提供拍卖的只读视图。从设计的角度来看,架构师宁愿不处理混合了大规模更新的只读流。除了上述投标人和拍卖人之间的差异外,这些差异还导致建筑师选择分布式架构。

在设计过程的早期确定架构的基本设计特性(单体还是分布式)的能力凸显了使用架构量子作为分析架构特性范围和耦合的一种优势。

原文 :https://www.jianshu.com/p/a0452b322a90


全书翻译目录:https://www.jianshu.com/p/05711d172dfa

声明:本资料仅供学习交流严禁使用于任何商业用途!请购买正版图书:https://www.oreilly.com/library/view/fundamentals-of-software/9781492043447/cover.html

资料整理和翻译:杨传池Chris  IT老兵,人生三大爱好(爱好喝茶,喝酒和喜欢做梦)

10+年的软件研发和项目管理经验;

7+年大型房产信息化、数字化咨询经验;

50+人以上研发团队管理,擅长团队管理和人才梯队建设;

熟悉研发管理、工程构建、体系建设、DevOps和领域建模,掌握IT研发价值链和工具链。

你可能感兴趣的:(第8章 基于组件的思想)