Arquitetura real · Pleno
Shopify: monólito modular (Deconstructing the Monolith)
Arquitetura real em produção. Pontos centrais: fronteiras de componente e checagem estática de dependências.
Resposta curta
Arquitetura real em produção. Pontos centrais: fronteiras de componente e checagem estática de dependências. O núcleo do Shopify é uma das maiores bases de código em Ruby on Rails e nasceu como um monólito: funcionalidades distintas no mesmo código, sem fronteiras entre elas.
01 · CONTEXTO
Um monólito Rails que cresceu demais
O núcleo do Shopify é uma das maiores bases de código em Ruby on Rails e nasceu como um monólito: funcionalidades distintas no mesmo código, sem fronteiras entre elas. Um balanço posterior do próprio time mostra o porte do resultado, com mais de 2,8 milhões de linhas de Ruby.
Enquanto o produto é jovem, o monólito tem vantagens reais: é a arquitetura mais fácil de implementar, concentra o trabalho num repositório e num pipeline de testes e deploy, e deixa os componentes se chamarem diretamente, sem passar por APIs de serviço.
O limite apareceu em 2016, quando ficou cada vez mais difícil construir e testar funcionalidades novas. Dois sintomas marcaram esse ponto: uma mudança aparentemente inocente podia causar uma cascata de falhas de teste sem relação aparente, e mudanças simples exigiam muito contexto.
O código de frete convivia com o de checkout, e havia pouco que impedisse um de chamar o outro.
O exemplo do artigo: o cálculo de frete chama o cálculo de imposto.
Mudar o cálculo de imposto podia alterar o resultado do frete, sem que a causa fosse óbvia.
Por causa desse entrelaçamento, quem entra no time de frete acabaria precisando entender também pedidos e pagamentos.
02 · ALTERNATIVAS
Monólito, microsserviços ou meio-termo
Uma alternativa conhecida eram os microsserviços, mas o time apontou o custo: vários pipelines e infraestrutura por serviço, sem garantia de acesso aos dados, e chamadas pela rede que somam latência e perdem confiabilidade. O Shopify escolheu manter o código numa base e definir e respeitar fronteiras entre os componentes.
| Monólito sem fronteiras | Microsserviços | Monólito modular | |
|---|---|---|---|
| Fronteiras entre domínios | Nenhuma entre as funcionalidades | A rede separa os serviços | Estritamente impostas entre domínios |
| Comunicação | Chamadas diretas entre componentes, sem API de serviço | Atravessa a rede, com mais latência e menos confiabilidade | Dentro de uma só aplicação, com código num único lugar |
| Implantação | Um repositório e um pipeline de testes e deploy | Pipelines e infraestrutura por serviço, com deploys coordenados em refatorações grandes | Mais modularidade sem aumentar as unidades de implantação |
03 · COMPONENTIZAÇÃO
Reorganizar por domínio e isolar dependências
O primeiro passo foi a organização do código. Ele seguia o padrão típico de Rails, por conceitos de software; a meta era reorganizá-lo por conceitos do mundo real, como pedidos, frete, estoque e cobrança, ou seja, por domínios de negócio.
A lista inicial de componentes veio de rotular manualmente cada classe Ruby numa planilha. A movimentação dos arquivos foi feita num único pull request gerado por scripts. O custo: parte do histórico no GitHub se perdeu, porque as movimentações apareceram como remoções e criações.
O segundo passo foi isolar dependências. Cada componente define uma interface pública e assume a posse exclusiva dos seus dados. Uma ferramenta interna, o Wedge, passou a destacar os acessos que violam essas fronteiras.
Cada componente expõe uma interface pública e é dono dos seus dados; o acesso de fora deve passar por ela.
Durante a CI, uma ferramenta usa tracepoints do Ruby para obter o grafo de chamadas do código.
As chamadas entre componentes seguem para o Wedge, que decide quais são aceitáveis e quais violam a fronteira.
Pela regra do Wedge, chamadas só são aceitas para o que é explicitamente público; num acesso como este a um interno do outro componente, o Wedge apontaria violação.
Com as dependências isoladas, o motor de imposto legado pôde ser trocado por um sistema novo de cálculo.
04 · CHECAGEM ESTÁTICA
Do grafo de chamadas ao Packwerk
No balanço seguinte, o time disse que manteve as ideias originais, mas que quase todos os detalhes mudaram. Na checagem de dependências, o time passou a usar uma ferramenta nova, o Packwerk, no lugar do Wedge.
| Wedge | Packwerk | |
|---|---|---|
| O que analisa | Chamadas registradas com a suíte de testes instrumentada na CI | Referências estáticas a constantes |
| Qualidade do sinal | Resultados muitas vezes pouco úteis; às vezes nem ficava claro de qual componente vinha a chamada | Menos ambiguidade; referências explícitas são mais acionáveis |
| Tempo de feedback | Mais de uma hora, por depender da suíte completa | Poucos minutos no maior código, encaixado no fluxo de pull request |
Uma mudança em algum componente chega como pull request.
O Packwerk analisa as referências estáticas a constantes, que são explícitas e menos ambíguas.
Como a análise completa leva poucos minutos no maior código, ela cabe no fluxo do pull request.
Mudanças que quebram o grafo de dependências ou a encapsulação são rejeitadas antes de chegar à branch principal.
05 · LIÇÕES
O que ainda não estava resolvido
O balanço posterior traz números de adoção: o monólito principal tinha 37 componentes com pontos de entrada públicos, e o Packwerk cobria cerca de um terço deles. A mudança de arquitetura em andamento é descrita como em grande parte um problema de pessoas.
Ao analisar o grafo, o time encontrou uma dependência muito densa, com cada componente dependendo de mais da metade dos demais, além de muitas dependências circulares. Isso, segundo o texto, impede raciocinar sobre os componentes e evoluí-los separadamente. Para desfazer ciclos, as saídas citadas foram mover responsabilidades ou aplicar inversão de controle.
A abordagem escolhida para o monólito principal seguiu a visão de Domain Driven Design para definir os componentes. A desvantagem reconhecida é que quase nenhum componente tem ainda uma fronteira completa e forte. Insistir em consistência de interface acrescentou regras, que sempre trazem atrito.
Perguntas frequentes
O que é Shopify: monólito modular (Deconstructing the Monolith)?
Arquitetura real em produção. Pontos centrais: fronteiras de componente e checagem estática de dependências.
O que o design de Shopify: monólito modular (Deconstructing the Monolith) cobre?
Ele está organizado nestas seções: Um monólito Rails que cresceu demais, Monólito, microsserviços ou meio-termo, Reorganizar por domínio e isolar dependências, Do grafo de chamadas ao Packwerk e O que ainda não estava resolvido.
Quanto tempo leva Shopify: monólito modular (Deconstructing the Monolith) e qual é o nível?
Leva cerca de 15 minutos para ler e tem nível Pleno.
Agora projete você mesmo
Rode este caso como uma entrevista na escola de System Design: você toma as decisões, o tutor as questiona e você vê a nota do seu desenho.