Slide 1

Slide 1 text

Complexity Hell Técnicas para lidar com microserviços legados Jéssica Bonson LUNCH AND LEARN BACKEND Staff Software Engineer

Slide 2

Slide 2 text

● Graduação / Mestrado em Ciências da Computação ● 10+ anos de experiências em diversas techstacks e projetos OLÁ! Eu sou Jéssica Bonson Staff Engineer @ iFood

Slide 3

Slide 3 text

Tópicos 1. Monolithic Hell 2. Complexity Hell 3. Como lidar com a complexidade? 4. Técnicas para lidar com microserviços legados

Slide 4

Slide 4 text

Monolithic Hell

Slide 5

Slide 5 text

● Monolito é uma boa solução para um produto simples… Arquitetura de Monolito

Slide 6

Slide 6 text

● …mas novas demandas por features e escala vão chegando… Arquitetura de Monolito e isso é bom, quer dizer que o produto está sendo um sucesso!

Slide 7

Slide 7 text

● …e mais demandas… Arquitetura de Monolito o que esse produto fazia mesmo?

Slide 8

Slide 8 text

● Monolito é uma boa solução para um produto simples ● …mas e quando o produto deixa de ser simples? Monolithic Hell

Slide 9

Slide 9 text

● Tentar entender o sistema é intimidador ● Baixa produtividade ○ Testes lentos, filas para deploy, incidentes constantes, testes manuais, conflitos em merges… ● Bugs em produção são comuns ○ E é normal levar dias para achar e consertar eles ● Baixa escalabilidade e desperdício de recursos ○ Uma parte do sistema precisa de mais memória, outra de mais CPU… ● Baixa resiliência e tolerância a falhas ● Sistema com techstack e bibliotecas desatualizadas Como saber se você está em um?

Slide 10

Slide 10 text

● Monolitos ainda são uma boa opção para aplicações simples. ● E é possível atrasar a deterioração do monolito, usando: ○ Boas práticas de desenvolvimento de software ○ Código modular, com responsabilidades bem definidas ○ Boa cobertura de testes automatizados ○ Equilíbrio de foco entre entrega de features e melhorias de débitos técnicos Disclaimers!

Slide 11

Slide 11 text

● À medida que uma aplicação se torna mais complexa, uma arquitetura de microserviços permite mais escalabilidade, resiliência e produtividade. Arquitetura de Microserviços

Slide 12

Slide 12 text

● Mas também existe ‘Monolithic Hell’ para microserviços! Mais Disclaimers!

Slide 13

Slide 13 text

Complexity Hell

Slide 14

Slide 14 text

● Quando se fala em microserviços é comum focar em como construir a arquitetura do zero, dados os requisitos de produto ou a partir de um monolito. ● Mas e se a própria arquitetura de microserviços se torna legada? Complexity Hell

Slide 15

Slide 15 text

● Tentar entender o sistema é intimidador ○ Mas agora você também tem que entender várias bases de código e a relação entre elas ● Baixa produtividade ○ Dependências entre serviços, serviços inchados, responsabilidades espalhadas… ● Bugs em produção são comuns ○ Mas agora eles se espalham entre serviços ● Baixa escalabilidade e desperdício de recursos ○ Uma parte de um serviço precisa escalar com requisições sync, outra com async, outra usa mais CPU… ● Baixa resiliência e tolerância a falhas ○ Requisições síncronas criam gargalos e dependências temporais ● Sistema com techstack e bibliotecas desatualizadas Os sintomas são parecidos, mudam os detalhes

Slide 16

Slide 16 text

● Os mesmos cuidados com monolito também se aplicam para microserviços. ○ modularidade, testes automatizados, boas práticas, não acumular débitos… ● Quanto mais complexa a arquitetura, mais rápido ela se tornará legada. ○ Busque simplicidade! Cuidados

Slide 17

Slide 17 text

Como lidar com a complexidade?

Slide 18

Slide 18 text

● Complexidade se refere mais ao que é difícil manter do que ao que é difícil fazer. ● Tem forte impacto na velocidade de desenvolvimento, na resiliência e na escalabilidade. O que é complexidade?

Slide 19

Slide 19 text

