História da Arquitetura Dodo IS: Um Monólito Primitivo

Ou toda empresa infeliz com um monólito é infeliz à sua maneira.

O desenvolvimento do sistema Dodo IS começou imediatamente, assim como o negócio Dodo Pizza, em 2011. Foi baseado na ideia de digitalização completa e total dos processos de negócios, e sozinho, que ainda em 2011 causou muitas dúvidas e ceticismo. Mas há 9 anos seguimos esse caminho - com nosso próprio desenvolvimento, que começou com um monólito.

Este artigo é uma “resposta” para as perguntas “Por que reescrever a arquitetura e fazer mudanças tão amplas e de longo prazo?” voltar ao artigo anterior "História da Arquitetura Dodo IS: O Caminho do Back Office". Começarei contando como o desenvolvimento do Dodo IS começou, como era a arquitetura original, como surgiram os novos módulos e por causa de quais problemas mudanças em grande escala tiveram que ser feitas.

História da Arquitetura Dodo IS: Um Monólito Primitivo

Série de artigos "O que é Dodo IS?" fala sobre:

  1. Monólito inicial em Dodo IS (2011-2015). (Você está aqui)

  2. O Caminho do Back Office: Bases e Barramento Separados.

  3. O caminho do lado do cliente: fachada sobre a base (2016-2017). (Em andamento...)

  4. A história dos verdadeiros microsserviços. (2018-2019). (Em andamento...)

  5. Serração finalizada do monólito e estabilização da arquitetura. (Em andamento...)

Arquitetura inicial

Em 2011, a arquitetura do Dodo IS era assim:

História da Arquitetura Dodo IS: Um Monólito Primitivo

O primeiro módulo da arquitetura é a aceitação do pedido. O processo comercial foi:

  • o cliente liga para a pizzaria;

  • o gerente atende o telefone;

  • aceita um pedido por telefone;

  • preenche-o paralelamente na interface de aceitação do pedido: leva em consideração informações sobre o cliente, dados sobre os detalhes do pedido, endereço de entrega. 

A interface do sistema de informação era mais ou menos assim ...

Primeira versão de outubro de 2011:

Ligeiramente melhorado em janeiro de 2012

Dodo Pizza Sistema de Informação Entrega Pizzaria Restaurante

Os recursos para o desenvolvimento do primeiro módulo de tomada de pedidos eram limitados. Tínhamos que fazer muito, rápido e com uma equipe pequena. Uma pequena equipe é composta por 2 desenvolvedores que lançaram as bases para todo o sistema futuro.

A primeira decisão deles determinou o destino da pilha de tecnologia:

  • Backend em ASP.NET MVC, linguagem C#. Os desenvolvedores eram dotnetchiki, essa pilha era familiar e agradável para eles.

  • Frontend em Bootstrap e JQuery: interfaces de usuário em estilos e scripts auto-escritos. 

  • Banco de dados MySQL: sem custos de licença, fácil de usar.

  • Servidores no Windows Server, porque o .NET só poderia estar no Windows (não discutiremos o Mono).

Fisicamente, tudo isso foi expresso no “dedic no hoster”. 

Arquitetura do aplicativo de recebimento de pedidos

Então todo mundo já estava falando sobre microsserviços, e SOA foi usado em grandes projetos por 5 anos, por exemplo, o WCF foi lançado em 2006. Mas então eles escolheram uma solução confiável e comprovada.

Aqui está.

História da Arquitetura Dodo IS: Um Monólito Primitivo

Asp.Net MVC é o Razor, que, a pedido de um formulário ou de um cliente, renderiza uma página HTML com a renderização do servidor. No cliente, os scripts CSS e JS já exibem informações e, se necessário, realizam solicitações AJAX por meio de JQuery.

