PythonBrasil 11
Matando um Monolítico Django: de Pluggable Apps aos Microservices
Bernardo Fontes
São José dos Campos/SP
09 de Novembro de 2015
## Nosso Cenário
### Sistema para um empresa de **medicina do trabalho** realizar atendimentos de **medicina ocupacional** por todo o Brasil e fornecer uma **análise inteligente** sobre o perfil dos colaboradores de uma empresa.
Maaaaaaas...
Tudo começou só com uma filinha:
Hoje em dia...
## Histórico do Projeto
- ### + de **5 anos**
- ### + de **10 devs** passaram
- ### Python e **Django 1.4**
- ### Hoje: 4 devs e **únicos Pythonistas**
- ### Empresa com **12 devs**
## Histórico do Projeto
- ### Em **Outubro de 2014**
- ### 3 devs
- ### 1 saindo em Dezembro
- ### Django com **uma única aplicação**
- ### 3k LOC de **models.py** (Banco de Dados)
- ### ~3k LOC de **views.py** (Controllers)
- ### ~2,5k LOC de **services.py**
## Menos de 50% de coverage
![No tests](images/no_test.jpg)
## Infra do Projeto
- ### 9 filials == 9 máquinas
- ### 1 ambiente de clientes
- ### 1 única máquina do banco
- ### **11 máquinas**
## $ fab all_hosts deploy
![XKCD](images/deploying.png)
## Cliente
- ### **Novos módulos** no sistema
- ### **Melhorias** nos antigos
- ### Correção de **bugs**
![Work](images/working.png)
### Estudamos
![Library](images/library.jpg)
Estudamos
## Microservices
Um estilo arquitetural para o desenvolvimento de **serviços enxutos** em que cada um possa ser **executado em um processo próprio** e se comunicando através de mecanismos de fácil implementação como o protocolo HTTP. Esses serviços são **construídos focando capacidades de negócio** e devem poder ser **deployados independentemente** através de um processo automatizado. A **necessidade de gestão centralizada deve ser mínima** para os serviços visando viabilizar a independência entre eles.
## Componentes por Serviços
- ### Componentes: unidades de software **independentes**
- ### Serviços: **componentes externos** com comunicação por API
## Foco em Capacidades do Negócio
- ### Serviços limitados ao **contexto do problema**
- ### Equipe precisa saber só do **contexto específico** e não mais do todo
## Pensamento em ~~Projeto~~ Produto
- ### Fim da **Lei de Conway**
- ### Domínio menor == entendimento mais simples
- ### Entendimento mais simples == equipe responsável por **todo o serviço**
- ### Deploy independentemente
## Smart Endpoints & Dumb Pipes
- ### Foco em **coesão** e **desacoplamento**
- ### Chamadas por métodos em memória viram **chamadas ao serviço**
- ### HTTP >> **síncrono**
- ### Mensageria >> **assíncrono**
## Governança Descentralizada
- ### **Decisões específicas** para o microservice
- ### **Right tool** for the job
- ### **Independência** no processo de desenvolvimento da equipe
## Armazenamento de Dados Descentralizados
- ### Dados sendo visualizados de **acordo com o contexto**
- ### **Perde-se** a gestão de transações automática
## Design orientado a Falhas
- ### **Falhas de comunicação** são sempre reais
- ### A comunicação deve ser sempre **desenvolvida pensando o cenário de falha**
- ### Código **mais estável**
## Conclusões do que poderíamos
- ### Desenvolver em **outras tecnologias**
- ### Ter rotinas de **deploy mais simples**
- ### Envolver **mais devs** no projeto
- ### Entregar **mais rápido**
- ### Gerar software de **maior qualidade**
- ### Focar em entregar **mais valor** para o cliente
## Nossa reação
![Mind](images/mind.gif)
## E começamos...
- ![Dog](images/no_Idea.png)
## Trade-offs que Encontramos
- ### Aumento da **complexidade operacional**
- ### Ambiente de dev mais burocrático (resolvemos com o Docker)
- ### Diferentes processos de deploy
- ### Problemas para garantir a **consistência dos dados**
- ### Todos os overheads de **comunicação em sistemas distribuídos**
## Killer Problems
- ### Nossas regras de negócios estavam **completamente acopladas** entre os módulos services.py, models.py e views.py
- ### A **modelagem acoplada** do nosso banco de dados
- ### Impossível de mudar
- ### Todos esses problemas foram refletidos pros novos serviços
![DatabaseFull](images/whole_db.jpg)
![Database](images/database.jpg)
## Solução
- ![Legacy](images/giphy.gif)
## *"É preciso **tirar o legado da cabeça**"*
- Isaac Souza
Então estudamos mais...
## Focos no Mindset
- ### Definição dos **Bounded Context** dos serviços
- ### Utilização de **Domain Objects** na implementação
- ### Criação de **Use Cases** orquestrando a troca de mensagem entre os objetos
- ### Implementação de **Anti-Corruption Layer** nos serviços
## Implementação em Etapas
## 1º Caso: Isolar lógica de um domínio (sem microservice)
- Objetivo: **aprender a isolar a lógica**
- Modelos de dados continuaram no legado
- Nova aplicação interna somente lidando com um tipo de atendimento
- Já existiam testes
- Migramos os controllers, services, rotas e testes isolando a app
- Ponto de acoplamento: import dos models do legado
## 2º Caso: Nova funcionalidade com baixo acoplamento (sem microservice)
- Objetivo: aprender a **organizar o código** para ser **consumido por clientes externos**
- Modelos, controllers, services e urls próprios em nova app
- Utilização de eventos para não mexer no meio legado
- Integração através de import em listeners no legado
- Ponto de acoplamento: único import para o model legado de Paciente
## 3º Caso: Autenticação como Microservice
- Objetivo: validar **participação de outros devs** no projeto
- App SSO em Sinatra (Ruby)
- **Todo** o ambiente isolado
- No legado mudamos a fina camada autenticação
## 4º Caso: Nova funcionalidade agnóstica (microservice interno)
- Objetivo: ser possível **deployar isoladamente** no futuro
- App plugável do Django
- Criação de UC para encapsular orquestração de domínio
- Integração através de disparo de mensagens via RabbitMq
- Modelos sem semântica alguma do domínio do legado
- Banco de dados próprio (máquina própria pro banco)
## 5º Caso: Nova Aplicação em Microservice
- Objetivo: implementar **microservices por completo**
- App Flask de ambiente isolado
- Utilização de comunicação por RabbitMq **e** API Rest
- Tivemos que desenvolver uma API para expor o legado
## 6º Caso (atual): Kill the Legacy!
- Guideline de arquitetura 1: ter o **domínio isolado** em libs
- Guideline de arquitetura 2: **atrasar** decisões
## Conclusões
- ### Tem **muitos** trade-offs para serem pensados
- ### Não usamos microservices para tudo
- ### Foco em **modularização**
- ### Diminuímos o **tempo de entrega**
- ### Diminuímos a **barreira de entrada** no projeto
## Conclusões
- ### Aumentamos o **nível técnico** da equipe
- ### **Monitoramento efetivo** é muito importante
- ### **Logs** são nossos melhores amigos
- ### Qualquer operação feita 2 vezes, deve ser automatizada