跳转到主要内容
Chinese, Simplified

这篇文章是软件架构编年史的一部分,一系列关于软件架构的文章。在这些文章中,我写了我在软件架构学到了什么,我如何看待它,以及我如何使用这些知识。如果您阅读了本系列以前的文章,那么本文的内容可能更有意义。

我们学习如何编码,我们构建一些很酷的应用程序,然后我们学习架构,以及如何让应用程序保持数年的可维护性……

然而,当我们需要向其他人(新开发人员、产品所有者、投资者……)解释应用程序如何工作时,我们需要更多的东西……我们需要文档。

但是,我们有哪些文档选项可以表达整个应用程序构建块及其工作方式呢?

在这篇文章中,我要写的是:

  • UML
  • 4+1架构视图模型
  • 体系结构决策记录
  • C4模型
  • 依赖关系图
  • 应用程序地图

UML

我们可以使用UML创建一些图表,我们可以将它们分成两类:

  • 行为的UML图
    • 活动图
    • 用例图
    • 交互概览图
    • 时间图
    • 状态机图
    • 通信图
    • 序列图
  • 结构UML图
    • 类图
    • 对象图
    • 组件图
    • 组合结构图
    • 部署图
    • 包图
    • 概要图

我不会详细介绍每一种类型的图,因为在这篇文章中涉及的内容太多了,而且有大量的资源记录了这些图类型。要了解更多关于这些类型的信息,您可以查看上面的链接,这些链接指向一些可视化范例指南,或者查看这篇博客文章。

总之,UML很酷,很有趣,我们可以用它来表达,我们可以很容易地用它来勾画一些想法,并与同事讨论。

然而,要使用UML记录整个应用程序体系结构,我们需要使用几种类型的图。此外,如果我们试图使用单个类图来表示整个应用程序,那就麻烦了。

一个很好的使用UML类图的例子是记录设计模式:

Source: https://java-design-patterns.com/patterns/strategy

 

这很好,这真的很棒!它可以表达类、接口、可用性和继承关系、数据和行为。它也很简洁、易读,而且因为它很小,创建起来也很快。

然而,下面的例子不是很有用……它非常大,所以它变得混乱和难以理解。此外,创建它将花费大量的时间,当我们完成时,它可能已经过时了,因为同时有人会对代码进行更改。

Source: https://knowhow.visual-paradigm.com

 

因此,我们可以并且应该使用UML,但是在某些情况下应该使用它:详细描述模式、应用程序的一小部分,或者用低细节(不使用类图)描述应用程序的高粒度视图。

但接下来的问题是,我们如何记录完整的应用程序?

4+1架构视图模型

4+1架构视图模型由Philippe Kruchten创建,并在1995年发表了他的论文《架构蓝图——软件架构的4+1视图模型》。

这种可视化软件应用程序架构的方法基于应用程序的5个视图/透视图,告诉我们哪些图可以用来记录这些视图。

逻辑/结构视图

关注系统所提供的功能,以及如何设计程式以提供这些功能;

实施/开发视图

描述程式码、组件、模组及套件的静态组织;

过程/行为视图

关注系统的运行时行为、系统进程如何通信、并发、同步、性能等;

部署/物理视图

举例说明应用程序的物理组织,它是关于“什么代码在什么硬件上运行”;

用例/场景视图

整个体系结构在几个用例的帮助下得到了解释,这些用例只是交互的序列。架构的一部分是从这样的用例发展而来的。

需要注意的是,4+1架构视图模型并不要求我们使用所有提到的图,甚至不是所有的视图。我们总是需要了解工具,并使用不超过或少于我们所需要的。

体系结构决策记录

架构决策记录(ADR)实际上并不是关于记录应用程序架构当前或未来的状态,而是关于导致这种状态的原因。它们特别重要,因为它们打算告诉别人,以及我们未来的自己,为什么建筑是这样的。

ADR是一个关于已经做出的架构决策的日志条目,这些决策导致了架构现在或将来的状态。它们包含了描述架构的图表背后的原因。