As requisições no servidor terminam nas classes *Controller, onde ocorre o processamento e geração da página HTML final no método. Os controladores fazem solicitações a uma camada de lógica chamada *Serviços. Cada um dos serviços correspondia a algum aspecto do negócio:

  • Por exemplo, o DepartmentStructureService forneceu informações sobre pizzarias, departamentos. Um departamento é um grupo de pizzarias administrado por um único franqueado.

  • O ReceiveingOrdersService aceitou e calculou a composição do pedido.

  • E o SmsService enviou SMS chamando os serviços da API para enviar SMS.

Os serviços processam dados do banco de dados, armazenam a lógica de negócios. Cada serviço tinha um ou mais *Repositórios com o nome apropriado. Eles já continham consultas a procedimentos armazenados no banco de dados e uma camada de mapeadores. Havia lógica de negócios nos storages, principalmente naqueles que emitiam dados de relatórios. ORM não foi usado, todos confiaram no sql escrito à mão. 

Havia também uma camada do modelo de domínio e classes auxiliares comuns, por exemplo, a classe Order que armazenava o pedido. No mesmo local, na camada, havia um ajudante para converter o texto de exibição de acordo com a moeda selecionada.

Tudo isso pode ser representado por tal modelo:

História da Arquitetura Dodo IS: Um Monólito Primitivo

Modo de pedido

Considere uma maneira inicial simplificada de criar tal ordem.

História da Arquitetura Dodo IS: Um Monólito Primitivo

Inicialmente, o site era estático. Tinha preços e no topo - um número de telefone e a inscrição "Se você quiser pizza - ligue para o número e faça o pedido". Para fazer o pedido, precisamos implementar um fluxo simples: 

  • O cliente visita um site estático com preços, seleciona produtos e liga para o número listado no site.

  • O cliente nomeia os produtos que deseja adicionar ao pedido.

  • Dá seu endereço e nome.

  • O operador aceita o pedido.

  • O pedido é exibido na interface de pedidos aceitos.

Tudo começa com a exibição do menu. Um usuário-operador conectado aceita apenas um pedido por vez. Portanto, o carrinho de rascunho pode ser armazenado em sua sessão (a sessão do usuário é armazenada na memória). Existe um objeto Carrinho contendo produtos e informações do cliente.

O cliente nomeia o produto, o operador clica em + ao lado do produto e uma solicitação é enviada ao servidor. As informações sobre o produto são extraídas do banco de dados e as informações sobre o produto são adicionadas ao carrinho.

História da Arquitetura Dodo IS: Um Monólito Primitivo

Nota. Sim, aqui você não pode extrair o produto do banco de dados, mas transferi-lo do front-end. Mas, para maior clareza, mostrei exatamente o caminho do banco de dados. 

Em seguida, insira o endereço e o nome do cliente. 

História da Arquitetura Dodo IS: Um Monólito Primitivo

Ao clicar em "Criar Pedido":

  • A solicitação é enviada para OrderController.SaveOrder().

  • Pegamos Carrinho da sessão, tem produtos na quantidade que precisamos.

  • Complementamos o Carrinho com informações sobre o cliente e passamos para o método AddOrder da classe ReceiveingOrderService, onde são salvas no banco de dados. 

  • O banco de dados tem tabelas com o pedido, a composição do pedido, o cliente, e estão todos conectados.

  • A interface de exibição de pedidos extrai os pedidos mais recentes e os exibe.

Novos módulos

Receber o pedido foi importante e necessário. Você não pode fazer um negócio de pizza se não tiver um pedido para vender. Portanto, o sistema começou a adquirir funcionalidade - aproximadamente de 2012 a 2015. Durante esse tempo, surgiram muitos blocos diferentes do sistema, que chamarei de módulos, em oposição ao conceito de serviço ou produto. 

Um módulo é um conjunto de funções unidas por algum objetivo de negócios comum. Ao mesmo tempo, eles estão fisicamente no mesmo aplicativo.

