我们如何看到巨石。第2部分,框架管理器

嗨,我叫Stas,我在Tinkoff业务团队工作。在上一篇文章中,我的同事Vanya讲述了我们的应用程序体系结构是如何安排的Vanya多次提到某个框架管理器,该框架管理器充当应用程序协调器,现在我将更详细地介绍给您。



图片



什么是Tinkoff业务



Tinkoff Business为中小型企业提供解决方案:薪金项目,现金和结算服务,文档设计器以及大约20种其他产品。

所有这些都是在应用程序中实现的。这些应用程序是由独立的团队开发的,具有各自的发布周期。所有这些应用程序都具有单一授权,包含业务逻辑的公共部分,放置在单独的库中,并使用公共UI组件。



让我们回到2年前



一个典型的Tinkoff Business应用程序看起来像这样:



图片



顶部-具有在应用程序中导航的标题,而右侧-具有产品导航的侧栏。

当时,微型阵线的想法尚未普及,但我们已经朝着这个方向发展:侧边栏是一个单独的Angular应用程序。主应用程序将侧边栏加载到iframe中,从而可以独立发布应用程序。



这种方法有其缺点:在产品之间切换时,您必须等待两个Angular应用程序加载整个页面。而且,由于大多数应用程序使用相同的后端API请求,因此用户必须等待它们重新执行。



框架经理的想法



我们一直使用这种架构,直到出现所有应用程序的全局任务-重新设计。然后,这个想法浮出水面:为什么不进行某种形式的控制反转,而不是将应用程序加载到自己内部的侧边栏中,而侧边栏将自己加载应用程序呢?



这样就可以保留当前体系结构的优势并摆脱上述问题(并带来新的问题,哈哈)。



初始原型



最初,我们创建了一个原型,该原型具有加载其他应用程序所需的最少功能。在测试平台上创建了一个单独的域。 nginx中用于应用程序静态的路由已更改:先前相应应用程序静态沿路径/ sme/ account/ salary加载。现在,沿着所有路径发送了Frame Manager静态消息,并且将postfix / static添加到了应用程序本身的静态路由中



为了使它更清楚,让我们看一个示例:您需要使用路径some-route加载位于path / some-app上的应用程序。抛开细节,让我们看一下加载/ some-app / some-route /时发生的过程

  1. Nginx沿此路径发送框架管理器静态信息。
  2. 框架管理器正在加载。基于路由,它知道加载some-app
  3. 使用src ='/ some-app / static /'创建一个iframe,并在其中加载主应用程序。


同时,需要对应用程序本身进行重大改进。因此,我们派生了应用程序分支的母版,并在其中添加了必要的更改,之后,我们将所做的更改提升了应用程序本身的各个实例。



第一个问题



因此,我们将4个应用程序转移到了Frame Manager,并确保该解决方案正在运行。所有其他应用程序都将被翻译。在这里,我们遇到了一个问题:事实证明,同时维护常规版本和用于Frame Manager的版本过于昂贵。



对于旧版本的母版的每次更改,我们都必须不断更新应用程序的新版本,解决新出现的冲突,现有功能经常失效,回归测试的成本几乎翻倍-所有这些花费了太多时间。显然,需要一种新的解决方案。



改进之处



如果该应用程序的一个版本可以与边栏和框架管理器一起使用,则可以为我们节省很多问题。让我们看看可以做什么。



首先,您需要以某种方式确定框架管理器中是否正在运行应用程序。这很简单:您需要比较window.top和window.self的引用。如果它们不相等,那么我们就在框架中,即在框架管理器中!但是,如果默认情况下在iframe中打开了应用程序,则需要添加其他逻辑。因此,我们有一个窗口小部件应用程序,该应用程序最初在框架中打开,并开始假定它始终在框架管理器中,这就是为什么它在旧模式下无法使用的原因。



