Pular para o conteúdo

Montar um ambiente local de microsserviços

Levantar um microsserviço em local é fácil: di-lo o seu README. O problema começa quando o fluxo que queres testar passa por quatro, e cada um chama outros dois que não são teus.

Esta página não é um tutorial de uma ferramenta concreta: é que peças fazem falta, porque a parte difícil não é a que parece, e como se resolve cada uma — com o Aseptic no fim, porque é o que este site documenta, mas os quatro problemas são os mesmos uses o que usares.

Um ambiente local que sirva para alguma coisa precisa de resolver quatro coisas. Nesta ordem de dificuldade real, que quase nunca é a ordem por que se abordam:

  1. Infraestrutura comum. Kafka, Redis, PostgreSQL, o que o teu sistema usar. É a parte mais visível e a mais resolvida: contentores.
  2. Arranque de cada serviço. O seu comando, o seu perfil, as suas variáveis, a sua porta. Trabalho repetitivo, mas mecânico.
  3. Comunicação entre serviços. Para onde chama cada um. É aqui que se vai a manhã, e é disto que quase ninguém fala.
  4. Dados. Um esquema criado e algo lá dentro com que testar.

Um docker-compose.yml com as peças comuns e docker compose up. É o que toda a gente faz e está bem: os contentores são exatamente a ferramenta adequada para isto.

Dois detalhes que mordem mais tarde:

  • As portas colidem. Cada equipa declara a 5432 e a 6379. Assim que tens dois projetos abertos, ou levantas e baixas, começam os conflitos.
  • A infraestrutura partilha-se entre projetos, mas os dados não. Reutilizar o mesmo Postgres para dois sistemas diferentes acaba num esquema que ninguém sabe de quem é.

Cada serviço tem a sua forma: ./mvnw quarkus:dev, ./gradlew bootRun -Dspring.profiles.active=local, npm start, um binário. Com perfis, variáveis e flags diferentes.

Isto é o que costuma acabar escrito num LOCAL_SETUP.md de quarenta passos: funciona, mas envelhece mal. Ninguém atualiza o documento quando muda uma flag, e o seguinte que chega descobre o desfasamento à força de erros.

Um detalhe que decide muito mais do que parece: nem todos os serviços querem correr da mesma maneira. Aquele em que estás a mexer queres nativo, com hot reload e com o depurador ligado. Os outros três só têm de estar de pé: em contentor estão bem, isolados e sem te sujarem a máquina.

Aqui está o trabalho a sério, e é o que nenhuma ferramenta de contentores te resolve.

O teu serviço bff chama o orders. Em produção esse URL vem da descoberta de serviços ou do ingress. Em local, alguém tem de decidir para onde aponta, e as opções não são equivalentes:

  • Para o serviço levantado na tua máquina. É o que queres quando estás a mudar os dois lados. Mas então tens de o levantar também, com toda a sua cauda de dependências atrás.
  • Para o ambiente partilhado de desenvolvimento. Poupa-te levantá-lo, em troca de ligação à sua rede, de dados que mudam debaixo dos teus pés e de que se esse ambiente estiver em baixo, tu também.
  • Para um mock. Perfeito quando a resposta desse serviço te é indiferente, ou quando nem sequer lhe consegues chegar. Em troca de manter o stub.

O caro não é escolher: é aplicar a escolha. Cada opção significa mudar um URL na configuração de um repositório que não é teu, lembrares-te de não o comitares, e desfazê-lo quando quiseres testar outra coisa. Multiplica por serviços e por vezes ao dia.

E há uma armadilha: o URL correto depende de onde corre quem chama. Um serviço nativo chega a um contentor por localhost:porta; um contentor que chama outro contentor, pelo nome do serviço; e um contentor que chama algo nativo da tua máquina precisa do nome especial do host. A mesma dependência, três URLs diferentes consoante como esteja a correr cada extremo.

Esquema criado, migrações passadas e uns dados mínimos. Costuma resolver-se com as migrações da própria aplicação mais um script de semente.

A regra que convém não quebrar: o ambiente local não toca em bases de dados partilhadas. Uma migração lançada sem querer contra o ambiente de desenvolvimento comum é uma tarde perdida para toda a equipa.

Abordagem Boa para Onde fica curta
docker-compose de tudo Infraestrutura; um sistema pequeno e estável Construir a imagem do teu serviço a cada mudança; sem hot reload nem depurador cómodos; a comunicação continua a ser configuração à mão
Scripts + LOCAL_SETUP.md Começar depressa Envelhece: ninguém o atualiza e falha de forma diferente em cada máquina
Testcontainers Testes de integração a sério O ambiente nasce e morre com o teste; não é um sítio onde trabalhar
Kubernetes local (Kind, minikube) Parecer-se com produção Muita máquina e muito ciclo de build para o dia a dia; o ciclo de mudança alonga-se
Orquestrador local O ciclo diário com vários serviços É preciso descrever cada serviço uma vez

Nenhum é parvo: resolvem coisas diferentes e muitas vezes convivem. O que nenhum dos quatro primeiros faz é decidir por dependência para onde chama cada serviço e aplicá-lo sem tocar nos repositórios.

O Aseptic é um orquestrador de ambiente de trabalho que trata das quatro peças, e trata a terceira como o problema principal que é.

  1. Descreves cada serviço uma vez. Aponta para a pasta do repositório e a sua tecnologia, as suas portas, a infraestrutura de que precisa e a quem chama são autodetetadas. Revês e já está.
  2. Agrupas num cenário os serviços de um fluxo — «Checkout», «Registo de cliente» — e levantam-se juntos, por ordem e esperando que cada um esteja saudável.
  3. Por cada dependência escolhes local, nuvem ou mock, e o motor reescreve o URL ao arrancar injetando-o como configuração —flags -D ou variáveis de ambiente—, com a perspetiva correta consoante onde corre cada extremo. Os teus repositórios não se tocam.
  4. A infraestrutura comum (Kafka, Redis, PostgreSQL) é gerida pela app em Docker, com remapeamento automático de portas quando colidem.
  5. Cada serviço, nativo ou em contentor, à tua escolha: nativo aquele em que estás a mexer, em contentor os de fundo.

E o que montas partilha-se: um cenário exporta-se para um ficheiro autocontido, por isso quem chega de novo clona, importa e arranca em vez de ler o LOCAL_SETUP.md.