Os módulos podem ser chamados de blocos do sistema. Por exemplo, este é um módulo de relatórios, interfaces administrativas, rastreador de comida na cozinha, autorização. Todas essas são interfaces de usuário diferentes, algumas até com estilos visuais diferentes. Ao mesmo tempo, tudo está dentro da estrutura de um aplicativo, um processo em execução. 

Tecnicamente, os módulos foram pensados ​​como Área (tal ideia até ficou em núcleo asp.net). Havia arquivos separados para o front-end, modelos, bem como suas próprias classes de controlador. Como resultado, o sistema foi transformado a partir deste ...

História da Arquitetura Dodo IS: Um Monólito Primitivo

...nisso:

História da Arquitetura Dodo IS: Um Monólito Primitivo

Alguns módulos são implementados por sites separados (projeto executável), devido a uma funcionalidade completamente separada e em parte devido a um desenvolvimento ligeiramente separado e mais focado. Esse:

  • Local - primeira versão site dodopizza.ru.

  • Exportações: upload de relatórios do Dodo IS para 1C. 

  • Pessoal - conta pessoal do empregado. Foi desenvolvido separadamente e tem seu próprio ponto de entrada e design separado.

  • fs — um projeto para hospedar estatísticas. Mais tarde, nos afastamos dele, transferindo todas as estáticas para o CDN da Akamai. 

O restante dos blocos estava no aplicativo BackOffice. 

História da Arquitetura Dodo IS: Um Monólito Primitivo

Explicação do nome:

  • Cashier - caixa do restaurante.

  • ShiftManager - interfaces para a função "Shift Manager": estatísticas operacionais sobre as vendas da pizzaria, capacidade de colocar produtos na lista de parada, alterar o pedido.

  • OfficeManager - interfaces para as funções de "Gerente de Pizzaria" e "Franqueado". Aqui estão reunidas as funções para montar uma pizzaria, suas promoções de bônus, receber e trabalhar com funcionários, relatórios.

  • PublicScreens - interfaces para TVs e tablets pendurados em pizzarias. As TVs exibem menus, informações publicitárias, status do pedido na entrega. 

Eles usaram uma camada de serviço comum, um bloco de classe de domínio Dodo.Core comum e uma base comum. Às vezes, eles ainda podem liderar as transições entre si. Incluindo sites individuais, como dodopizza.ru ou personal.dodopizza.ru, foi para serviços gerais.

Quando novos módulos apareceram, tentamos reutilizar ao máximo o código de serviços já criado, procedimentos armazenados e tabelas no banco de dados. 

Para uma melhor compreensão da escala dos módulos feitos no sistema, aqui está um diagrama de 2012 com planos de desenvolvimento:

História da Arquitetura Dodo IS: Um Monólito Primitivo

Em 2015, tudo estava no mapa e ainda mais em produção.

  • A aceitação do pedido cresceu em um bloco separado do Contact Center, onde o pedido é aceito pelo operador.

  • Havia telas públicas com cardápios e informações penduradas nas pizzarias.

  • A cozinha possui um módulo que reproduz automaticamente a mensagem de voz "Nova Pizza" quando chega um novo pedido e também imprime uma nota fiscal para o entregador. Isso simplifica muito os processos na cozinha, permite que os funcionários não se distraiam com um grande número de operações simples.

  • A unidade de entrega tornou-se um Delivery Checkout separado, onde o pedido era emitido para o entregador que havia feito o turno anteriormente. Seu tempo de trabalho foi levado em consideração para o cálculo da folha de pagamento. 

Paralelamente, de 2012 a 2015, surgiram mais de 10 desenvolvedores, abriram 35 pizzarias, implantaram o sistema na Romênia e se prepararam para a abertura de pontos de venda nos Estados Unidos. Os desenvolvedores não lidavam mais com todas as tarefas, mas eram divididos em equipes. cada um especializado em sua própria parte do sistema. 

Problemas

Inclusive pela arquitetura (mas não só).

caos na base

