Microsserviços: arquitetura escalável e a importância de modelar bem
Dividir uma aplicação em microsserviços é simples. Dividi-la bem exige entender o que é um domínio, por que cada serviço deve ser dono dos seus dados e quando não dividir é a decisão correta.
Do monólito ao microsserviço
Um monólito não é ruim. Para equipes pequenas e produtos em estágio inicial, um monólito bem estruturado é mais barato de manter do que uma rede de serviços. O problema surge quando o sistema cresce: uma mudança no módulo de pagamentos exige reimplantar toda a aplicação; escalar o catálogo de produtos significa escalar também usuários e pedidos, mesmo sem necessidade; a equipe de frontend não consegue avançar porque o backend acumulou dívida técnica em um único repositório gigante.
Microsserviços resolvem esse problema específico — não todos os problemas. A decisão de migrar faz sentido quando os gargalos de implantação, escalabilidade ou autonomia das equipes justificam a complexidade operacional adicional.
Por que o modelo de dados é a decisão mais importante
Muitas equipes começam dividindo o código em serviços, mas mantêm um banco de dados compartilhado. É o pior dos dois mundos: você tem a complexidade de rede dos microsserviços sem independência de implantação.
A regra é simples, mas difícil de respeitar: cada serviço é o único dono dos seus dados. Nenhum outro serviço pode ler ou escrever diretamente no seu banco. Se o Serviço de Pedidos precisa do nome do cliente, ele o solicita ao Serviço de Usuários por uma API — não faz um JOIN entre serviços.
Isso tem consequências para o projeto. Dados que antes estavam em uma tabela agora precisam existir em vários serviços, nos respectivos contextos. O usuário do serviço de pagamentos não tem os mesmos atributos do usuário do CRM: é o mesmo conceito de negócio, mas com modelos diferentes. Essa duplicação é intencional e necessária.
Fronteiras de domínio (Bounded Contexts)
O conceito de Bounded Context do Domain-Driven Design é o guia mais útil para decidir onde dividir. Um bounded context é uma área do sistema na qual um modelo de dados tem um significado preciso e consistente. “Produto” no catálogo inclui descrição, imagens e variantes. “Produto” no serviço de estoque é apenas um SKU com uma quantidade. São o mesmo objeto do mundo real, mas modelos diferentes adaptados ao seu contexto.
Quando as fronteiras são mal definidas, os serviços começam a se acoplar: o serviço A depende de uma estrutura específica do serviço B para funcionar; uma mudança em B quebra A sem que ninguém antecipe. Essa dependência oculta no modelo de dados é a fonte mais comum de fragilidade em arquiteturas de microsserviços.
Comunicação entre serviços
Há dois padrões principais:
- Síncrono (REST / gRPC) — o serviço A chama B e aguarda a resposta. É simples de compreender, mas introduz acoplamento temporal: se B estiver indisponível, A falha. Adequado para leituras e situações nas quais o resultado imediato é necessário.
- Assíncrono (mensageria) — A publica um evento em uma fila (Kafka, RabbitMQ, SQS) e continua sem esperar. B consome o evento quando puder. Oferece maior resiliência e desacoplamento, mas a consistência eventual exige um projeto explícito.
Um exemplo concreto com FastAPI para o serviço de usuários:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
# DTO público — solo lo que otros servicios pueden ver
class UserPublicDTO(BaseModel):
user_id: str
name: str
email: str
shipping_address: str | None = None
# Datos internos (nunca expuestos directamente)
_users_db: dict[str, dict] = {
"u1": {
"name": "Ana García",
"email": "ana@example.com",
"password_hash": "...", # nunca sale
"shipping_address": "Calle Mayor 1",
"internal_score": 98, # nunca sale
}
}
@app.get("/users/{user_id}", response_model=UserPublicDTO)
def get_user_public(user_id: str):
user = _users_db.get(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return UserPublicDTO(
user_id=user_id,
name=user["name"],
email=user["email"],
shipping_address=user.get("shipping_address"),
)
O DTO atua como uma fronteira explícita. Os campos internos (password_hash, internal_score) nunca saem do serviço. Se você adicionar novos campos internos, o contrato externo não muda e nenhum consumidor é afetado.
Erros comuns
- Microsserviços divididos por função técnica, não por domínio — “serviço de banco de dados”, “serviço de validação”. Não resolvem o acoplamento, apenas o deslocam.
- Banco de dados compartilhado — o antipadrão mais comum e o mais caro de reverter. Se dois serviços escrevem na mesma tabela, na prática são um monólito disfarçado.
- Decomposição prematura — dividir antes de compreender bem o domínio. Fronteiras mal definidas no início custam caro para mudar quando o sistema já tem tráfego real.
- Ignorar a consistência eventual — em sistemas distribuídos, a consistência imediata entre serviços tem um custo alto. Projetar com consistência eventual desde o início simplifica muitos casos.
Quando não usar microsserviços?
Se a equipe tiver menos de 5–8 pessoas, se o produto ainda não tiver product-market fit ou se não houver experiência operacional com sistemas distribuídos, um monólito modular bem estruturado quase sempre será a melhor opção. Microsserviços resolvem problemas de escala — de equipe, carga ou domínio. Antes desses problemas existirem, eles acrescentam complexidade sem benefício.