
Como mudar a arquitetura de um produto monolítico para acelerar seu desenvolvimento e como dividir uma equipe em várias, mantendo a consistência do trabalho? Para nós, a resposta para essas perguntas foi a criação de uma nova API. Abaixo, você encontrará uma história detalhada sobre o caminho para essa solução e uma visão geral das tecnologias selecionadas, mas para começar - uma pequena digressão.
Alguns anos atrás, li em um artigo científico que é necessário cada vez mais tempo para o treinamento completo e, em um futuro próximo, serão necessários oitenta anos para obter conhecimento. Aparentemente, em TI esse futuro já chegou.
Tive a sorte de começar a programar naqueles anos em que não havia separação entre os programadores back-end e front-end, quando as palavras “protótipo”, “engenheiro de produto”, “UX” e “QA” não soaram. O mundo era mais simples, as árvores eram mais altas e mais verdes, o ar era mais limpo e as crianças brincavam no quintal, em vez de estacionar carros. Não importa como eu queira voltar naquele momento, devo admitir que tudo isso não é a intenção do supervilão, mas o desenvolvimento evolutivo da sociedade. Sim, a sociedade poderia se desenvolver de maneira diferente, mas, como você sabe, a história não tolera o humor subjuntivo.
Antecedentes
O BILLmanager apareceu exatamente no momento em que não havia uma separação rígida de direções. Ele tinha uma arquitetura coerente, era capaz de controlar o comportamento do usuário e até poderia ser expandido com plugins. O tempo passou, a equipe desenvolveu o produto e tudo parecia estar bem, mas fenômenos estranhos começaram a ser observados. Por exemplo, quando um programador estava envolvido na lógica de negócios, ele começou a criar formulários de maneira inadequada, tornando-os inconvenientes e difíceis de entender. Ou a adição de uma funcionalidade aparentemente simples levou várias semanas: arquiteturalmente, os módulos foram fortemente acoplados; portanto, ao mudar um, o outro teve que ser ajustado.
Conveniência, ergonomia e desenvolvimento global de produtos em geral podem ser esquecidos quando o aplicativo falha com um erro desconhecido. Se antes um programador conseguia trabalhar em diferentes direções, com o crescimento do produto e os requisitos para isso, isso se tornava impossível. O desenvolvedor viu toda a imagem e entendeu que, se a função não funcionar corretamente e de maneira estável, os formulários, botões, testes e promoções não ajudarão. Portanto, ele adiou tudo e sentou-se para corrigir o erro infeliz. Ele fez sua pequena façanha, que não foi apreciada por ninguém (simplesmente não havia mais força para a entrega correta ao cliente), mas a função começou a funcionar. Na verdade, para que esses pequenos feitos cheguem aos clientes, a equipe incluirá pessoas responsáveis por diferentes áreas: front-end e back-end, testes, design, suporte, promoção.
Mas esse foi apenas o primeiro passo. A equipe mudou e a arquitetura do produto permaneceu tecnicamente acoplada. Por esse motivo, não foi possível desenvolver o aplicativo no ritmo necessário; ao alterar a interface, a lógica de back-end precisou ser alterada, embora a estrutura dos dados em si permanecesse muitas vezes inalterada. Algo tinha que ser feito com tudo isso.
Front-end e back-end
Para se tornar um profissional em tudo é longo e caro, portanto, o mundo moderno dos programadores aplicados é dividido, em sua maioria, em front-end e back-end.
Tudo parece estar claro aqui: estamos recrutando programadores front-end, eles serão responsáveis pela interface do usuário e o back-end finalmente poderá se concentrar na lógica comercial, nos modelos de dados e em outros mecanismos. Ao mesmo tempo, o back-end, o front-end, os testadores e os designers permanecerão em uma equipe (porque eles produzem um produto comum, apenas se concentram em diferentes partes dele). Estar em uma equipe significa ter um espaço informacional e, de preferência, territorial; discutir novos recursos juntos e desmontar os acabados; coordene o trabalho em uma grande tarefa.
Para um novo projeto abstrato, isso será suficiente, mas já tínhamos a inscrição escrita, e os volumes do trabalho planejado e o tempo de sua implementação indicavam claramente que uma equipe não poderia fazer. Há cinco pessoas no time de basquete, 11 no time de futebol, e tínhamos cerca de 30. Isso não se encaixava no time de scrum perfeito de cinco a nove pessoas. Era necessário dividir, mas como manter a coerência? Para avançar, foi necessário resolver os problemas arquitetônicos e organizacionais.