Uma base é conveniente. A consistência pode ser alcançada nele e à custa de ferramentas construídas em bancos de dados relacionais. Trabalhar com ele é familiar e conveniente, especialmente se houver poucas tabelas e poucos dados.

Mas ao longo de 4 anos de desenvolvimento, o banco de dados acabou tendo cerca de 600 tabelas, 1500 procedimentos armazenados, muitos dos quais também tinham lógica. Infelizmente, os procedimentos armazenados não trazem muita vantagem ao trabalhar com o MySQL. Eles não são armazenados em cache pela base e armazenar a lógica neles complica o desenvolvimento e a depuração. A reutilização de código também é difícil.

Muitas tabelas não tinham índices adequados, em algum lugar, pelo contrário, havia muitos índices, o que dificultava a inserção. Foi necessário modificar cerca de 20 mesas - a transação para criar um pedido pode levar cerca de 3-5 segundos. 

Os dados nas tabelas nem sempre estavam na forma mais apropriada. Em algum lugar foi necessário fazer desnormalização. Parte dos dados recebidos regularmente estava em uma coluna na forma de uma estrutura XML, isso aumentava o tempo de execução, alongava as consultas e complicava o desenvolvimento.

Para as mesmas mesas foram produzidos muito pedidos heterogêneos. As mesas populares sofreram especialmente, como a mesa mencionada acima. ordens ou tabelas pizzaria. Eles foram usados ​​para exibir interfaces operacionais na cozinha, análises. Outro site entrou em contato com eles (dodopizza.ru), onde a qualquer momento muitos pedidos podem surgir repentinamente. 

Os dados não foram agregados e muitos cálculos ocorreram em tempo real usando a base. Isso criou cálculos desnecessários e carga adicional. 

Freqüentemente, o código ia para o banco de dados quando não poderia. Em algum lugar não havia operações em massa suficientes, em algum lugar seria necessário espalhar uma solicitação em várias por meio do código para acelerar e aumentar a confiabilidade. 

Coesão e ofuscação no código

Módulos que deveriam ser responsáveis ​​por sua parte no negócio não o fizeram honestamente. Alguns deles tiveram duplicação de funções por papéis. Por exemplo, um comerciante local responsável pela atividade de marketing da rede em sua cidade teve que usar tanto a interface "Admin" (para criar promoções) quanto a interface "Office Manager" (para visualizar o impacto das promoções no negócio). Claro, dentro de ambos os módulos utilizamos o mesmo serviço que funcionava com promoções de bônus.

Os serviços (classes dentro de um grande projeto monolítico) podem chamar uns aos outros para enriquecer seus dados.

Com as próprias classes de modelo que armazenam dados, o trabalho no código foi realizado de forma diferente. Em algum lugar havia construtores através dos quais era possível especificar campos obrigatórios. Em algum lugar isso foi feito por meio de propriedades públicas. Obviamente, obter e transformar dados do banco de dados variava. 

A lógica estava nos controladores ou nas classes de serviço. 

Estes parecem ser problemas menores, mas retardaram muito o desenvolvimento e reduziram a qualidade, levando a instabilidade e bugs. 

A complexidade de um grande empreendimento

Dificuldades surgiram no próprio desenvolvimento. Era necessário fazer blocos diferentes do sistema e em paralelo. Encaixar as necessidades de cada componente em um único código tornou-se cada vez mais difícil. Não foi fácil concordar e agradar todos os componentes ao mesmo tempo. Somam-se a isso as limitações de tecnologia, principalmente no que diz respeito à base e ao frontend. Foi necessário abandonar o jQuery para frameworks de alto nível, especialmente em termos de atendimento ao cliente (website).

Em algumas partes do sistema, bancos de dados mais adequados para isso podem ser usados.. Por exemplo, posteriormente tivemos o caso de uso de mover do Redis para o CosmosDB para armazenar uma cesta de pedidos. 

