EstevezAlvarez
Microsserviços Arquitetura Python

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.

Monólito Módulo Usuários Módulo Pedidos Módulo Pagamentos Módulo Catálogo um único banco de dados → Microsserviços Serviço Usuários DB: users_db Serviço Pedidos DB: orders_db Serviço Pagamentos DB: payments_db Serviço Catálogo DB: catalog_db API Gateway
No monólito, todos os módulos compartilham o banco de dados e são implantados juntos. Em microsserviços, cada serviço é dono dos seus dados e é implantado de forma independente.

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.

Serviço Pedidos GET /users/{id} → precisa de: name, email, address Contrato (OpenAPI) GET /users/{user_id} → 200 UserPublicDTO Serviço Usuários expõe: name, email, address (DTO público) oculta: password etc.
O contrato da API define exatamente o que cada serviço expõe. O Serviço de Pedidos só acessa os dados que o Serviço de Usuários decide publicar no seu DTO — nunca o banco diretamente.

Comunicação entre serviços

Há dois padrões principais:

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

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.