Kirsten Westeinde, engenheira sênior do Shopify, discutiu a evolução do Shopify para um monolito modular no Shopify Unite 2019. Essa evolução incluiu o uso do design payoff line para decidir quando fazer a alteração, como foi alcançada e também por que a arquitetura de microservices foi descartada.
Uma conclusão importante é que os monolitos não são necessariamente uma arquitetura ruim, possuem muitas vantagens, como um único pipeline de testes e implementação. É uma arquitetura particularmente útil no início de um projeto, quando novos recursos devem ser entregues rapidamente. Somente quando o "design payoff line" é cruzado, ou seja, quando o ponto no qual um design ruim pode impedir o desenvolvimento de novos recursos, a arquitetura deve começar a ser melhorada. No caso do Shopify, melhorar a arquitetura não significava uma mudança para microservices, mas uma mudança para um monolito modular. Isso combinou as vantagens de um monolito, um único pipeline de testes e implementação, com as vantagens dos microservices, como modularidade de código e desacoplamento.
Westeinde acredita que uma arquitetura monolítica é um bom ponto de partida para um projeto, afirmando: "Realmente recomendo que novos produtos e novas empresas comecem com um monolito". Kirsten lista algumas das vantagens:
- Um único projeto contendo todo o código;
- Devido a essa única base de código, os testes e as implantações são simples;
- Todos os dados estão disponíveis e não precisam ser transmitidos entre os serviços;
- Um único conjunto de infraestrutura.
Devido a esses benefícios, o Shopify começou como um pequeno monolito em Ruby on Rails, evoluindo para uma base de código extremamente grande. Estando nessa situação, o Shopify começou a se tornar insustentável, dificultando a entrega de novos funcionalidades. Por exemplo, se fosse alterado uma parte do código, o resultado causaria efeitos colaterais não intencionais em outra parte não relacionada ao motivo da alteração, fazendo com que a criação e o teste do aplicativo demorasse muito.
Referindo-se à hipótese de resistência do projeto de Martin Fowler, Westeinde explicou que quando chegou a hora de refatorar a arquitetura, uma vez que o desenvolvimento de recursos foi impedido pelo design ruim, o design payoff line foi cruzado, o que significava que fazia sentido investir recursos para corrigir este problema.
Inicialmente, o Shopify considerou os microservices como uma alternativa de arquitetura mais sustentável, porém, foram descartados devido à complexidade de um sistema distribuído, em vez de querer um monolito mais sustentável:
Percebemos que todas as coisas de que gostávamos em nosso monolito eram resultado do código vivo que era implantado em um único lugar. E todos os problemas que estávamos enfrentando eram resultados diretos da falta dos limites entre funcionalidades distintas em nosso código.
Westeinde explica que o objetivo de design era aumentar a modularidade do sistema, como o que ocorre nos microservices, enquanto era mantido uma única unidade implantável, como um monolito. Para conseguir isso, o Shopify adotou o padrão de um monolito modular, permitindo limites entre o código, mas residindo e sendo implantados no mesmo local. O caminho da migração incluiu:
- Reorganização do código: Inicialmente, o código era organizado como uma aplicação típica do Rails, com as partes de nível superior sendo nomeadas após componentes técnicos, como controllers. Esse modelo foi alterado para ser organizado com base na funcionalidade de negócios, como "faturamento" e "pedidos", facilitando a localização do código;
- Isolando dependências: Cada componente de negócios foi isolado um do outro e disponibilizado para uso por meio de uma API pública. Uma ferramenta chamada Wedge foi desenvolvida internamente, que rastreia o objetivo de cada componente em direção ao isolamento. A ferramenta faz isso criando um gráfico de chamadas e, em seguida, determinando quais chamadas e componentes, estão violando;
- Impondo limites: Uma vez que cada componente tenha alcançado total isolamento, os limites serão aplicados entre estes componentes. A ideia é que haverá um erro de tempo de execução quando o código tentar acessar outro código de um componente no qual não depende explicitamente. Ter dependências declaradas dessa maneira também permitirá que sejam visualizadas em um gráfico de dependência.
Para concluir, Westeinde explica que esse foi um bom exemplo de como a arquitetura pode evoluir com base nas necessidades de negócios:
Uma boa arquitetura de software é uma tarefa em constante evolução, e a solução correta para o aplicativo depende da escala em que estamos operando.
A palestra completa pode ser assistida online e também pode ser encontrada no blog do Shopify.