As equipes e desenvolvedores envolvidos em seu campo claramente queriam mais autonomia para seus serviços, tanto em termos de desenvolvimento quanto de implantação. Mescle conflitos, libere problemas. Se para 5 desenvolvedores esse problema é insignificante, então com 10, e ainda mais com o crescimento planejado, tudo ficaria mais sério. E pela frente estava o desenvolvimento de uma aplicação móvel (começou em 2017, e em 2018 foi grande queda). 

Diferentes partes do sistema exigiam diferentes níveis de estabilidade, mas devido à forte conectividade do sistema, não pudemos fornecer isso. Um erro no desenvolvimento de uma nova função no painel de administração pode muito bem ter ocorrido na aceitação de um pedido no site, pois o código é comum e reutilizável, o banco de dados e os dados também são os mesmos.

Provavelmente seria possível evitar esses erros e problemas na estrutura de uma arquitetura modular monolítica: fazer uma divisão de responsabilidade, refatorar o código e o banco de dados, separar claramente as camadas umas das outras, monitorar a qualidade todos os dias. Mas as soluções arquitectónicas escolhidas e a aposta na rápida expansão da funcionalidade do sistema originaram problemas ao nível da estabilidade.

Como o blog Power of the Mind colocou as caixas registradoras nos restaurantes

Se o crescimento da rede de pizzarias (e carga) continuasse no mesmo ritmo, depois de um tempo as quedas seriam tantas que o sistema não subiria. Bem ilustra os problemas que começamos a enfrentar em 2015, aqui está essa história. 

No blog"Poder da mente” era um widget que mostrava dados sobre a receita do ano de toda a rede. O widget acessou a API pública Dodo, que fornece esses dados. Esta estatística está atualmente disponível em http://dodopizzastory.com/. O widget foi mostrado em todas as páginas e fez solicitações em um cronômetro a cada 20 segundos. A solicitação foi para api.dodopizza.ru e solicitou:

  • o número de pizzarias da rede;

  • receita total da rede desde o início do ano;

  • receita para hoje.

A solicitação de estatísticas sobre receita foi direto para o banco de dados e começou a solicitar dados sobre pedidos, agregando dados na hora e fornecendo o valor. 

Os caixas eletrônicos dos restaurantes iam para a mesma mesa de pedidos, descarregavam uma lista de pedidos recebidos para hoje e novos pedidos eram adicionados a ela. As caixas registradoras faziam suas solicitações a cada 5 segundos ou na atualização da página.

O diagrama ficou assim:

História da Arquitetura Dodo IS: Um Monólito Primitivo

Em uma queda, Fyodor Ovchinnikov escreveu um artigo longo e popular em seu blog. Muita gente veio até o blog e começou a ler tudo com atenção. Enquanto cada uma das pessoas que vieram lia o artigo, o widget de receita funcionou corretamente e solicitou a API a cada 20 segundos.

A API chamou um procedimento armazenado para calcular a soma de todos os pedidos desde o início do ano para todas as pizzarias da rede. A agregação foi baseada na tabela de pedidos, que é muito popular. Todos os caixas eletrônicos de todos os restaurantes abertos naquele momento vão para lá. Os caixas eletrônicos pararam de responder, os pedidos não foram aceitos. Eles também não eram aceitos no site, não apareciam no rastreador, o gerente de turno não conseguia vê-los em sua interface. 

Esta não é a única história. No outono de 2015, toda sexta-feira a carga no sistema era crítica. Várias vezes desligamos a API pública e, uma vez, até tivemos que desligar o site, porque nada adiantou. Havia até uma lista de serviços com ordem de desligamento sob cargas pesadas.

A partir de agora, começa nossa luta com cargas e pela estabilização do sistema (do outono de 2015 ao outono de 2018). Foi quando aconteceu"grande queda". Além disso, às vezes também ocorreram falhas, algumas foram muito sensíveis, mas o período geral de instabilidade agora pode ser considerado passado.