● Requisitos funcionais podem ser implementados de várias formas. ○ Diferentes decisões técnicas acarretam diferentes complexidades. ● Existe pouca relação entre os requisitos funcionais e a escolha de arquitetura. Decisões Técnicas

Slide 20

Slide 20 text

Requisitos funcionais de cadastro de um cliente “Building Microservices”, Sam Newman Exemplo

Slide 21

Slide 21 text

● Todas essas arquiteturas podem atender os requisitos funcionais. Microserviços com Orquestração Microserviços com Coreografia Monolito “Monolito Distribuído”

Slide 22

Slide 22 text

● Se não há relação entre os requisitos funcionais e a arquitetura, então para que ela serve? ● A arquitetura define os requisitos não-funcionais do produto, ou seja, a qualidade do sistema. ○ A decomposição das responsabilidades da aplicação e os relacionamentos entre elas determinam as -ilities. Requisitos Não-Funcionais

Slide 23

Slide 23 text

● Velocidade de desenvolvimento ○ debuggability, maintainability, extensibility, testability, deployability, code quality… ● Confiança ○ availability, reliability, durability, resiliency, fault tolerance, data consistency… ● Escalabilidade ○ modularity, capacity, throughput, performance, operability… ● Segurança ● Custo Requisitos Não-Funcionais

Slide 24

Slide 24 text

● Não existe ‘bala de prata’, toda decisão de arquitetura tem prós e contras. ● Perguntas a serem respondidas: ○ Os ‘contras’ são aceitáveis? ○ Os ‘prós’ são necessários? ■ Cuidado com a otimização prematura! Trade-Offs de Arquitetura

Slide 25

Slide 25 text

● É algo vivo, que evolui à medida que o produto cresce. ○ Não tente criar algo perfeito e eterno ● O que não quer dizer que é para fazer de qualquer jeito! ● Não temos como impedir as mudanças, mas podemos guiá-las e planejar para elas. O que é arquitetura?

Slide 26

Slide 26 text

● Responsabilidades bem-definidas ● Princípio de ‘Information hiding’ Como planejar para mudanças? “Be worried about what happens between the boxes, and be liberal in what happens inside.” Martin Fowler

Slide 27

Slide 27 text

‘Information hiding’ a nível de microserviços Promotions Recommendations Sales Fácil de mudar Difícil de mudar

Slide 28

Slide 28 text

‘Information hiding’ a nível de domínios Promotions Recommendations Sales Fácil de mudar Difícil de mudar Marketing Domain Customer Domain Stock Domain

Slide 29

Slide 29 text

Técnicas para Lidar com Microserviços Legados

Slide 30

Slide 30 text

Por que quebrar um microserviço? ● Microserviços legados tem problemas muito semelhantes a monolitos legados… ○ …mas com algumas complexidades a mais ● Importante ter um mindset de “arquitetura sacrificável” ○ O código precisar ser sacrificado é sinal de sucesso do produto ○ Ainda é importante ter qualidade, para o código se deteriorar mais lentamente ○ Modularidade é essencial ○ Evita otimização prematura

Slide 31

Slide 31 text

Como quebrar um microserviço? ● Motivo? ○ Complexidade Procure por domínios ○ Performance Procure por bottlenecks ○ Time to market Procure por volatilidade ● Trade-off entre benefício e dificuldade da refatoração

Slide 32

Slide 32 text

Como não quebrar um microserviço? ● Não faça refatorações “Big Bang” ○ Além dos riscos ao colocar em produção, também há o risco de nunca colocar em produção. Por isso, faça mudanças incrementais. “Se você fizer uma refatoração Big Bang, a única coisa garantida é o Big Bang.” Martin Fowler

Slide 33

Slide 33 text

Como quebrar por domínio? ● Muitas fontes e literatura explicam como modelar uma arquitetura do zero, a partir das regras de negócio, ou como quebrar e modificar um sistema dado que você sabe onde mexer…

Slide 34

Slide 34 text

Como identificar domínios a partir do código? ● …mas e quando as regras de negócio não foram bem definidas, e o código só foi surgindo? Como entender ele e descobrir onde quebrar as responsabilidades?

Slide 35

Slide 35 text

Como identificar domínios a partir do código? ● Exemplo prático com a aplicação ‘Call Records API’ ○ Github: shorturl.at/ehp03 ○ “Uma API que recebe detalhes de chamadas telefônicas e calcula a conta mensal para um número de telefone”

