关于技术架构:PingCode-Flow技术架构揭秘
作者:PingCode 研发 VP@徐子岩本文是PingCode Flow系列文章的第四篇,不出意外的话,也是最初一篇。从去年五月份PingCode Flow正式上线,就在脑海中构思好了四篇文档的程序和大抵内容,从介绍研发自动化的现状、痛点和解决方案,到展现如何应用PingCode Flow来实现研发的自动化。而这最初一篇,则是心愿可能从纯技术角度展现PingCode Flow外部是如何工作的,如何在每天将近4000次规定执行的压力下,保障99%的规定都能在1秒内实现,同时反对程序、并行、判断、循环等简单的运行逻辑。同时,咱们也心愿在这篇文章中分享一下咱们是如何剖析和思考,并最终得出当初的架构。因而本文不会间接展现最终的设计后果,而是会论述咱们为什么要如此的设计,优缺点的衡量,以及开发过程中的重构。PingCode Flow系列文章传送门 研发自动化,你筹备好了么 5分钟带你玩转PingCode Flow 咱们是如何应用 PingCode Flow实现研发自动化治理的 PingCode Flow的实质是什么在前几篇文章中咱们提到,PingCode Flow是一款研发自动化工具。所谓的自动化,就是指在某个事件产生后,依照预约义的流程去实现一系列的操作。所以,实质上来讲,PingCode Flow是一个TAP(Trigger Action Platform)零碎。它由一个触发器和多个动作组成一个有序的执行规定,而后依照这个规定程序执行。因而,PingCode Flow在技术架构设计的时候,就是要确保这样的流程可能顺畅的运行。 数据结构:怎么定义一个规定确定了产品的外围指标后,第一件事就是要明确数据是如何定义的。依照下面的图示,在一个团队中用户可能会定义多个规定,而每个规定都蕴含了一个触发器和多个后续的动作。基于这个简略的需要,能够将规定的数据结构定义如下。这样,一个规定就蕴含了一个触发器以及它所蕴含的动作。而动作的序号决定了他们执行的先后顺序。这样的设计看来根本满足了目前的产品需要。然而咱们晓得,当初的PingCode Flow不仅仅反对上述的复线程序执行流程,还反对条件、并行、判断、循环等负责的执行流程。而且不止于此,咱们须要将上述这些流程自由组合,譬如并行外部有判断,判断外面有循环,循环中还有并行……通过这样简直有限的组合,让一个规定实现简直任意的流转。因而,上述简略的数据结构就齐全不能满足需要。而如何设计一个可能反对各种场景的规定,是摆在咱们PingCode Flow团队背后的第一个难题。如果咱们以「一个规定就是一系列动作的汇合」这个形式去思考,那么很难设计出绝对通用的数据结构。因为规定内的动作是由用户决定的,不可能穷举出所有可能的构造进去。然而能够尝试换一种思路来思考这个问题,也就是说,咱们不再把规定看做是触发器和动作的有序列表,而是将他们定义为一个链表。那么一个规定就是 触发器及下一个动作以后动作及下一个动作的汇合。如果咱们再将「触发器」和「动作」合并为「步骤」。那么一个规定就是第一个步骤以后步骤的下一个步骤这样,咱们对于规定和步骤的定义就能够对立为即规定并不关怀它外部的动作都是什么以及先后顺序,它只关怀第一个动作是什么。而每一个动作也仅仅关怀下一个动作是什么。对于并行、循环、判断等简单的流程,咱们只须要扩大对应动作的数据结构,就能够实现不同的排列组合。譬如对于并行,它的数据结构是这样的。「并行」外部的每个分支的第一个步骤ID保留在一个数组中,示意这个分支要执行的第一个步骤是什么。「并行」自身不关怀每个分支外面具体的流程是什么样的。它只关怀当所有分支都执行结束后,下一个步骤是什么。基于这样的构造,对于上述这个简单的规定 咱们的数据大抵是这样的。对于 步骤1 ,它只设置了下一个步骤是 步骤2 。 对于 步骤2 ,它外部有两个分支 步骤3 和 步骤4 ,下一个步骤是整个分支全副执行结束的 步骤10 。因而它的数据是这样的。 对于 步骤4 ,它是一个循环步骤。进入循环体的第一个步骤是 步骤6 ,而它实现循环后就会完结以后的分支操作。因而它的数据是这样的。 下一个步骤ID 为空,示意以后分支完结。 执行逻辑:如何反对各种类型的步骤当咱们确定了数据结构之后,规定内步骤的执行形式也随之确定了。和构造相似,当一个规定被启动后(咱们先不思考规定是如何被触发的),它会首先找到第一个动作的ID。相熟PingCode Flow的读者们都晓得,咱们零碎内预置了很多的动作,譬如设置工作项负责人、创立页面、变更测试用例状态等等。那么这些动作是怎么被执行起来的呢。首先,每一个动作都会有一个全局惟一的名称。当规定执行到这个步骤的时候,咱们会通过步骤的ID找到它的动作名。通过动作的名称定位代码中对应的理论执行逻辑。譬如「设置工作项负责人」这个动作,它的连接器名称是 project ,动作名称是 set_assignee 。代码大抵如下。 @action({ name: "set_assignee", displayName: "设置工作项负责人", description: "设置以后一个或多个工作项的负责人。", isEnabled: Is.yes, allowCreation: Is.yes, allowDeletion: Is.yes})export class AgileActionSetWorkItemsAssignee extends AgileWorkItemsAction<AgileActionSetAssigneeDynamicPropertiesSchema, AgileActionSetAssigneeDirectives, AgileActionSetAssigneeRuleStepEntity> { constructor() { super(AgileActionSetAssigneeRuleStepEntity, undefined, /* ... */); } protected onGetDirectivesMetadata(): DirectivesMetadata<Omit<AgileActionSetAssigneeDirectives, keyof AgileWorkItemsActionDirectives>> { /* ... */ }; protected onGetDynamicPropertiesMetadata(): PropertiesMetadata<Omit<AgileActionSetAssigneeDynamicPropertiesSchema, keyof AgileWorkItemsDynamicPropertiesSchema>> { /* ... */ }; protected async onExecute(context: ExecuteContextWrapper<AgileActionSetAssigneeDynamicPropertiesSchema, AgileActionSetAssigneeDirectives, AgileActionSetAssigneeRuleStepEntity>): Promise<RuleStepResult<AgileActionSetAssigneeDynamicPropertiesSchema>> { /* ... */ }}其中最次要的代码是 onExecute ,它将会在执行这个步骤时候被调用。当操作执行结束后,会将数据库中保留的的 下一个步骤ID 返回,规定执行引擎会去调用后续的步骤。这就是一个最简略的动作步骤,由零碎调用,执行具体的操作,而后返回下一个步骤的ID。除了一般的动作之外,PingCode Flow还反对条件、并行、判断、循环等简单的流程管制。和方才提到的动作一样,都是通过重写 onExecute 这个办法来实现的。以「条件」为例,它须要在判断为真的时候继续执行后续的步骤,为假则进行以后步骤。那么它的 onExecute 就是这样的。 ...