作者A leksandr Slepchenkov,前端开发员和Andrey Sitnik,PostCSS和Autoprefixer的作者,Evil Martians的首席前端开发员
当客户的项目让我们的团队能够摇身一变,从事一些真正独特的工作时,这是非常幸运的。我们继续我们一系列以专业工具和安全工具的界面为中心的项目,跳入Akeero的UI设计和前端。这是一个分析AWS架构漏洞的服务,我们建立了一个协作式的实时图表合成器。在这篇文章中,我们着重介绍了围绕该项目前端的开发方面。
安全架构审计
Akeero是一个云安全平台,是应用和网络架构的虚拟安全审计师。这个位于爱尔兰的项目由来自世界领先的科技公司的安全和金融工程师于2020年创立,仅在一年内就在种子期前的融资回合中筹集了120万美元。
使用审计师,开发人员绘制一个应用程序的方案(包括数据库、API网关、客户端应用程序等),指出方案中每个节点的属性,平台提供由任何检测到的漏洞组成的报告。一旦发现威胁,Akeero会向用户提供一些缓解控制措施。
尽快建立MVP
Akeero团队设想了一个雄心勃勃的任务--他们希望尽早建立一个MVP,以便向投资者和潜在客户展示。该团队需要一个方便和有吸引力的专业界面,以及用于协作工作的独特功能。
该应用程序还需要在浏览器中工作,而且我们假设信息密度很高。所以Evil Martians提出了一个具有乐观的用户界面和实时功能的可视化图表合成器。
Akeero项目已经成为我们团队在设计和前端方面最杰出的客户项目之一。我们没有建立典型的 "表格",而是创建了一个智能和复杂的自定义界面,在设计中采用了漂亮的玻璃元素,并利用前沿的CSS技术作为前端开发的一部分。
图表组成:一个应用程序架构的全貌
如何描述一个有数百个相互关联的元素的复杂架构?最简单的方法是把它画成图表。
在过去,安全专家使用图形工具(例如draw.io)来绘制这种图。但这种工具是为纯粹的可视化而创建的,并不适合于链接结构。因此,举例来说,从一个方案中的一个节点到另一个节点的箭头插图只是箭头的插图;如果你导出整个方案,你最终不会得到一个Akeero算法能够处理的节点的正式连接图。
因此,我们创建了自己的作曲家,它不仅允许我们绘制矩形,用箭头连接它们,而且可以正式描述系统组件之间的关系:数据流的方向;节点和连接的属性,复杂的节点层次结构,等等。
因此,图形合成器是首要重点,所以我们围绕它建立了整个界面,并对它进行了微调,使之成为Akeero特有的。最终的结果呈现为一个经典的IDE--作曲家的画布(由辅助面板包围)占据了界面的中心位置。
建立在我们自己的基础上
市场上并没有很多关于浏览器中视觉编辑器的构建模块的解决方案。有些在开发中没有进展。还有一些不符合我们的设计目标和性能要求。
我们也有一些限制。首先,集成开发环境需要是一个协作式的,它可以让项目组一起设计他们的架构。第二,我们有自己的复杂的定制设计元素。第三,架构通常有大量的内部属性,如果能让用户折叠/展开这些属性,那就太好了。最后,我们必须以可读的方式和元素重叠的方式自动对齐对象(此外,我们必须在它们周围保持一个安全区)。
经过一系列的实验和花时间研究现有的解决方案,我们意识到,几乎所有的东西都由我们自己建立,比完善任何现有的解决方案要快。
一个 "没有传统 "的技术栈
由于我们从头开始做这个项目,我们有机会对应用程序的架构进行实验。Akeero团队让我们自由选择前端技术。这些选择包括。
- Webpack 5
- 反应器
- 类型描述
- 脚本中的CSS(Astroturf)。
- 用于设计原子化的CSS变量和PostCSS灌输
- 使用我们的配置的Stylelint和ESLint
- 带有Chromatic的Storybook,用于可视化组件测试
Logux和其他一些火星的OSS项目(文中后面会有更多介绍
整个后端和分析引擎都在客户端。
与Logux的实时协作
下一个具有挑战性的功能是协作式实时编辑。这是在火星开放源码产品Logux的基础上实现的。
Logux是一种连接客户端(网络和移动应用程序)和服务器的新方法。它不是发送HTTP请求(AJAX/REST),而是通过WebSockets在客户端、服务器和其他客户端之间同步操作日志。它建立在CRDT(无冲突复制数据类型)思想之上,具有实时更新、乐观的用户界面,并且在设计上是离线优先的。
使用Akeero应用程序,用户不需要为每个用户活动等待服务器响应。用户可能在其电脑上建立的所有众多架构模型(以及任何变化)都必须与服务进行实时无缝同步。
多重编辑
理论上,人们可以直接在后台发送AJAX--但在实际操作中,这在某些时候可能会很棘手。例如,如果两个人同时编辑某样东西,他们可能会以不同的顺序改变东西。而且他们也可以建立不同的图。或者,也许会发生一些其他的干扰:例如,由于服务器错误而无法保存更改,或者服务突然离线。
如果你想要一个现成的解决方案,Logux提供了它。它允许在复杂的情况下进行数据同步,它可以解决冲突并解决 "离线 "问题。
Logux的完善
最初,Akeero团队探索了其他解决方案,如Apollo、AppSync(亚马逊的GraphQL扩展)与Amplify DataStore(GraphQL之上的状态管理器,具有冲突解决和离线功能)。但Amplify DataStore是全新的,没有太多的文档,而且它的大小是我们所能承受的10倍以上。此外,在这种情况下,无服务器的WebSockets并不适用,而且对于我们的需求来说,会非常复杂。
但多亏了这个项目,Logux的发展能够满足我们的挑战。我们实现了将Logux动作传输到REST请求的能力,改进了Logux服务器的同步通道,并因此将项目中的依赖性减半。
典型的AJAX架构不是为实时UI设计的。当用户使用一个应用程序时,在服务器响应一切都已成功保存之前,可能会发送一个改变模型的请求。在一些应用中,加载器解决了这个问题,但代价是带来了一些速度上的限制。为了消除这个问题,Evil Martian团队不得不将一些后台逻辑复制到前端,以提供一个乐观的用户界面。同时,我们实现了在服务器发送错误响应的情况下回滚更改的选项。顺便说一下,这个功能在许多情况下都是内置在Logux中的。
最后,我们实现了一种共生的感觉。Logux完全适合现有的结构和任务,而且有了专业版--Logux服务器专业版--它支持可扩展性,服务可以在不断增加的负载下快速扩展。
使用更多的火星人开放源码
除了Logux,我们还在Akeero中使用了其他一些火星开放源码的前端项目。
UI的逻辑是建立在Nano Stores上。在前端项目中,所有的逻辑通常都是用React组件来描述。因此,编写测试并不容易,因为他们需要模拟整个浏览器。在我们的案例中,业务逻辑是以许多商店的形式抽象地描述的。因此,为了测试这些逻辑,我们需要编写纯JS代码;这些测试工作很快,而且很容易编写。我们设法用测试覆盖100%的商店代码。React组件简单地呈现了商店的状态,由于它们本身只包含很少的逻辑,我们能够通过Storybook服务用屏幕截图来测试它们。因此,由于Nano Stores,我们很快就用测试覆盖了整个应用程序,这有助于在紧迫的时间内准备MVP。
我们还使用了流行的Autoprefixer、Browserslist和PostCSS项目。然而,我们可以有把握地假设,几乎每一个网络项目都会利用这些东西(包括你自己的!),所以现在我们不要纠结于它们。
一个基于SVG的合成器
在浏览器中,有三种描述页面元素的方式:通过Canvas、HTML或SVG。我们可以立即注意到,Canvas有太多的代码开销,不适合我们的目的。HTML的好处来自于它提供了许多灵活的布局元素。我们只需设置块的顺序,它们就会自动出现在应有的位置。缺点是,HTML在这里相当慢。不过,我们还是对这个选项进行了一番探索。最终,我们意识到,由于我们的应用程序需要处理大量的元素,速度的下降会让人望而却步。相比之下,SVG元素更容易(也更快)被浏览器渲染,而且你还可以创建更多的元素。
我们最初编写的图表合成器有两层--一个HTML层和一个SVG层,其中的组件用于与用户创建的架构图进行交互--移动连接、创建新的连接,等等。然后,Akeero团队给我们分配了一个任务,将图表导出为SVG(这是在其他导出格式之外的;SVG作为一种矢量格式,通常用于演示目的--在大屏幕上展示、打印海报等)。为了支持这一功能并提高渲染速度,我们将整个合成器迁移到了SVG。
在这一过程中,我们不得不解决以下问题:虽然浏览器是最先进的工具,但嵌入到操作系统中的SVG浏览器已经过时了。而且,我们不得不编写一些代码来自动填充部分SVG代码。
分批处理
当用户编辑一个架构的模式时,我们必须首先积累变化,存储它们,并将它们转移到一个框架--在我们的案例中是React--以便进行渲染。通常,在一个框架中,每个组件都是独立更新的。因此,当用户选择5000个节点并将它们向右移动10个像素时,就会有5000条信息传到React,它将重绘元素5000次,而这是非常缓慢的。
解决这个问题需要批处理--一种允许组合数据和设置条件的机制:例如,每200秒渲染一次或在每个渲染周期中渲染一次。我们给Akeero团队提供了几个实施方案。第一个方案(他们最终选择了这个方案)是迁移到React 18,它默认有内置的渲染批处理功能。现在,当框架收到5000个渲染请求时,它们可以被分组并一起进行渲染。浏览器有渲染的时刻,在这些时刻之间,React将事件分组。
我们还消除了一些额外的抽象问题,并在我们的Logux状态管理器中实现了批处理(我们专门为此目的建立了它)。
通过Logux状态管理器,我们使用了事件传递通道,或者更准确地说,是日志--一个在客户端和服务器之间自动同步的事件列表。这就是为什么我们可以很容易地找到我们需要同步的事件。
自动布局
这个功能允许用户将元素以任何顺序放入图中,然后我们运行代码,它就会自动排列。我们研究了许多前端库来实现它,考虑那些适合我们的限制--嵌套组和节点的物理尺寸。后者是一个重要的考虑因素,因为许多自动排列器只是将所有的圆圈排列成相同的尺寸。
我们选择了一个允许我们在服务器端简单实现所需功能的库--Cytoscape.jsBFS(一个用于绘制具有多种布局的图形的大型库),我们将其与Cola布局串联使用。我们在Logux后端集成了它们,实现起来有点复杂:我们通过两种算法对元素进行排列,使其在图中看起来更好。
项目中的优化
在这个项目的工作过程中,我们有很多的优化工作要做。以下是其中一些发挥作用的地方。
模拟它!
由于我们需要在紧迫的时间内交付一个MVP,我们不能等后端工作完成后再开始实施我们的前端想法。因此,我们与后端团队同时工作,创建了3个工作区域:前端、Logux服务器(或后端的前端)和后端。因此,当后端部分准备好后,我们不需要重建前台来匹配它,我们在Logux服务器中解决了所有的集成任务。
我们模拟了本地服务器和API,以检查前端的操作,我们能够立即向客户展示所实现的功能。这个模拟为开发其他功能节省了时间。
轻量级前台
我们希望确保前端的代码有最小的数据占用。我们最终得到了一个最小尺寸为315KB(无gzip),最大尺寸为540KB(无gzip)的JS包。这比行业平均水平低了10倍左右,因为典型项目的JS文件可以达到2MB。这个文件大小减少了应用程序启动时浏览器的冻结,对提高应用程序的速度有很大作用。
以下是使之成为可能的一些事情:首先,我们没有仅仅使用我们遇到的第一个适用的库。即使它们在技术上符合我们的需求,它们中的大多数在质量上仍然很差,所以对开发速度的好处是以降低用户体验质量、开发敏捷性和性能为代价的。我们严格审查了所有可能的替代方案,只有当它们符合我们的标准时才会采用。
第二,我们试图与典型的前端行业疾病作斗争,在这种情况下,团队倾向于创造一个由许多现成的组件组成的弗兰肯斯坦的怪物,而不是开发一个解决方案。从理论上讲,这应该能加快工程时间,但在实际操作中,只有在项目开始时才会这样。这是因为团队会很快意识到他们没有足够的灵活性,而且由于他们使用了许多由其他人构建的模块,他们对整个工程架构缺乏了解。结果,一两个月后,开发速度就开始变慢。因此,我们并不害怕自己写代码,这意味着我们知道在应用程序中使用的所有代码。开始时比较慢,但我们后来没有任何减速,因为我们对代码库有完全的控制。
第三,我们从一开始就有一个性能预算。每当JS程序集的大小增加时,我们的工程师必须更新预算--这一要求使我们在让大小增加之前三思而行。我们使用自己的Size LimitOSS项目对JS文件和项目的大小进行预算。
最后,我们对我们的解决方案也采用了同样严格的要求。Logux和Nano Stores是非常紧凑的库,与一些现代解决方案不同,它们不会包括不会在JS捆绑中使用的代码(由于API设计)。例如,我们一开始就在Logux和AWS的socket解决方案之间做决定。但是AWS的解决方案重达100KB,而Logux-9KB。这是一个明确的选择。
符号
作为这个项目的一部分,我们还处理了favicons。我们收集了一个最小的favicon集,在所有的浏览器中都很好用。48px的/favicon.ico可以兼容所有的浏览器,而不同尺寸的/icon.hash.svg(感谢SVG)则适合现代浏览器。SVG还让我们通过媒体表达式来设计一个黑暗/光明的图标主题。用于iOS的/apple-app-icon.hash.png/icon-192.hash.png和用于PWA的/icon-512.hash.png与manifest。
火星人设计系统
Akeero是我们最早使用火星设计系统的项目之一,它使我们的前端设计师的生活变得更加轻松。例如,字体被赋予一个小数点的大小以适应网格。另外,我们在各处都使用了明确定义的缩进系统,这样前端工程师就不需要手动测量一切。工程师们知道哪里和上下的距离,这在很大程度上帮助了事情。
因此,当我们的设计师完成了他的部分工作后,火星人的前端工程师可以独立地添加预制件中缺少的接口。
4个月内完成MVP
在相当短的4个月时间里,我们成功地完成了一项复杂的任务,并帮助发布了产品的第一个版本,其中包括一个可以工作的图表合成器和其他功能。CRDT和专业的UI设计使协作工作变得顺畅和实时方便。请看这个视频,看看它是如何运作的!
公开发布后,该应用程序获得了第一批现实生活中的用户,Akeero团队继续自行开发该产品。
我们已经准备好讨论您在任何开发阶段对构建开发者工具的需求--从MVP到关键功能发布。联系我们,了解我们在这一领域的经验和技能可以为您做什么