Slide 36

Slide 36 text

1) Análise das Dependências do Código ● Objetivo: Visualizar mais facilmente quais são os domínios que já existem no código. ● Passos: ○ Entender a estrutura do programa dado só classes, métodos e como eles se comunicam. ○ Considerar apenas código que implementa regras de negócio, e que é chamado por outras partes do código (‘público’). ○ Categorizar as dependências em grupos. ○ Não se prenda a detalhes técnicos da linguagem ou do algoritmo.

Slide 37

Slide 37 text

Call Records API ● ~1000 linhas de código Python (~600 com implementação das regras de negócio) ● ~2h para entender o código

Slide 38

Slide 38 text

Resultado da Etapa 1

Slide 39

Slide 39 text

Resultado da Etapa 1

Slide 40

Slide 40 text

2) Fluxo das Dependências do Código ● Objetivo: Visualizar como os domínios do código interagem, para mapear os contextos que existem no código. ● Passos: ○ Cada componente aparece apenas uma vez ○ Ligue cada componente ao componentes que ele interage ○ Organize o diagrama para os componentes ficarem em ‘clusters’, tentando não deixar que as linhas se sobreponham ■ Se as linhas estão muitos sobrepostas, isso indica que o código não está modular, e é boa ideia refatorá-lo para consertar isso antes de quebrá-lo.

Slide 41

Slide 41 text

Resultado da Etapa 2

Slide 42

Slide 42 text

Resultado da Etapa 2 (caso real)

Slide 43

Slide 43 text

3) Mapear Responsabilidades e Domínios ● Objetivo: Entender como a implementação dos requisitos pode ser quebrada em domínios ● Questões: ○ Os contextos identificados são sobre quais responsabilidades e entidades? ○ Um serviço deve ter uma responsabilidade única e bem-definida. Dado isso, esses contextos deveriam estar nesse serviço, ou em outros? ○ Quais são os requisitos funcionais que esse serviço está implementando? ○ Quais interações externas ocorrem com esses contextos? ■ Quais são as chamadas síncronas e assíncronas que o código recebe e faz? ■ De quais domínios externos essas chamadas fazem parte?

Slide 44

Slide 44 text

Tipos de Refatoração ● Decomposição (‘quebrar’) ○ Strangler Fig Pattern ○ Branch by Abstraction Pattern ● Modificação ○ Parallel Run Pattern ○ Expand-Contract Pattern ● Combinação

Slide 45

Slide 45 text

Strangler Fig Pattern Técnica de Decomposição:

Slide 46

Slide 46 text

‘Strangler Fig Pattern’ aplicado em uma arquitetura síncrona. “Monolith To Microservices”, Sam Newman

Slide 47

Slide 47 text

“Monolith To Microservices”, Sam Newman ‘Strangler Fig Pattern’ aplicado em uma arquitetura assíncrona.

Slide 48

Slide 48 text

Branch by Abstraction Pattern ● Usado como preparação para o Strangler Fig Pattern “Monolith To Microservices”, Sam Newman Técnica de Decomposição:

Slide 49

Slide 49 text

Exemplo de uso de ‘Branch by Abstraction Pattern’ “Monolith To Microservices”, Sam Newman

Slide 50

Slide 50 text

Parallel Run Pattern ● É importante comparar resultados e acompanhar o uso. ● Podem receber as mesmas requisições e só um ser usado, ou funcionar como um canário. Service 1 App Service 1 App Service 2 Service 2 App Técnica de Modificação:

Slide 51

Slide 51 text

Expand-Contract Pattern ● Exemplo: Renomear um campo A para B 1. Adiciona o campo B, sem usar 2. Escrita tanto em A como em B 3. Leitura apenas em B 4. Aguardar/Corrigir bugs 5. Remover campo A Técnica de Modificação:

Slide 52

Slide 52 text

“Arquitetura é o que acontece, não o que é planejado. Se você não participa do processo de implementar a arquitetura na prática, você não é um arquiteto, é um sonhador.” Martin Fowler

Slide 53

Slide 53 text

Referências ● E minha experiência há 10+ anos na carreira

Slide 54

Slide 54 text

Valeu! @jpbonson Slides disponíveis em https://speakerdeck.com/jpbonson/