现在,让我们仔细看看应用程序中需要进行哪些更改以及如何支持两种模式下的工作:

  1. url. iframe, . , - , — . url’ Frame Manager, . .

  2. . Frame Manager’ . : . . , , , post messages custom events. Frame Manager.

  3. . , Frame Manager. , Angular .

  4. . , , TCS, config.js . Frame Manager .

  5. base href. nginx, base href ( /static/). : , base href , . , , , , base href, .

  6. 授权。为了在所有应用程序中进行授权,使用了一个单独的脚本,该脚本嵌入在index.html中。在新版本中,此脚本嵌入在框架管理器中,在应用程序中重复使用会导致错误。您可以更改脚本逻辑,以便在将应用程序加载到框架管理器中时将其忽略。



这些都是可行的解决方案,但不够灵活。添加了具有逻辑的新分支,该逻辑也需要在不同位置进行维护。通常,所有事物看起来都过于复杂且相当不稳定。



重塑iframe



然后,我想到了修改一下index.html应用程序加载过程的想法。您可以向index.html发出xhr请求,以文本形式获取页面,进行处理,然后将其加载到iframe中,而不是将应用程序加载到指定src属性的iframe中。这将完全控制已加载的应用程序:它将允许您定义基本href,删除不必要的脚本,补丁样式,覆盖变量等等!



是的,开发人员不鼓励进行Mokey修补,这被认为是不好的做法,但是如果Angular团队在zone.js库中使用它,我们会变得更糟吗?可能会对性能产生怀疑:解析html看起来很昂贵。但是,通常,Angular应用程序的起始页面不超过50行,并且所有浏览器(甚至是IE 10!)都具有便捷的apiDOMParser,它允许您从字符串获取DOM。



让我们看一下框架管理器在加载应用程序时的功能(框架管理器本身已经加载):

  1. 根据路径,加载应用程序的index.html。

  2. 解析页面,将其转换为DOM,删除内存中不必要的脚本,用配置和样式替换基本href,全局变量。

  3. 创建一个iframe元素,该元素使用document.write()写入生成的文档(转换回字符串)。
  4. 将应用程序置于应连接的路由。它还提供了业务逻辑通过数据交换服务工作所需的模型。



因此,在上述六个必要的逻辑更改中,仅第一个(URL同步)需要在应用程序内部实现,其余的由框架管理器接管!



得到了什么



实际上,我们完全更改了应用程序的外观,而无需更改应用程序本身的代码。

之前。侧边栏用红色圈出。嵌入到iframe中
sidebar



后。框架管理器以红色突出显示。应用已加载到iframe中
frame manager



获得了覆盖或添加全局变量和样式的功能。

例如,这就是应用程序的样式配置的样子
export const business = {
    'sidebar.b-main__sidebar': {
        display: 'none'
    },
    '.b-main': {
        'margin-left': '260',
        position: 'relative',
        display: 'block',
        width: '1104px',
        'min-height': '100vh',
        margin: '0 auto'
    }
};




等等-应用程序本身的配置
{
        id: 'products',
        name: ' ',
        icon: 'products',
        frameSupported: true,
        applications: [
            {
                id: 'products',
                path: '/products',
                apiPrefix: '/products',
                hasMenuConfig: true,
                dynamicCompanyChange: true,
            }
        ]
    }




同时,这些配置位于与Frame Manager分开的存储库中,这使您可以更改应用程序工作的某些参数而无需释放。



我们还创建了应用程序之间的无缝过渡,并在Frame Manager中进行了授权。我们已经实现了由于Frame Manager和应用程序之间的数据共享,因此不会发出不必要的请求。



并非没有问题:某些chrome插件(CryptoPro,redux devtools)在可下载的应用程序中停止工作,因为到窗口的链接在交互过程中丢失了。需要其他改进。



结果,在2019年底,我们成功地将所有应用程序转移到了Frame Manager,并且侧边栏已经被遗忘了。但是框架管理器的工作仍在继续,并且出现了一个新问题:是否有可能以某种方式改进和优化Tinkoff Business中前端的工作?原来可以!但是在下一篇文章中会对此进行更多介绍。



All Articles