首先,有一些我们需要知道的人工制品:

  • 体系结构重要需求(ASR):对软件系统的体系结构具有可度量影响的需求;
  • 架构决策(AD):针对重要需求的软件设计选择;
  • 架构决策记录(ADR):捕获重要架构决策及其上下文和结果的文档;
  • 架构决策日志(ADL):为特定项目(或组织)创建和维护的所有adr的集合;
  • 架构知识管理(AKM):以前所有概念的更高层次。

我已经看到了一些用于创建adr的模板,并且我在其中的几个中看到了一些不错的东西,所以我创建了自己的模板。你可以,或许也应该创建一个对你和你的团队有意义的网站。

对我来说,模板最重要的一点是它很简单,它有一些文档来帮助填充它,甚至帮助做出实用的和公正的决定。

使用ADR的最佳方式不是简单地将其作为讨论和决定之后的书面文件。最好是将其作为讨论的起点,作为RFC(征求意见),这是我们提交给团队/部门的其他成员的想法/建议,要求他们提供输入/意见/批准。其目的实际上是用它来开始讨论,进行头脑风暴,做出可能的最佳决策,并使用提案文档本身作为决策日志条目(ADR)。ADR是预先编写的这一事实并不意味着它是不可变的,它必须随着讨论的展开而更新/改进。我觉得把所有正在考虑的方案都写下来,把它们的利弊都写下来,以便引起讨论,做出明确的决定,这一点特别重要。

这就是我想到的模板:

https://docs.google.com/document/d/1Xe5erulKsdaha3uwU6tNWsAWxjQK-Rcrr_i…

可以从谷歌文档中随意复制。

如果你想进一步了解这个主题,我建议你去Joel Parker Henderson github的adr库。

C4模型

C4模型是由Simon Brown引入的,这是迄今为止我遇到的关于软件架构文档的最好的想法。我将很快地用我自己的话解释主要思想,尽管使用他自己的示例图。

这个想法是使用4种不同的粒度(或缩放)级别来记录软件架构:

  • 级别1:系统上下文关系图
  • 级别2:容器图
  • 级别3:组件图
  • 级别4:代码图

级别1:系统上下文关系图

这是最高粒度的图表。它几乎没有细节,但是它的主要目标是描述应用程序所在的上下文。因此,它将由整个应用程序的单个框组成,它将被引用应用程序与之交互的外部系统和用户的其他框包围。

级别2:容器图

现在,我们放大我们的应用程序,上图中的蓝色正方形映射到下图中的虚线正方形。

在这个粒度级别,我们将看到应用程序的容器,其中容器是应用程序的任何独立技术部分,例如移动应用程序、API或数据库。它还记录了使用的主要技术以及容器如何通信。

级别3:组件图

