Arquitetura de microsserviços é uma abordagem de design que divide grandes aplicações em pequenos serviços independentes. Netflix, Amazon, Uber e muitas outras empresas adotam esse padrão, tornando-o a principal abordagem no desenvolvimento cloud native moderno. Este artigo explica sistematicamente desde conceitos básicos até padrões de implementação.
Comparação entre Monolito e Microsserviços
Arquitetura Monolítica
flowchart TB
subgraph Mono["Arquitetura Monolítica - Aplicação Única"]
subgraph Modules["Módulos de Funcionalidade"]
M1["Gerenciamento de Usuários"]
M2["Gerenciamento de Produtos"]
M3["Gerenciamento de Pedidos"]
M4["Processamento de Pagamentos"]
end
DB["Banco de Dados Compartilhado"]
end
Modules --> DB
Note["Unidade única de deploy"]
Arquitetura de Microsserviços
flowchart TB
Gateway["API Gateway / Service Mesh"]
subgraph UserSvc["Serviço de Usuários"]
US["Serviço de<br/>Usuários"]
UDB["Users DB"]
US --> UDB
end
subgraph ProductSvc["Serviço de Produtos"]
PS["Serviço de<br/>Produtos"]
PDB["Products DB"]
PS --> PDB
end
subgraph OrderSvc["Serviço de Pedidos"]
OS["Serviço de<br/>Pedidos"]
ODB["Orders DB"]
OS --> ODB
end
subgraph PaymentSvc["Serviço de Pagamentos"]
PaS["Serviço de<br/>Pagamentos"]
PaDB["Payments DB"]
PaS --> PaDB
end
Gateway --> UserSvc
Gateway --> ProductSvc
Gateway --> OrderSvc
Gateway --> PaymentSvc
Tabela Comparativa
| Aspecto | Monolito | Microsserviços |
|---|---|---|
| Deploy | Tudo junto | Independente por serviço |
| Escalabilidade | Escala tudo | Apenas serviços necessários |
| Stack tecnológico | Precisa ser unificado | Escolha por serviço |
| Impacto de falhas | Afeta todo o sistema | Limitado ao serviço afetado |
| Equipe de desenvolvimento | Todos conhecem o todo | Equipes dedicadas por serviço |
| Complexidade | Concentrada no código | Distribuída em infra/operações |
Princípios de Design de Microsserviços
1. Princípio da Responsabilidade Única (Single Responsibility)
Cada serviço se concentra em uma única funcionalidade de negócio.
// Exemplo ruim: Múltiplas responsabilidades em um serviço
class UserOrderService {
createUser() { /* ... */ }
updateUser() { /* ... */ }
createOrder() { /* ... */ } // Domínio diferente
processPayment() { /* ... */ } // Domínio diferente
}
// Exemplo bom: Serviços separados por responsabilidade
// user-service
class UserService {
createUser() { /* ... */ }
updateUser() { /* ... */ }
getUserById() { /* ... */ }
}
// order-service
class OrderService {
createOrder() { /* ... */ }
getOrdersByUser() { /* ... */ }
}
2. Independência de Dados (Database per Service)
Cada serviço possui seu próprio armazenamento de dados e não acessa diretamente o banco de dados de outros serviços.
flowchart TB
subgraph Anti["Anti-padrão"]
A1["Serviço A"]
B1["Serviço B"]
SharedDB["DB Compartilhado"]
A1 -->|Acesso direto| SharedDB
B1 -->|Acesso direto| SharedDB
end
subgraph Good["Padrão Recomendado"]
A2["Serviço A"]
B2["Serviço B"]
DBA["DB A"]
DBB["DB B"]
A2 --> DBA
B2 --> DBB
A2 <-->|Via API| B2
end
style Anti fill:#fee,stroke:#f00
style Good fill:#efe,stroke:#0f0
3. Acoplamento Fraco (Loose Coupling)
Minimizar dependências entre serviços, comunicando apenas através de interfaces.
// Exemplo de comunicação entre order-service e user-service
interface UserClient {
getUserById(userId: string): Promise<User>;
}
class OrderService {
constructor(private userClient: UserClient) {}
async createOrder(userId: string, items: OrderItem[]): Promise<Order> {
// Comunicação com outro serviço via interface
const user = await this.userClient.getUserById(userId);
if (!user.isActive) {
throw new Error('User is not active');
}
return this.orderRepository.create({
userId,
items,
createdAt: new Date(),
});
}
}
4. Alta Coesão (High Cohesion)
Funcionalidades relacionadas são agrupadas no mesmo serviço.
flowchart TB
subgraph ECommerce["Domínio E-Commerce"]
subgraph UserCtx["Contexto de Usuário"]
U1["Usuário"]
U2["Perfil"]
U3["Endereço"]
U4["Autenticação"]
end
subgraph OrderCtx["Contexto de Pedido"]
O1["Pedido"]
O2["Item do Pedido"]
O3["Pagamento"]
O4["Envio"]
end
subgraph InvCtx["Contexto de Inventário"]
I1["Produto"]
I2["Estoque"]
I3["Armazém"]
I4["Fornecedor"]
end
end
Padrões de Comunicação entre Serviços
1. Comunicação Síncrona (REST / gRPC)
Usada quando resposta imediata é necessária.
// Comunicação síncrona via REST API
class ProductClient {
private baseUrl = 'http://product-service:8080';
async getProduct(productId: string): Promise<Product> {
const response = await fetch(`${this.baseUrl}/products/${productId}`, {
headers: {
'Content-Type': 'application/json',
'X-Request-ID': generateRequestId(), // Para tracing
},
signal: AbortSignal.timeout(5000), // Configuração de timeout
});
if (!response.ok) {
throw new ProductServiceError(response.status);
}
return response.json();
}
}
// Comunicação síncrona via gRPC (protocol buffers)
syntax = "proto3";
service ProductService {
rpc GetProduct(GetProductRequest) returns (Product);
rpc ListProducts(ListProductsRequest) returns (stream Product);
}
message GetProductRequest {
string product_id = 1;
}
message Product {
string id = 1;
string name = 2;
int32 price = 3;
int32 stock = 4;
}
2. Comunicação Assíncrona (Fila de Mensagens)
Usada quando consistência eventual é aceitável ou quando separação de processamento é necessária.
// Arquitetura orientada a eventos
interface OrderCreatedEvent {
eventType: 'ORDER_CREATED';
orderId: string;
userId: string;
items: OrderItem[];
totalAmount: number;
timestamp: Date;
}
// order-service: Publicação de evento
class OrderService {
async createOrder(order: CreateOrderDto): Promise<Order> {
const created = await this.orderRepository.create(order);
// Publicar evento (outros serviços assinam)
await this.eventBus.publish<OrderCreatedEvent>({
eventType: 'ORDER_CREATED',
orderId: created.id,
userId: created.userId,
items: created.items,
totalAmount: created.totalAmount,
timestamp: new Date(),
});
return created;
}
}
// inventory-service: Assinatura de evento
class InventoryEventHandler {
@Subscribe('ORDER_CREATED')
async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
for (const item of event.items) {
await this.inventoryService.decrementStock(item.productId, item.quantity);
}
}
}
Critérios de Seleção de Padrão de Comunicação
| Padrão | Caso de Uso | Características |
|---|---|---|
| REST | Operações CRUD, APIs simples | Amplamente difundido, fácil debug |
| gRPC | Alta performance, tipagem forte | Rápido, suporte a streaming |
| Fila de Mensagens | Processamento assíncrono, orientado a eventos | Acoplamento fraco, escalabilidade |
| GraphQL | Obtenção de dados dirigida pelo cliente | Queries flexíveis, evita over-fetching |
Padrão API Gateway
Fornece um ponto de entrada único entre clientes e microsserviços.
flowchart TB
Client["Cliente"] --> Gateway
subgraph Gateway["API Gateway"]
G1["Autenticação/Autorização"]
G2["Rate Limiting"]
G3["Roteamento de Requisições"]
G4["Agregação de Respostas"]
G5["Conversão de Protocolo"]
end
Gateway --> US["User Service"]
Gateway --> OS["Order Service"]
Gateway --> PS["Product Service"]
// Exemplo de configuração de roteamento no API Gateway (Express + http-proxy-middleware)
import express from 'express';
import { createProxyMiddleware } from 'http-proxy-middleware';
const app = express();
// Middleware de autenticação
app.use(authMiddleware);
// Roteamento por serviço
app.use('/api/users', createProxyMiddleware({
target: 'http://user-service:8080',
changeOrigin: true,
pathRewrite: { '^/api/users': '' },
}));
app.use('/api/orders', createProxyMiddleware({
target: 'http://order-service:8080',
changeOrigin: true,
pathRewrite: { '^/api/orders': '' },
}));
app.use('/api/products', createProxyMiddleware({
target: 'http://product-service:8080',
changeOrigin: true,
pathRewrite: { '^/api/products': '' },
}));
Padrões de Recuperação de Falhas
1. Circuit Breaker
Interrompe temporariamente chamadas para serviços com falhas.
enum CircuitState {
CLOSED, // Operação normal
OPEN, // Interrompido
HALF_OPEN, // Verificando recuperação
}
class CircuitBreaker {
private state = CircuitState.CLOSED;
private failureCount = 0;
private lastFailureTime: Date | null = null;
private readonly failureThreshold = 5;
private readonly resetTimeout = 30000; // 30 segundos
async call<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === CircuitState.OPEN) {
if (this.shouldAttemptReset()) {
this.state = CircuitState.HALF_OPEN;
} else {
throw new CircuitBreakerOpenError();
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess(): void {
this.failureCount = 0;
this.state = CircuitState.CLOSED;
}
private onFailure(): void {
this.failureCount++;
this.lastFailureTime = new Date();
if (this.failureCount >= this.failureThreshold) {
this.state = CircuitState.OPEN;
}
}
private shouldAttemptReset(): boolean {
return Date.now() - (this.lastFailureTime?.getTime() ?? 0) > this.resetTimeout;
}
}
2. Padrão de Retry
Tenta novamente com backoff exponencial para falhas temporárias.
async function withRetry<T>(
fn: () => Promise<T>,
options: {
maxRetries: number;
baseDelay: number;
maxDelay: number;
}
): Promise<T> {
let lastError: Error;
for (let attempt = 0; attempt <= options.maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (attempt === options.maxRetries) break;
// Backoff exponencial + jitter
const delay = Math.min(
options.baseDelay * Math.pow(2, attempt) + Math.random() * 1000,
options.maxDelay
);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError!;
}
// Exemplo de uso
const user = await withRetry(
() => userClient.getUserById(userId),
{ maxRetries: 3, baseDelay: 1000, maxDelay: 10000 }
);
3. Padrão Bulkhead
Isola recursos para limitar o escopo de impacto de falhas.
// Isolamento de pools de threads/conexões
const pools = {
userService: new ConnectionPool({ maxConnections: 10 }),
orderService: new ConnectionPool({ maxConnections: 20 }),
paymentService: new ConnectionPool({ maxConnections: 5 }),
};
// Mesmo com sobrecarga em um serviço, outros não são afetados
Transações Distribuídas
Padrão Saga
Implementa transações que abrangem múltiplos serviços como uma série de transações locais.
sequenceDiagram
participant OS as Order Service
participant IS as Inventory Service
participant PS as Payment Service
Note over OS,PS: Fluxo Normal
OS->>IS: 1. Criar Pedido
IS->>PS: 2. Reservar Estoque
PS->>PS: 3. Processar Pagamento
PS-->>IS: Sucesso
IS-->>OS: 4. Tudo OK
Note over OS,PS: Fluxo de Compensação (Falha no Pagamento)
PS--xPS: Falha no Pagamento
PS-->>IS: Reverter Estoque
IS-->>OS: Cancelar Pedido
// Orquestrador Saga
class OrderSaga {
async execute(orderData: CreateOrderData): Promise<Order> {
const sagaLog: SagaStep[] = [];
try {
// Passo 1: Criar pedido
const order = await this.orderService.create(orderData);
sagaLog.push({ service: 'order', action: 'create', data: order });
// Passo 2: Reservar estoque
await this.inventoryService.reserve(order.items);
sagaLog.push({ service: 'inventory', action: 'reserve', data: order.items });
// Passo 3: Processar pagamento
await this.paymentService.process(order.id, order.totalAmount);
sagaLog.push({ service: 'payment', action: 'process', data: order.id });
// Passo 4: Confirmar pedido
await this.orderService.confirm(order.id);
return order;
} catch (error) {
// Transação de compensação (executar em ordem reversa)
await this.compensate(sagaLog);
throw error;
}
}
private async compensate(sagaLog: SagaStep[]): Promise<void> {
for (const step of sagaLog.reverse()) {
switch (step.service) {
case 'inventory':
await this.inventoryService.release(step.data);
break;
case 'order':
await this.orderService.cancel(step.data.id);
break;
}
}
}
}
Observabilidade
Os 3 Pilares
flowchart TB
subgraph Observability["Observabilidade - Os 3 Pilares"]
subgraph Logs["Logs"]
L1["Registro de eventos<br/>da aplicação"]
L2["ELK Stack / Loki"]
end
subgraph Metrics["Métricas"]
M1["Estado do sistema<br/>expresso em números"]
M2["Prometheus / Grafana"]
end
subgraph Traces["Traces"]
T1["Rastreamento do fluxo<br/>de requisições"]
T2["Jaeger / Zipkin"]
end
end
Tracing Distribuído
// Tracing distribuído com OpenTelemetry
import { trace, context, SpanKind } from '@opentelemetry/api';
const tracer = trace.getTracer('order-service');
async function createOrder(req: Request): Promise<Order> {
return tracer.startActiveSpan(
'createOrder',
{ kind: SpanKind.SERVER },
async (span) => {
try {
span.setAttribute('user.id', req.userId);
// Span filho: Validação de usuário
const user = await tracer.startActiveSpan('validateUser', async (childSpan) => {
const result = await userClient.getUser(req.userId);
childSpan.end();
return result;
});
// Span filho: Salvar pedido
const order = await tracer.startActiveSpan('saveOrder', async (childSpan) => {
const result = await orderRepository.save(req.orderData);
childSpan.setAttribute('order.id', result.id);
childSpan.end();
return result;
});
span.setStatus({ code: SpanStatusCode.OK });
return order;
} catch (error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
throw error;
} finally {
span.end();
}
}
);
}
Critérios de Decisão para Adotar Microsserviços
Casos Apropriados
- Equipe com mais de 10 pessoas, necessitando desenvolvimento independente
- Diferentes partes com diferentes requisitos de escalabilidade
- Necessidade de diversidade de stack tecnológico
- Isolamento de falhas é importante
Casos a Evitar
- Equipes pequenas (cerca de 3-5 pessoas)
- Fase inicial com entendimento insuficiente do domínio
- Capacidade operacional (monitoramento, CI/CD) insuficiente
- Aplicações CRUD simples
Importante: A abordagem “comece com monolito e divida conforme necessário” é recomendada na maioria dos casos.
Resumo
Arquitetura de microsserviços traz grandes benefícios quando implementada corretamente, mas também traz complexidade.
Princípios de Design
- Responsabilidade única: 1 serviço = 1 funcionalidade de negócio
- Independência de dados: DB separado por serviço
- Acoplamento fraco: Comunicação via interfaces
- Alta coesão: Funcionalidades relacionadas no mesmo serviço
Padrões Essenciais
- API Gateway: Ponto de entrada único
- Circuit Breaker: Prevenção de propagação de falhas
- Saga: Gerenciamento de transações distribuídas
- Tracing Distribuído: Rastreamento de requisições
A chave para microsserviços de sucesso está na definição adequada de limites e na construção de uma base operacional robusta.