Pular para o conteúdo

Microservices

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.

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
  • 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.
  • 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.

Exemplo Java/Spring: serviço com database-per-service

Seção intitulada “Exemplo Java/Spring: serviço com database-per-service”
@SpringBootApplication
public 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);
}
@Service
public 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();
}
}
@Component
public 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.

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
  • 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.
  1. 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.

  1. 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.

  1. Por que não se pode usar @Transactional para 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.

  1. 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.