O que é Tinkoff Business
A Tinkoff Business oferece soluções para pequenas e médias empresas: projeto salarial, serviços de caixa e liquidação, designer de documentos e cerca de 20 outros produtos.
Tudo isso é implementado em aplicativos. Esses aplicativos são desenvolvidos por equipes separadas e têm seus próprios ciclos de lançamento. E todos esses aplicativos funcionam com uma única autorização, contêm uma parte comum da lógica de negócios em uma biblioteca separada e usam componentes de UI comuns.
Vamos voltar 2 anos atrás
Um aplicativo típico da Tinkoff Business parecia algo assim:
No topo - um cabeçalho com navegação pelo aplicativo e à direita - uma barra lateral com navegação do produto.
Naquela época, a ideia de uma microfronta ainda não era tão popular, mas já estávamos indo nessa direção: a barra lateral era um aplicativo Angular separado. O aplicativo principal carregou a barra lateral em um iframe, o que permitiu que o aplicativo fosse lançado de forma independente.
Essa abordagem tinha suas desvantagens: ao alternar entre os produtos, era necessário aguardar o carregamento completo da página com dois aplicativos angulares. E devido ao fato de que a maioria dos aplicativos usa as mesmas solicitações de API de back-end, os usuários tinham que esperar que elas fossem executadas novamente.
Ideia do gerenciador de quadros
Vivemos com tal arquitetura até que uma tarefa global para todos os aplicativos apareceu - redesenhar. Então surgiu a ideia: por que não fazer algum tipo de inversão de controle e, em vez de aplicativos carregar a barra lateral dentro de si, a própria barra lateral carregaria os aplicativos?
Isso tornou possível preservar as vantagens da arquitetura atual e se livrar dos problemas acima (e trazer novos, haha).
Protótipo inicial
Inicialmente, criamos um protótipo com a funcionalidade mínima necessária para carregar outros aplicativos. Um domínio separado foi criado na bancada de teste. As rotas no nginx para a estática do aplicativo mudaram: anteriormente , as estáticas dos aplicativos correspondentes eram carregadas ao longo dos caminhos / sme , / conta , / salário , etc. Agora, as estatísticas do Frame Manager foram enviadas ao longo de todos os caminhos, e o postfix / static foi adicionado às rotas estáticas dos próprios aplicativos .
Para deixar mais claro, vejamos um exemplo: você precisa carregar um aplicativo localizado no caminho / some-app com some-route . Deixando os detalhes de lado, vamos ver qual processo acontece ao carregar / some-app / some-route / :
- O Nginx envia estatísticas do Frame Manager ao longo deste caminho.
- O Frame Manager está carregando. Com base na rota, ele entende como carregar algum aplicativo .
- Um iframe é criado com src = '/ some-app / static /' onde o aplicativo principal é carregado.
Ao mesmo tempo, melhorias significativas foram necessárias nos próprios aplicativos. Portanto, bifurcamos o master do branch do aplicativo e adicionamos as alterações necessárias lá, após o que levantamos cópias individuais dos próprios aplicativos com as alterações feitas.
Primeiros problemas
Portanto, transferimos 4 aplicativos para o Frame Manager e verificamos se a solução está funcionando. Todos os outros aplicativos deveriam ser traduzidos. E aqui encontramos um problema: ficou muito caro manter simultaneamente a versão regular e a versão para trabalhar com o Frame Manager.
Tínhamos que atualizar constantemente novas versões de aplicativos para cada mudança no mestre de versões mais antigas, resolver conflitos emergentes, a funcionalidade existente frequentemente quebrava, o custo do teste de regressão quase dobrou - tudo isso demorou muito. Estava claro que uma nova solução era necessária.
Melhorias
Se uma versão do aplicativo pudesse funcionar com a barra lateral e com o Gerenciador de quadros, isso nos pouparia muitos problemas. Vamos ver o que pode ser feito.
Em primeiro lugar, você precisa determinar de alguma forma se o aplicativo está sendo executado no Gerenciador de quadros. Isso é muito simples: você precisa comparar as referências de window.top e window.self. Se não forem iguais, então estamos em um quadro, ou seja, no Gerenciador de Quadros! Mas, se houver aplicativos que abrem em um iframe por padrão, você precisa adicionar lógica adicional. Então, tínhamos um aplicativo widget que inicialmente abria em um quadro e começamos a supor que está sempre no Gerenciador de quadros, por isso ele quebrou no modo antigo.
Agora vamos examinar mais de perto quais mudanças são necessárias nos aplicativos e como você pode oferecer suporte ao trabalho em dois modos:
- url. iframe, . , - , — . url’ Frame Manager, . .
- . Frame Manager’ . : . . , , , post messages custom events. Frame Manager.
- . , Frame Manager. , Angular .
- . , , TCS, config.js . Frame Manager .
- base href. nginx, base href ( /static/). : , base href , . , , , , base href, .
- Autorização. Para autorização em todos os aplicativos, um script separado é usado, que é incorporado em index.html. Na nova versão, este script está embutido no Frame Manager e sua reutilização em aplicativos levará a erros. Você pode alterar a lógica do script para que seja ignorada se o aplicativo for carregado dentro do Frame Manager.
Todas essas soluções funcionam, mas não são suficientemente flexíveis. Novas ramificações foram adicionadas com lógica que também precisa ser mantida em locais diferentes. Em geral, tudo parece uma estrutura complicada e bastante instável.
Reinventando o iframe
Então, tive a ideia de hackear um pouco o processo de carregamento do aplicativo index.html. Em vez de carregar o aplicativo em um iframe especificando o atributo src, você pode fazer uma solicitação xhr para index.html, obter a página em formato de texto, processá-la e carregá-la no iframe. Isso dará controle completo sobre o aplicativo carregado: permitirá que você defina href de base, remova scripts desnecessários, estilos de patch, substitua variáveis e muito mais!
Sim, o patch mokey não é recomendado pelos desenvolvedores e é considerado uma prática ruim, mas se a equipe do Angular usa na biblioteca zone.js, como pioramos? Pode haver dúvidas sobre o desempenho: analisar html parece uma operação cara. Mas, como regra, a página inicial de um aplicativo Angular não ultrapassa 50 linhas e em todos os navegadores (até mesmo no IE 10!) Há uma API convenienteDOMParser , que permite obter o DOM de uma string.
Vamos dar uma olhada no que o Frame Manager faz ao carregar o aplicativo (o próprio Frame Manager já está carregado):
- Com base no caminho, carrega o index.html do aplicativo.
- Analisa a página, convertendo-a em DOM, remove scripts desnecessários na memória, substitui href de base, variáveis globais por configurações e estilos.
- Cria um elemento iframe que grava o documento resultante (convertido novamente em uma string) usando document.write ().
- Coloca o aplicativo em uma rota à qual ele deve ser conectado. Ele também alimenta os modelos necessários para que a lógica de negócios funcione por meio do serviço de troca de dados.
Assim, das seis alterações necessárias na lógica acima, apenas a primeira (sincronização de url) precisa ser implementada dentro da aplicação, o resto é assumido pelo Frame Manager!
O que conseguiu
Mudou completamente a aparência do aplicativo, praticamente sem fazer nenhuma alteração no código do próprio aplicativo.
Antes. A barra lateral está circulada em vermelho. Incorporado em um iframe
Depois de. O Gerenciador de quadros é destacado em vermelho. App carregado em iframe
Tem a capacidade de substituir ou adicionar variáveis globais e estilos.
Por exemplo, é assim que a configuração de estilo do aplicativo se parece
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'
}
};
E então - a configuração do próprio aplicativo
{
id: 'products',
name: ' ',
icon: 'products',
frameSupported: true,
applications: [
{
id: 'products',
path: '/products',
apiPrefix: '/products',
hasMenuConfig: true,
dynamicCompanyChange: true,
}
]
}
Ao mesmo tempo, os configs estão em um repositório separado do Frame Manager, o que permite alterar alguns parâmetros de trabalho do aplicativo sem liberar.
Também criamos transições contínuas entre aplicativos, autorizações feitas no Frame Manager. Conseguimos que, devido ao compartilhamento de dados entre o Frame Manager e os aplicativos, solicitações desnecessárias não são feitas.
Não sem problemas: alguns plug-ins do Chrome (CryptoPro, redux devtools) pararam de funcionar no aplicativo para download, porque o link para a janela foi perdido durante a interação. Melhorias adicionais eram necessárias.
Como resultado, no final de 2019, transferimos com sucesso todos os aplicativos para o Frame Manager e a barra lateral caiu no esquecimento. Mas o trabalho no Frame Manager continuou, e uma nova questão surgiu: é possível melhorar e otimizar de alguma forma o trabalho do frontend no Tinkoff Business? Descobriu-se que você pode! Mais sobre isso no próximo artigo.