Microservices
Definição
Seção intitulada “Definição”Um microservice é um serviço com fronteira de processo que:
- é dono exclusivo do seu banco de dados (database-per-service);
- expõe suas capacidades apenas por contrato (API ou eventos), nunca por acesso direto ao seu schema;
- pode ser desenvolvido, testado e deployado independentemente dos demais;
- é operado por um único time.
A autonomia é a propriedade central. Se dois serviços precisam ser deployados juntos, eles são um serviço só disfarçado.
Decomposição por Bounded Context
Seção intitulada “Decomposição por Bounded Context”A pergunta certa não é “quão pequeno”, e sim “onde corta a fronteira”. A resposta vem do domínio: cada Bounded Context do Domain-Driven Design é candidato a serviço. Dentro dele os Agregados definem os limites de consistência transacional local — uma transação ACID nunca cruza a fronteira de um agregado, e muito menos a de um serviço.
graph LR
subgraph Vendas
P[Pedido Agregado]
end
subgraph Estoque
I[Item Agregado]
end
subgraph Pagamento
C[Cobranca Agregado]
end
P -.evento PedidoCriado.-> C
C -.evento PagamentoConfirmado.-> I
Vantagens
Seção intitulada “Vantagens”- Deploy independente: um bug no serviço de pagamento não bloqueia o deploy do catálogo.
- Escala seletiva: escale apenas o serviço sob pressão, não a aplicação inteira.
- Autonomia de time e de stack: cada time evolui no seu ritmo, podendo usar tecnologias distintas.
- Isolamento de falha: com resiliência adequada, a queda de um serviço degrada em vez de derrubar o sistema.
Custos (não subestime)
Seção intitulada “Custos (não subestime)”- Rede: toda colaboração vira chamada remota, sujeita às falácias da computação distribuída.
- Consistência: sem transação ACID distribuída, adota-se consistência eventual e compensação.
- Operação: observabilidade distribuída, tracing, versionamento de contratos, service discovery e deploy de N serviços.
- Cognitivo: raciocinar sobre fluxos que atravessam vários processos é mais difícil do que ler uma stack trace local.
Quando NÃO usar: monólito modular primeiro
Seção intitulada “Quando NÃO usar: monólito modular primeiro”Exemplo Java/Spring: serviço com database-per-service
Seção intitulada “Exemplo Java/Spring: serviço com database-per-service”@SpringBootApplicationpublic class PedidoServiceApplication { public static void main(String[] args) { SpringApplication.run(PedidoServiceApplication.class, args); }}
@Entity@Table(name = "pedidos")public class Pedido {
@Id private UUID id;
@Column(nullable = false) private UUID clienteId;
@Enumerated(EnumType.STRING) private StatusPedido status;
@Column(nullable = false) private BigDecimal total;
protected Pedido() { }
public Pedido(UUID clienteId, BigDecimal total) { this.id = UUID.randomUUID(); this.clienteId = clienteId; this.total = total; this.status = StatusPedido.CRIADO; }
public void confirmarPagamento() { if (status != StatusPedido.CRIADO) { throw new IllegalStateException("Pedido nao esta aguardando pagamento"); } this.status = StatusPedido.PAGO; }
public UUID getId() { return id; }
public StatusPedido getStatus() { return status; }}
enum StatusPedido { CRIADO, PAGO, CANCELADO}
public interface PedidoRepository extends JpaRepository<Pedido, UUID> { List<Pedido> findByClienteId(UUID clienteId);}
@Servicepublic class PedidoAppService {
private final PedidoRepository repository; private final ClienteClient clienteClient;
public PedidoAppService(PedidoRepository repository, ClienteClient clienteClient) { this.repository = repository; this.clienteClient = clienteClient; }
@Transactional public UUID criar(UUID clienteId, BigDecimal total) { clienteClient.validar(clienteId); Pedido pedido = new Pedido(clienteId, total); return repository.save(pedido).getId(); }}
@Componentpublic class ClienteClient {
private final RestClient restClient;
public ClienteClient(RestClient.Builder builder) { this.restClient = builder.baseUrl("http://cliente-service").build(); }
@Retry(name = "clienteService") @CircuitBreaker(name = "clienteService", fallbackMethod = "clienteIndisponivel") public void validar(UUID clienteId) { restClient.get() .uri("/clientes/{id}", clienteId) .retrieve() .toBodilessEntity(); }
private void clienteIndisponivel(UUID clienteId, Throwable ex) { throw new ServicoIndisponivelException("cliente-service indisponivel", ex); }}Note que @Transactional cobre apenas o banco local do serviço de pedidos. A validação do cliente é uma chamada remota, protegida por Retry e Circuit Breaker — ela não participa de nenhuma transação distribuída.
Dos custos nascem os padrões
Seção intitulada “Dos custos nascem os padrões”Cada custo estrutural gera uma família de soluções catalogadas em Microservices Patterns:
| Desafio | Padrão |
|---|---|
| Transação que cruza serviços | SAGA com compensação |
| Publicar evento e persistir atomicamente | Message Outbox |
| Chamada remota que falha ou trava | Retry, Circuit Breaker, Fallback |
| Consulta que junta dados de vários serviços | API Composition |
| Ponto único de entrada e roteamento | API Gateway |
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”- Shared database: dois serviços lendo/escrevendo a mesma tabela. Destrói a autonomia e recria o monólito distribuído.
- Serviços anêmicos e nano-serviços: fragmentar além do bounded context multiplica chamadas de rede sem coesão.
- Chamadas síncronas em cadeia: A chama B que chama C que chama D — a latência soma e a disponibilidade multiplica-se para baixo. Prefira eventos (Event-Driven Architecture).
- Transação distribuída via 2PC: bloqueante e frágil; use SAGA.
- Extrair microservices sem fronteiras claras em vez de amadurecer um monólito modular.
Relações
Seção intitulada “Relações”- Fronteira: Bounded Context, Domain-Driven Design, Agregados.
- Estilo interno de cada serviço: Arquitetura Hexagonal, Ports, Adapters.
- Comunicação: APIs - REST, GraphQL e gRPC, Event-Driven Architecture.
- Padrões: Microservices Patterns, SAGA, Message Outbox, API Composition, Circuit Breaker, Retry, Fallback.
- Leitura assimétrica: CQRS.
- Contexto: Sistemas Distribuídos, Home.
Perguntas de revisão
Seção intitulada “Perguntas de revisão”- Por que “database-per-service” é uma propriedade definidora e não um detalhe?
Resposta
Porque a autonomia — deploy e evolução independentes — só existe se o serviço for dono exclusivo do seu schema. Banco compartilhado acopla serviços pelo modelo de dados, forçando mudanças e deploys coordenados e recriando o monólito distribuído.
- Qual é o conselho central antes de adotar microservices e qual o raciocínio por trás dele?
Resposta
Comece com um monólito modular. Fronteiras de domínio erradas são baratas de corrigir dentro de um processo e caríssimas entre processos. Extraia um serviço só quando ele provar necessidade real de escala ou autonomia independente.
- Por que não se pode usar
@Transactionalpara garantir consistência entre dois microservices?
Resposta
@Transactional opera sobre uma única conexão/banco local. Serviços têm bancos separados; não há transação ACID cruzando processos (2PC é bloqueante e evitado). A consistência entre serviços é obtida por SAGA com compensação e consistência eventual.
- Como os custos estruturais de microservices se traduzem em Microservices Patterns?
Resposta
Transação distribuída → SAGA; publicação confiável de eventos → Message Outbox; falha em chamada remota → Retry/Circuit Breaker/Fallback; consulta agregada entre serviços → API Composition. Cada padrão é a resposta a um custo inerente à fronteira de processo.