我们学习如何编写代码并构建一些很酷的应用程序,然后我们了解架构以及如何使应用程序可维护多年......
但是,当我们需要向其他人(新开发人员,产品所有者,投资者......)解释应用程序如何工作时,我们需要更多......我们需要文档。
但是我们有哪些文档选项可以表达整个应用程序构建块及其工作原理?!
在这篇文章中,我将涉及:
UML
我们可以使用UML创建几个图表,我们可以将它们分为两类:
我不会详细介绍每种类型的图表,因为在这篇文章中要介绍的内容太多了,并且有很多资源可以记录这些图表类型。要了解有关这些类型的更多信息,您可以查看上面的每个链接。
总而言之,UML很酷,非常有趣,我们可以非常有表现力,我们可以轻松地用它描绘一些想法并与同事讨论。
但是,要使用UML记录整个应用程序体系结构,我们需要使用几种类型的图表。此外,如果我们尝试使用单个类图来表达整个应用程序,我们就会遇到麻烦。
良好使用UML类图的一个示例是描述设计模式:
可以表达类,接口,可用性和继承关系,数据和行为。它既简洁又易读,而且因为它很小,所以创建起来也很快。
但是,下面的例子并不是那么有用......它非常大,所以它让人感到困惑和难以理解。此外,创建它需要花费很多时间,当我们完成时,它可能已经过时了,因为有人会在同一时间对代码进行更改。
因此,我们可以而且应该使用UML,但是对于它应该使用的情况:详细描述模式,应用程序的小部分或低细节的应用程序的高粒度视图(不使用类图)。
4 + 1架构视图模型
4 + 1架构视图模型由Philippe Kruchten创建,并于1995年在题为“ 架构蓝图 - 4 + 1”软件架构视图模型 “的论文中发表。
这种可视化软件应用程序体系结构的方式基于应用程序的5个视图/视角,告诉我们可以使用哪些图来记录每个视图。
值得注意的是,4 + 1架构视图模型并未强制要求我们使用所有提到的图表,甚至不是所有视图。我们总是需要了解这些工具,并且不再使用我们需要的工具。
架构决策记录文档
架构决策记录(ADR)实际上不是真正的记录应用程序架构当状态,而是记录原因,可以告诉别人和我们未来,为什么架构就是这样。
ADR是一个关于已经做出的体系结构决策的日志条目,它可以导致体系结构的状态,就像它现在或将来一样。它们包含了描述架构的图表背后的原因。
首先,我们需要了解一些脚手架:
我已经看到了一些用于创建ADR的模板,我在其中几个中看到了很好的东西,所以我创建了自己的模板。你也可以,也许应该创造你的,一个对你和你的团队有意义的东西。
对我来说,模板最重要的是它很简单,并且它有一些文档可以帮助填写它,甚至可以帮助做出实用和公正的决策。
使用ADR的最佳方式不仅仅是在进行讨论和做出决定后编写的文档。最好的是将它作为讨论的起点,作为RFC(征求意见请求),这是我们提交给团队/部门的其他成员的想法/提议,请求他们的意见/意见/批准。目的是使用它来开始讨论,集体讨论,做出最佳决策,并将提案文档本身用作决策日志条目(ADR)。ADR事先编写的事实并不意味着它是不可变的,它必须随着讨论的展开而更新/改进。我发现特别重要的是,所考虑的所有选项都要记下它们的优点和缺点,以激发讨论和明确的决定。
这是我提出的模板:
随意 从谷歌文档中复制它 。
如果您想更多地探讨这个主题,我建议前往 Joel Parker Henderson github存储库了解ADR 。
C4模型
C4模型是由Simon Brown引入的,它是迄今为止我遇到的关于软件架构文档的最佳想法。尽管使用他自己的示例图表,我会用自己的语言快速解释主要想法。
我们的想法是使用4种不同的粒度(或缩放)级别来记录软件架构:
第1级:系统上下文图
这是最高的粒度图。它没有什么细节,但其主要目标是描述应用程序的上下文。因此,它将由整个应用程序的一个单独的框组成,并且它将被其他框所包围,这些框指的是外部系统和应用程序与之交互的用户。
第2级:容器图
现在,我们放大我们的应用程序,上图中的蓝色方块映射到下图中的虚线方块。
在此粒度级别,我们将看到应用程序的容器,其中容器是应用程序的任何独立技术部分,例如移动应用程序,API或数据库。它还记录了所使用的主要技术以及容器的通信方式。
第3级:组件图
组件图显示了一个容器内的组件。在此上下文中,每个组件是应用程序的模块,不限于域智能模块(即计费,用户......),还包括纯功能模块(即电子邮件,短信,......)。因此,该图显示了容器的主要齿轮以及这些齿轮之间的关系。
第4级:代码
最细粒度的图,旨在描述组件内部的代码结构。对于这个级别,我们使用具有类级别人工制品的UML图。
要了解更多信息,你可以 在这里 和 这里 阅读Simon Brown自己的解释,甚至可以看到他 在这里 谈论它。
我认为C4模型是记录应用程序架构的一种很好的方式,很高兴将应用程序的架构理解到一定程度,但我仍觉得它不够用,虽然我花了一些时间把手指放在缺少的东西上。
我在这些图中看到了三个限制:
我发现了两类可以帮助我们的图表:
1. 依赖关系图:有助于告诉我们代码库中不同类型代码中存在的依赖关系。这里至关重要的是这些图表是直接从代码中自动生成的,否则图表将仅反映我们认为代码的样子,如果这是准确的,我们真的不需要这种类型的文档。
此外,可能比图表本身更重要的是能够使用这些依赖关系分析在我们预定义的依赖关系规则中断的情况下停止构建。因此,用于生成这些图表的工具也应该可以用作测试工具并包含在我们的CI管道中,就像单元测试一样,防止不必要的依赖关系到达生产,从而维护和实施模块化,从而有助于实现高可变性速率和特征发展的高速度。
在这个类别的图表中,我发现有3种不同类型的图表来断言,以断言不同的依赖类型。
在我下面的示例中,它们都是由 deptrac 为我的 宠物项目(explicit-architecture-php)生成的 ,我用它来进行实验。您可以在存储库根目录中找到用于生成它们的配置。
但请注意,我自己添加了颜色,以便在本博文中更容易阅读。颜色代表应用程序中的不同层,根据我在之前的博客文章中写到的层次:
2. 层依赖关系图
此图的目的是可视化并确保每个层中的代码只能依赖于其内部或下方的层。
因此,在下图中,我们可以看到,例如,作为顶层外层之一的Infrastructure层可以依赖于任何其他层。另一方面,作为顶层中心层的Domain层只能依赖于下面的层,即SharedKernel-Domain(也是Domain的一部分)和PhpExtension(其代码的使用就像它是语言本身的一部分)。
3. 类依赖关系图
层依赖关系图分析了层之间的依赖关系,但是在一个层中仍然存在必须不会发生的依赖关系。
类依赖关系图对于分析我们在代码库中具有的不同类型类之间的依赖关系非常有用,特别是如果它们位于同一层中。
例如,如果我们希望我们的事件可序列化,以便我们可以将它们放入队列中,我们可能不希望它们包含实体,因为将它反序列化并使用ORM将其保留是有问题的。事件依赖于服务也没有意义。使用这种类型的图表,或者更准确地说使用工具来测试依赖关系,我们可以轻松地检测到这种情况并防止它们到达生产阶段。
4. 组件依赖关系图
组件是域智能模块,包含应用程序和域层的模块。例如,组件可以是包含其所有用例和域逻辑的“计费”。
组件可以映射到DDD有界上下文和/或微服务 ,这意味着它们必须在物理上和时间上与其他组件完全解耦。如果我们有一个具有完全解耦组件的单片应用程序,那么将它转换为微服务架构将非常容易(代码方面)。
此外,将相同的解耦要求应用于其他非域模块,我们可以保证我们可以轻松更换任何模块。
组件依赖关系图旨在确保应用程序组件和模块分离。
请注意,在下图中,同一层中的模块(具有相同颜色的节点)如何彼此都不知道,至少是直接的。
特别重要的是两个组件(用户和博客,中蓝色)是分离的。如果此应用程序具有微服务架构,则这两个组件将是微服务。
deptrac 为 https://github.com/hgraca/explicit-architecture-php 生成的组件依赖关系图:
应用地图
大约一年前,我意识到我在这些文档选项中也缺少了一些东西:所有这些图表,它们告诉我们应用程序的构建块是什么,它们阻止彼此之间的交互以及它们如何相关,但它们不是告诉我们他们做了什么,以及他们如何以及何时互相交流。为此,我们需要从用户的角度非常了解应用程序,或者从开发人员的角度来看代码库。前面的图表没有告诉我们在应用程序中有哪些用例,也没有告诉我们哪些事件由什么用例触发,以及这些事件的后果是什么。如果我们向产品负责人展示这些图表,他会发现它们对于他的角色来说几乎没用。
所以我提出了一个新的文档图表的想法,我称之为应用程序图,它可以取代C4模型组件图。
应用地图的目的是为真正的地图应用程序,确定其“城市”(组件),其“地方道路”(用例),“高速公路”(事件),等等。
模块和组件之间的区别在于模块是应用程序的任何模块化部分,而组件是应用程序的领域智能模块。
因此,虽然ORM是应用程序的一个模块,但它不是一个组件,因为它只处理技术问题。另一方面,“结算”模块是一个组件,因为它处理领域关注。
应用程序映射首先定义应用程序的组件,即领域模块,如“Billing”,“User”,“Company”,“Orders”,“Products”等。在简单的博客应用程序的情况下,我们可以有两个组件,“用户”和“博客”组件。
在每个组件中,我们定义了可以向它们发出的命令。“用户”组件可以创建和删除用户,而“博客”组件可以创建和删除帖子,并为帖子创建评论。
接下来,在每个组件中,我们列出任何相关服务。这些服务是相关的,因为,例如,它们触发事件或由另一个组件直接使用。这很重要,因为应用程序映射应该使组件之间的连接以及它们的含义和任何后续副作用可见,为此我们需要公开连接到其他组件及其名称的服务(应该表达它们的作用) )。
在服务之后,我们列出了每个组件中的所有事件监听器,即使它们实际上并未使用,这很方便,因为我们可以检测它并修复需要修复的任何内容或删除未使用的代码。
事件监听器是一个类,其公共方法都是由一种类型的事件独立触发,它们专注于事件。
我们还将列出每个组件中的事件订阅者,原因与我们列出侦听器的原因完全相同。
事件订阅者类似于事件侦听器,除了它的公共方法由不同的事件触发,它们关注于复合任务,订阅者的示例可以是监听不同框架事件的类,以便控制何时启动,提交或回滚Request事务。
此时,我们在地图中拥有所有组件及其功能。这非常有价值,因为它告诉我们或任何非技术人员,每个组件可以做什么。
但是,它仍然没有告诉我们所有这些功能如何相互关联,例如“用户创建博客文章后会发生什么?”。
为了实现这一点,第一步是列出触发特定功能时组件中发生的情况。
在下图中,我们可以看到删除帖子(“DeletePost”)将触发PostService中的deletePost()方法,该方法也是由侦听通知用户已被删除的事件的侦听器触发的。这告诉我们,我们的应用程序会删除来自用户的直接命令或删除帖子作者的帖子。
在用户组件中,我们可以看到,当创建帖子时,其作者会自动订阅该帖子主题(标签)。
现在我们获得了有关组件内流的信息,但是我们仍然缺乏有关跨组件流的信息,因此我们添加被触发和监听的事件:
例如,我们可以看到:
有了我们地图中的所有这些信息,我们就可以浏览它。任何技术人员或非技术人员都可以清楚地看到触发应用程序的任何用例时会发生什么。这可以帮助我们澄清我们的代码,以及我们对应用程序行为的想法。
但是,当在大型应用程序中使用时,此图仍将存在前面提到的图表中常见的问题:
要解决第一个问题,我们需要能够按需从代码生成图表。这样可以轻松创建这样的图表,无需维护它,并使其立即创建。
为了解决第二个问题,我们需要能够有选择地仅生成图的一部分。例如,通过提供我们想要分析的用例的名称,这将导致仅生成以某种方式与给定用例相关的图的部分。
所以我们需要一个......不存在的工具......
前段时间我开始创建它,我到了只缺少组件内部流程的点,但它列出了所有命令,服务,监听器,订阅者和事件。由于缺少信息,它仍然是非常alpha的,但也因为它需要分析的代码库不灵活,但是,从我目前工作的公司的代码库,它可以生成如下内容:
由 https://gitlab.com/hgraca/app-mapper 生成的(不完整)应用程序映射示例
如果你对这个项目感到好奇,你可以在 这里查看 ,但是请注意它仍然是非常alpha,它只是一个概念证明,我已经几个月没有工作了。如果您认为这是一个有价值的项目,并且您有空闲时间做出贡献,请告诉我,我会尝试让您加快速度并创建可以将其提升到更高级别的任务。