"Faremos tudo em um projeto, será mais conveniente", disseram eles ...
Arquitetura
Quando um produto está desatualizado, parece lógico abandoná-lo e escrever um novo. Esta é uma boa decisão se você puder prever o tempo e será adequado para todos. Mas, no nosso caso, mesmo em condições ideais, o desenvolvimento de um novo produto levaria anos. Além disso, as especificidades do aplicativo são de tal ordem que seria extremamente difícil mudar do antigo para o novo com toda a diferença. A compatibilidade com versões anteriores é muito importante para nossos clientes e, se não existirem, eles se recusarão a atualizar para a nova versão. A viabilidade de se desenvolver do zero nesse caso é duvidosa. Portanto, decidimos atualizar a arquitetura do produto existente, mantendo a máxima compatibilidade com versões anteriores.
Nossa aplicação é um monólito, cuja interface foi construída no lado do servidor. O frontend implementou apenas as instruções recebidas dele. Em outras palavras, o back-end não era responsável pela interface do usuário . Em termos de arquitetura, o front-end e o back-end funcionavam como um, portanto, mudando um, fomos forçados a mudar o outro. E isso não é a pior coisa, é muito pior - era impossível desenvolver uma interface do usuário sem um conhecimento profundo do que está acontecendo no servidor.
Era necessário separar o front-end e o back-end, para criar aplicativos de software separados: a única maneira de começar a desenvolvê-los era no ritmo e no volume necessários. Mas como fazer dois projetos em paralelo, mudar sua estrutura se eles são altamente dependentes um do outro?
A solução foi um sistema adicional - uma camada . A idéia do intercalar é extremamente simples: ele deve coordenar o trabalho do back-end e do front-end e assumir todos os custos adicionais. Por exemplo, para que quando a função de pagamento for decomposta no lado de back-end, a camada combine dados e, no lado de front-end, nada precise ser alterado; ou então, para concluir o painel de todos os serviços solicitados pelo usuário, não executamos uma função adicional no back-end, mas agregamos os dados na camada.
Além disso, a camada era para adicionar certeza ao que pode ser chamado do servidor e que acabará retornando. Queria que a solicitação de operações fosse possível sem conhecer a estrutura interna das funções que as executam.

Maior estabilidade dividindo áreas de responsabilidade.
Comunicações
Devido à forte dependência entre o front-end e o back-end, era impossível fazer o trabalho em paralelo, o que atrasava as duas partes da equipe. Dividindo programaticamente um grande projeto em vários, obtivemos liberdade de ação em cada um, mas, ao mesmo tempo, precisávamos manter a consistência no trabalho.
Alguém dirá que a consistência é alcançada melhorando as habilidades sociais. Sim, eles precisam ser desenvolvidos, mas isso não é uma panacéia. Olhe para o trânsito, também é importante que os motoristas sejam educados, saibam como evitar obstáculos aleatórios e se ajudem em situações difíceis. Mas! Sem regras de trânsito, mesmo com as melhores comunicações, teríamos acidentes em todos os cruzamentos e o risco de não chegar ao local a tempo.
Precisávamos de regras que seriam difíceis de quebrar. Como se costuma dizer, para facilitar o cumprimento do que violar. Mas a implementação de qualquer lei traz não apenas vantagens, mas também custos indiretos, e nós realmente não queremos desacelerar o trabalho principal, atraindo todos para o processo. Portanto, criamos um grupo de coordenação e, em seguida, uma equipe cujo objetivo era criar as condições para o desenvolvimento bem-sucedido de diferentes partes do produto. Ela configurou as interfaces que permitiam que diferentes projetos funcionassem como um todo - as mesmas regras que são mais fáceis de seguir do que quebrar.
Chamamos esse comando de "API", embora a implementação técnica da nova API seja apenas uma pequena parte de suas tarefas. Como seções comuns de código são colocadas em uma função separada, a equipe da API analisa os problemas gerais das equipes de produtos. É aqui que a conexão de nosso front-end e back-end ocorre, para que os membros desta equipe precisem entender as especificidades de cada direção.
Talvez a “API” não seja o nome mais adequado para a equipe, algo sobre arquitetura ou visão em larga escala seria mais adequado, mas, eu acho, esse pouco não muda a essência.
API
A interface de acesso à função no servidor existia em nosso aplicativo inicial, mas parecia caótica para o consumidor. Separar o front-end e o back-end precisava de mais certeza.
Os objetivos da nova API surgiram das dificuldades diárias na implementação de novos produtos e idéias de design. Nós precisávamos de:
- Fraca conectividade dos componentes do sistema para que o back-end e o front-end possam ser desenvolvidos em paralelo.
- Alta escalabilidade para que a nova API não interfira na construção da funcionalidade.
- Estabilidade e consistência.
A busca por uma solução para a API não começou com o back-end, como geralmente é aceito, mas, pelo contrário, pensou no que os usuários precisavam.
Os mais comuns são todos os tipos de APIs REST. Nos últimos anos, modelos descritivos foram adicionados a eles por meio de ferramentas como o swagger, mas você precisa entender que esse é o mesmo REST. E, de fato, suas principais vantagens e desvantagens ao mesmo tempo são as regras, que são exclusivamente descritivas. Ou seja, ninguém proíbe que o criador dessa API se desvie dos postulados do REST ao implementar partes individuais.
Outra solução comum é o GraphQL. Também não é perfeita, mas, diferentemente do REST, a API GraphQL não é apenas um modelo descritivo, mas regras reais.
Anteriormente, falei sobre o sistema, que deveria coordenar o trabalho do front-end e back-end. O intercalar é exatamente esse nível intermediário. Tendo considerado as opções possíveis para trabalhar com o servidor, optamos pelo GraphQL como uma API para o front-end . Mas, como o back-end é escrito em C ++, a implementação do servidor GraphQL acabou sendo uma tarefa não trivial. Não vou descrever todas as dificuldades e truques que enfrentamos para superá-las, não trouxe um resultado real. Analisamos o problema do outro lado e decidimos que a simplicidade é a chave do sucesso. Portanto, decidimos por soluções comprovadas: um servidor Node.js separado com o Express.js e o Apollo Server.
Em seguida, você teve que decidir como acessar a API de back-end. Inicialmente, analisamos a direção de aumentar a API REST e, em seguida, tentamos usar complementos no C ++ para Node.js. Como resultado, percebemos que tudo isso não nos convinha e, após uma análise detalhada do back-end, escolhemos uma API baseada em serviços de gRPC .
Reunindo a experiência adquirida com o uso de C ++, TypeScript, GraphQL e gRPC, obtivemos uma arquitetura de aplicativos que permite o desenvolvimento flexível do back-end e do front-end, continuando a criar um único produto de software.
O resultado é um esquema no qual o front-end se comunica com um servidor intermediário usando consultas do GraphQL (sabe o que perguntar e o que receberá em troca). O servidor graphQL nos resolvedores chama as funções de API do servidor gRPC e, para isso, elas usam esquemas Protobuf para comunicação. O servidor de API baseado em gRPC sabe de qual microsserviço obter dados ou para quem enviar a solicitação. Os próprios microsserviços também são construídos no gRPC, o que garante a velocidade do processamento de consultas, a digitação de dados e a capacidade de usar várias linguagens de programação para seu desenvolvimento.