Crescimento rápido dos negócios

Por que não poderia ser feito imediatamente? Basta olhar para os gráficos a seguir.

História da Arquitetura Dodo IS: Um Monólito Primitivo

Também em 2014-2015 houve uma abertura na Romênia e uma abertura nos EUA estava sendo preparada.

A rede cresceu muito rápido, novos países foram abertos, novos formatos de pizzarias surgiram, por exemplo, abriu uma pizzaria na praça de alimentação. Tudo isso exigiu atenção significativa especificamente para a expansão das funções do Dodo IS. Sem todas essas funções, sem rastreamento na cozinha, contabilização de produtos e perdas no sistema, exibição de emissão de pedido na praça de alimentação, dificilmente estaríamos falando da arquitetura “correta” e da abordagem “correta” para desenvolvimento agora.

Outro obstáculo para a revisão oportuna da arquitetura e atenção geral aos problemas técnicos foi a crise de 2014. Coisas como essa afetam fortemente as oportunidades de crescimento das equipes, especialmente para uma empresa jovem como a Dodo Pizza.

Soluções rápidas que ajudaram

Problemas precisavam de soluções. Convencionalmente, as soluções podem ser divididas em 2 grupos:

  • Rápidos que apagam o fogo e dão uma pequena margem de segurança e nos dão tempo para trocar.

  • Sistêmico e, portanto, longo. Reengenharia de vários módulos, divisão de uma arquitetura monolítica em serviços separados (a maioria deles não são micro, mas macro serviços, e há algo nisso O relatório de Andrey Morevskiy). 

A lista seca de mudanças rápidas é a seguinte:

Escalar mestre de base

Claro, a primeira coisa que se faz para lidar com as cargas é aumentar a capacidade do servidor. Isso foi feito para o banco de dados mestre e para servidores web. Infelizmente, isso só é possível até um certo limite, então fica muito caro.

Desde 2014, mudamos para o Azure, também escrevemos sobre esse assunto na época no artigo “Como a Dodo Pizza entrega pizza usando a nuvem do Microsoft Azure". Mas depois de uma série de aumentos no servidor para a base, eles se depararam com o custo. 

Réplicas base para leitura

Duas réplicas foram feitas para a base:

LeiaReplica para pedidos de referência. É usado para ler diretórios, tipo, cidade, rua, pizzaria, produtos (domínio alterado lentamente) e naquelas interfaces onde um pequeno atraso é aceitável. Existiam 2 destas réplicas, garantimos a sua disponibilidade da mesma forma que os masters.

ReadReplica para solicitações de relatório. Esse banco de dados tinha menor disponibilidade, mas todos os relatórios iam para ele. Deixe-os ter solicitações pesadas para grandes recálculos de dados, mas não afetam o banco de dados principal e as interfaces operacionais. 

Caches no código

Não havia caches em nenhum lugar do código (de jeito nenhum). Isso levou a solicitações adicionais, nem sempre necessárias, ao banco de dados carregado. Os caches estavam primeiro na memória e em um serviço de cache externo, que era o Redis. Tudo foi invalidado pelo tempo, as configurações foram especificadas no código.

Vários servidores de back-end

O back-end do aplicativo também precisava ser dimensionado para lidar com o aumento das cargas de trabalho. Foi necessário fazer um cluster de um servidor iis. Nós reagendamos sessão de aplicação da memória para o RedisCache, o que possibilitou fazer vários servidores atrás de um balanceador de carga simples com round robin. A princípio, o mesmo Redis foi usado para caches, depois foi dividido em vários. 

Como resultado, a arquitetura tornou-se mais complicada ...

História da Arquitetura Dodo IS: Um Monólito Primitivo

… mas parte da tensão foi removida.

E então foi necessário refazer os componentes carregados, o que fizemos. Falaremos sobre isso na próxima parte.

Fonte: habr.com

Adicionar um comentário