组件图向我们展示了一个容器中的组件。在这种情况下,每个组件都是应用程序的一个模块,不局限于域明智的模块(即。但也包括纯粹的功能模块(即。电子邮件、短信、……)。这张图向我们展示了集装箱的主齿轮以及这些齿轮之间的关系。

四级:代码

最细粒度的图,旨在描述组件内部的代码结构。对于这个级别,我们使用带有类级工件的UML图。

要了解更多,你可以在这里和这里阅读西蒙·布朗自己对它的解释,甚至在这里观看他谈论它。

还缺什么?

我认为C4模型是一种记录应用程序架构的好方法,在一定程度上理解应用程序架构是很好的,但是我仍然觉得它不够,尽管我花了一些时间来了解所缺少的内容。

在这些图表中,我看到了三个限制:

  • 除了一些例外,比如Simon Brown的structurizr,它们需要手动生成,而不是自动生成,也不是直接从代码中提取,这意味着它们可能不会反映实际的代码,而是我们目前对它的理解;
  • 它们并不能很好地帮助我们了解我们的应用程序代码库中存在的问题,比如混乱的代码关系和糟糕的结构,这些问题影响了对任何工程产品都至关重要的模块化和封装;
  • 它们不能帮助我们从整体上理解我们的代码库,应用程序齿轮可以做什么,以及它们之间如何交互。

我发现了两类图表可以帮助我们。

依赖关系图

依赖关系图有助于告诉我们存在于代码库中不同类型代码中的依赖关系。

这里至关重要的一点是,这些图是直接从代码中自动生成的,否则图将只反映我们认为的代码的样子,如果这是正确的,我们就不需要这种类型的文档了。

此外,可能比图本身更重要的是,在我们的预定义依赖关系规则中断的情况下,使用这些依赖关系分析来停止构建的能力。工具用于生成这些图也应该是可用的测试工具,包括在我们的CI管道,就像单元测试,防止不必要的依赖性达到生产、维护和实施模块化,这反过来有助于达到高可变性率,因此发展高速度的功能。

在这类关系图中,我发现拥有3种不同类型的关系图很有用,可以断言不同的依赖项类型。

在下面的例子中,它们都是由deptrac为我的宠物项目(explicit-architecture-php)生成的,我使用它进行实验。您可以在存储库根中找到用于生成它们的配置。

不过,请注意,我自己添加的颜色,以使它更容易阅读这篇博客文章。颜色代表应用程序中的不同层,与我在以前的博客文章中所写的层一致:

层依赖图

此图的目的是可视化并确保每个层中的代码只能依赖于其内部或下面的层。

因此,在下面的图中我们可以看到,例如,作为最外层之一的基础结构层可以依赖于任何其他层。另一方面,域层作为顶部的中心层,只能依赖于下面的层,即SharedKernel-Domain(它也是域的一部分)和PhpExtension(它的代码被当作语言本身的一部分使用)。

类依赖关系图

层依赖关系图分析层与层之间的依赖关系,但是在层中仍然有不能发生的依赖关系。

类依赖关系图对于分析代码库中不同类型的类之间的依赖关系非常有用,特别是当它们位于同一层时。

Class dependencies diagram generated by deptrac for https://github.com/hgraca/explicit-architecture-php

组件依赖关系图

组件是一个域明智的模块,一个包含应用程序和域层的模块。例如,一个组件可以是包含所有用例和域逻辑的“计费”。

组件可以映射到DDD有界的上下文和/或微服务,这意味着它们必须在物理上和时间上与其他组件完全解耦。如果我们有一个具有完全解耦组件的单片应用程序,那么将其转换为微服务体系结构将是相当容易的(在代码方面)。

此外,将相同的解耦需求应用于其他非域明智模块,我们可以保证可以轻松替换任何模块。

组件依赖关系图的目的是确保应用程序组件和模块是解耦的。

请注意,在下面的图表中,同一层中的模块(具有相同颜色的节点)都不知道彼此,至少是直接不知道。

特别重要的是,这两个组件(用户和博客,中蓝色)是解耦的。如果这个应用程序有一个微服务体系结构,那么这两个组件就是微服务。

Component dependencies diagram generated by deptrac for https://github.com/hgraca/explicit-architecture-php

应用程序地图

大约一年前,我意识到别的我还缺少这些文档中选择:所有这些图,他们告诉我们什么是应用程序的构建块,哪些块相互作用以及它们之间的关系,但是他们不告诉我们他们所做的,也不是当他们如何相互影响。为此,我们需要从用户的角度非常了解应用程序,或者从开发人员的角度了解代码库。前面的图没有告诉我们在应用程序中有什么用例,也没有告诉我们什么用例触发了什么事件,也没有告诉我们这些事件的结果。如果我们将这些图显示给产品负责人,他会发现这些图对他的角色几乎毫无用处。

所以我想到了一个新的文档图的想法,我称之为应用程序映射,它可以替代C4模型组件图。

应用程序地图的目标是成为真正的应用程序地图,定义它的“城市”(组件)、“本地道路”(用例)、“高速公路”(事件)等等。

模块和组件之间的区别是,模块是应用程序的任何模块,而组件是应用程序的域模块。因此,虽然ORM是应用程序的模块,但它不是组件,因为它只处理技术问题。另一方面,“计费”模块是一个组件,因为它处理域问题。

应用程序映射首先定义应用程序的组件,域明智的模块,如“账单”、“用户”、“公司”、“订单”、“产品”等。对于一个简单的博客应用程序,我们可以有两个组件,“用户”和“博客”组件:

在每个组件中,我们定义可以向它们发出的命令。“用户”组件可以创建和删除用户,而“博客”组件可以创建和删除帖子,并创建对帖子的评论。

接下来,在每个组件中列出所有相关服务。这些服务是相关的,因为,例如,它们触发一个事件或被另一个组件直接使用。这很重要,因为应用程序映射应该使组件之间的连接以及它们的含义和任何后续副作用可见,为此,我们需要公开连接到其他组件的服务及其名称(应该表示它们的功能)。

在服务之后,我们在每个组件中列出所有的事件监听器,即使它们实际上没有被使用,这样做很方便,因为这样我们就可以检测它,并修复需要修复的问题,或者删除未使用的代码。

我说的侦听器是指一个类,它的公共方法都是由一种类型的事件独立触发的,它们关注的是事件。

我们还将在每个组件中列出事件订阅者,其原因与列出侦听器的原因完全相同。

事件订阅者类似于一个事件侦听器,除了它的公共方法引发了不同的事件,他们专注于一个复合任务,用户可以是一个类的一个例子听不同的框架事件为了控制何时开始、提交或回滚事务请求。

此时,我们已经在映射中包含了所有组件及其功能。这是非常有价值的,因为它告诉我们,或任何非技术人员,每个组件可以做什么。

但是,它仍然没有告诉我们所有这些功能是如何相互关联的,例如“用户创建博客文章的结果是什么?”

为了实现这一点,第一步是列出触发特定功能时组件中发生的事情。

在下面的图像中,我们可以看到删除post(“DeletePost”)将触发PostService中的DeletePost()方法,该方法也由侦听器触发,侦听通知用户已被删除的事件。这告诉我们,我们的应用程序删除帖子是用户直接命令的结果,或者是帖子作者被删除的结果。

在User组件中,我们可以看到,在创建post时,它的作者被自动订阅到post主题(标记)。

现在我们有了一个组件内部的流的信息,但是我们仍然缺乏关于跨组件流的信息,所以让我们添加被触发的事件并监听:

 

例如,我们可以看到:

删除用户将触发删除用户帖子的事件;

创建一个帖子将触发事件,这将导致订阅作者的帖子主题和增加作者评级;

从任何用例中删除一个帖子都会触发一个事件,导致作者的评分下降。

有了这些地图上的信息,我们就可以导航了。任何技术人员或非技术人员都可以清楚地看到当应用程序的任何用例被触发时会发生什么。这可以帮助我们澄清代码和应用程序行为的概念。

但是,当在大的应用程序中使用时,这个图仍然会有与前面提到的图相同的问题:

这是一种人工制品,需要花费大量的精力和时间来完成,而且只需保持它是最新的;

我们仍然会得到一个有很多行的大图表,这不是最易读的。

要解决第一个问题,我们需要能够根据需要从代码生成图。这将使创建这样一个图表变得毫不费力,消除了维护它的需要,并使创建它变得几乎是立即的。

要解决第二个问题,我们需要能够有选择地只生成图的一部分。例如,通过提供我们想要分析的用例的名称,这将只生成图中与给定用例相关的部分。

所以我们需要一个工具,一个还不存在的工具!

还是它? !????

不久前,我开始创建它,我发现只有组件内部流丢失了,但是它列出了所有的命令、服务、侦听器、订阅者和事件。它仍然是非常阿尔法的,因为缺少信息,但也因为它不灵活时,它需要分析的代码基础,但从我目前工作的公司的代码库,它可以产生这样的东西:

Example of an (incomplete) application map, as generated by https://gitlab.com/hgraca/app-mapper

如果你对这个项目很好奇,你可以在这里查看它,但是它仍然是非常alpha化的,它只是一个概念的证明,我已经有几个月没做这个了。如果你觉得这是一个有价值的项目,你有空闲时间可以贡献,告诉我,我会努力让你跟上进度,并创建任务,你可以拿起它,把它带到下一个阶段。

 

原文:https://herbertograca.com/2019/08/12/documenting-software-architecture/#more-16953

本文:

讨论:请加入知识星球【首席架构师圈】或者飞聊小组【首席架构师智库】

Tags
 
Article
知识星球
 
微信公众号
 
视频号