Cómo cortamos el monolito. Parte 2, Administrador de cuadros

Hola, mi nombre es Stas, trabajo en el equipo de Tinkoff Business. En el último artículo, mi colega Vanya contó cómo está organizada nuestra arquitectura de aplicaciones . Varias veces Vanya mencionó cierto Frame Manager, que sirve como orquestador de aplicaciones, y ahora te lo contaré con más detalle.



imagen



¿Qué es Tinkoff Business?



Tinkoff Business ofrece soluciones para pequeñas y medianas empresas: un proyecto salarial, servicios de liquidación y caja, un diseñador de documentos y alrededor de 20 productos más.

Todo esto se implementa en aplicaciones. Estas aplicaciones son desarrolladas por equipos independientes y tienen sus propios ciclos de lanzamiento. Y todas estas aplicaciones funcionan con una sola autorización, contienen una parte común de la lógica empresarial, se colocan en una biblioteca separada y utilizan componentes de IU comunes.



Regresemos hace 2 años



Una aplicación típica de Tinkoff Business se veía así:



imagen



arriba, un encabezado con navegación a través de la aplicación, y a la derecha, una barra lateral con navegación de productos.

En aquel entonces, la idea de una microfrente aún no era tan popular, pero ya nos estábamos moviendo en esta dirección: la barra lateral era una aplicación Angular separada. La aplicación principal cargó la barra lateral en un iframe, lo que permitió que la aplicación se lanzara de forma independiente.



Este enfoque tenía sus inconvenientes: al cambiar entre productos, tenía que esperar a que se cargara una página completa con dos aplicaciones angulares. Y debido a que la mayoría de las aplicaciones utilizan las mismas solicitudes de API de backend, los usuarios tuvieron que esperar a que se volvieran a ejecutar.



Idea de Frame Manager



Vivimos con esa arquitectura hasta que apareció una tarea global para todas las aplicaciones: el rediseño. Entonces surgió la idea: ¿por qué no hacer algún tipo de inversión de control y en lugar de que las aplicaciones carguen la barra lateral dentro de sí mismas, la barra lateral cargaría las aplicaciones?



Esto hizo posible preservar las ventajas de la arquitectura actual y deshacerse de los problemas anteriores (y traer otros nuevos, jaja).



Prototipo inicial



Inicialmente, creamos un prototipo con la funcionalidad mínima requerida para cargar otras aplicaciones. Se creó un dominio separado en el banco de pruebas. Las rutas en nginx para las estáticas de la aplicación han cambiado: anteriormente , las estáticas de las aplicaciones correspondientes se cargaban a lo largo de las rutas / sme , / cuenta , / salario , etc. Ahora las estáticas de Frame Manager se enviaron a lo largo de todas las rutas, y se agregó el sufijo / estática a las rutas estáticas de las aplicaciones .



Para hacerlo más claro, veamos un ejemplo: necesita cargar una aplicación ubicada en la ruta / alguna-aplicación con alguna-ruta . Dejando los detalles a un lado, veamos qué proceso ocurre al cargar / some-app / some-route / :

  1. Nginx envía estadísticas de Frame Manager a lo largo de esta ruta.
  2. Se está cargando Frame Manager. Según la ruta, entiende cargar alguna aplicación .
  3. Se crea un iframe con src = '/ some-app / static /' donde se carga la aplicación principal.


Al mismo tiempo, se requirieron mejoras significativas en las propias aplicaciones. Por lo tanto, bifurcamos el maestro de la rama de la aplicación y agregamos los cambios necesarios allí, después de lo cual eliminamos las instancias individuales de las aplicaciones con los cambios realizados.



Primeros problemas



Así que transferimos 4 aplicaciones a Frame Manager y nos aseguramos de que la solución funcione. Todas las demás aplicaciones debían traducirse. Y aquí nos encontramos con un problema: resultó demasiado caro mantener simultáneamente la versión normal y la versión para trabajar con Frame Manager.



Teníamos que actualizar constantemente nuevas versiones de aplicaciones para cada cambio en el maestro de versiones antiguas, resolver conflictos emergentes, la funcionalidad existente a menudo se rompía, el costo de las pruebas de regresión casi se duplicaba, todo esto llevó demasiado tiempo. Estaba claro que se necesitaba una nueva solución.



Mejoras



Si una versión de la aplicación pudiera funcionar tanto con la barra lateral como con Frame Manager, eso nos ahorraría muchos problemas. Veamos qué se puede hacer.