Esquema geral de trabalho após uma mudança na arquitetura
Essa abordagem possui vários pontos negativos, o principal dos quais é o trabalho adicional de configurar e coordenar circuitos, além de escrever funções auxiliares. Mas esses custos serão compensados quando houver mais usuários de API.
Resultado
Seguimos o caminho evolutivo de desenvolver um produto e uma equipe. Sucesso alcançado ou o empreendimento se transformou em um fracasso, provavelmente é cedo para julgar, mas resultados intermediários podem ser resumidos. O que temos agora:
- O front-end é responsável pela exibição e o back-end é responsável pelos dados.
- No front-end, a flexibilidade permaneceu em termos de consulta e recebimento de dados. A interface sabe o que você pode perguntar ao servidor e quais respostas devem ser.
- O back-end tem a oportunidade de alterar o código com confiança de que a interface do usuário continuará funcionando. Tornou-se possível mudar para a arquitetura de microsserviço sem a necessidade de refazer todo o front-end.
- Agora você pode usar dados simulados para o front-end quando o back-end ainda não estiver pronto.
- A criação de esquemas de colaboração eliminou problemas de interação quando as equipes entenderam a mesma tarefa de maneira diferente. O número de iterações para alterar os formatos de dados foi reduzido: agimos com o princípio de “medir sete vezes, cortar uma vez”.
- Agora você pode planejar o trabalho de sprint em paralelo.
- Para implementar microsserviços individuais, agora você pode recrutar desenvolvedores que não estão familiarizados com o C ++.
Por tudo isso, eu chamaria a oportunidade de desenvolver conscientemente a equipe e o projeto como a principal conquista. Acho que fomos capazes de criar condições nas quais cada participante pode melhorar de maneira mais proposital suas competências, focar nas tarefas e não despertar a atenção. Todos são obrigados a trabalhar apenas em seu próprio site, e agora isso é possível com alto envolvimento e sem alternância constante. É impossível se tornar um profissional em tudo, mas agora não é necessário para nós .
O artigo acabou sendo uma revisão e muito geral. Seu objetivo era mostrar o caminho e os resultados de pesquisas complexas sobre o tema de como mudar a arquitetura de um ponto de vista técnico para continuar o desenvolvimento do produto, além de demonstrar as dificuldades organizacionais de dividir uma equipe em partes acordadas.
Aqui, toquei superficialmente os problemas do trabalho de equipe e equipe em um produto, a escolha da tecnologia API (REST vs GraphQL), a conexão dos aplicativos Node.js. com C ++, etc. Cada um desses tópicos desenha um artigo separado e, se você estiver interessado, então nós os escreveremos.