En primer lugar, debe determinar de alguna manera si la aplicación se está ejecutando en Frame Manager. Esto es bastante simple: necesita comparar las referencias de window.top y window.self. Si no son iguales, entonces estamos en un marco, es decir, ¡en el Administrador de marcos! Pero, si hay aplicaciones que se abren en un iframe de forma predeterminada, debe agregar lógica adicional. Entonces, teníamos una aplicación de widget que inicialmente se abrió en un marco y comenzamos a asumir que siempre está en el Administrador de marcos, por lo que se rompió en el modo anterior.



Ahora echemos un vistazo más de cerca a los cambios que se necesitan en las aplicaciones y cómo puede apoyar el trabajo en dos modos:

  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. Autorización. Para la autorización en todas las aplicaciones, se utiliza una secuencia de comandos separada, que está incrustada en index.html. En la nueva versión, este script está integrado en Frame Manager y su reutilización en aplicaciones dará lugar a errores. Puede cambiar la lógica del script para que se ignore si la aplicación se carga dentro del Frame Manager.



Todas estas son soluciones que funcionan, pero no son lo suficientemente flexibles. Se han agregado nuevas ramas con lógica que también debe mantenerse en diferentes lugares. En general, todo parece una estructura demasiado complicada y bastante inestable.



Reinventando el iframe



Luego tuve la idea de hackear un poco el proceso de carga de la aplicación index.html. En lugar de cargar la aplicación en un iframe que especifique el atributo src, puede realizar una solicitud xhr para index.html, obtener la página en forma de texto, procesarla y cargarla en el iframe. Esto le dará un control completo sobre la aplicación cargada: le permitirá definir href base, eliminar scripts innecesarios, estilos de parche, anular variables y mucho más.



Sí, los desarrolladores desaconsejan el parche mokey y se considera una mala práctica, pero si el equipo de Angular lo usa en la biblioteca zone.js, ¿en qué estamos peor? Pueden surgir dudas de rendimiento: analizar html parece una operación costosa. Pero, como regla general, la página de inicio de una aplicación Angular no excede las 50 líneas, y en todos los navegadores (¡incluso IE 10!) Hay una api convenienteDOMParser , que le permite obtener el DOM de una cadena.



Echemos un vistazo a lo que hace Frame Manager mientras carga la aplicación (el Frame Manager en sí ya está cargado):

  1. Según la ruta, carga el index.html de la aplicación.

  2. Analiza la página, la convierte a DOM, elimina los scripts innecesarios en la memoria, reemplaza el href base, las variables globales con configuraciones y estilos.

  3. Crea un elemento iframe que escribe el documento resultante (convertido nuevamente en una cadena) usando document.write ().
  4. Pone a la aplicación una ruta a la que debe conectarse. También alimenta los modelos necesarios para que la lógica empresarial funcione a través del servicio de intercambio de datos.



Por lo tanto, de los seis cambios necesarios en la lógica anteriores, solo el primero (sincronización de url) debe implementarse dentro de la aplicación, ¡el resto es asumido por Frame Manager!



Que tiene



Cambió por completo el aspecto de la aplicación, prácticamente sin realizar ningún cambio en el código de la propia aplicación.

Antes de. La barra lateral está rodeada de rojo. Incrustado en un iframe
sidebar



Después. Frame Manager está resaltado en rojo. Aplicación cargada en iframe
frame manager



Tiene la capacidad de anular o agregar variables y estilos globales.

Por ejemplo, así es como se ve la configuración de estilo para la aplicación
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'
    }
};




Y así, la configuración de la propia aplicación.
{
        id: 'products',
        name: ' ',
        icon: 'products',
        frameSupported: true,
        applications: [
            {
                id: 'products',
                path: '/products',
                apiPrefix: '/products',
                hasMenuConfig: true,
                dynamicCompanyChange: true,
            }
        ]
    }




Al mismo tiempo, las configuraciones están en un repositorio separado del Frame Manager, lo que le permite cambiar algunos parámetros del trabajo de la aplicación sin liberarlos.



También creamos transiciones perfectas entre aplicaciones y realizamos la autorización en Frame Manager. Hemos logrado que, debido al intercambio de datos entre Frame Manager y las aplicaciones, no se realicen solicitudes innecesarias.



No sin problemas: algunos complementos de Chrome (CryptoPro, redux devtools) dejaron de funcionar en la aplicación descargada, porque el enlace a la ventana se perdió durante la interacción. Se requirieron mejoras adicionales.



Como resultado, a fines de 2019, transferimos con éxito todas las aplicaciones a Frame Manager, y la barra lateral se ha hundido en el olvido. Pero el trabajo en Frame Manager continuó y surgió una nueva pregunta: ¿es posible mejorar y optimizar de alguna manera el trabajo de la interfaz en Tinkoff Business? ¡Resultó que puedes! Pero más sobre eso en el próximo artículo.



All Articles