Introdução à Arquitetura de Microsserviços - Princípios de Design e Padrões de Implementação

20 min leitura | 2025.12.02

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

AspectoMonolitoMicrosserviços
DeployTudo juntoIndependente por serviço
EscalabilidadeEscala tudoApenas serviços necessários
Stack tecnológicoPrecisa ser unificadoEscolha por serviço
Impacto de falhasAfeta todo o sistemaLimitado ao serviço afetado
Equipe de desenvolvimentoTodos conhecem o todoEquipes dedicadas por serviço
ComplexidadeConcentrada no códigoDistribuí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ãoCaso de UsoCaracterísticas
RESTOperações CRUD, APIs simplesAmplamente difundido, fácil debug
gRPCAlta performance, tipagem forteRápido, suporte a streaming
Fila de MensagensProcessamento assíncrono, orientado a eventosAcoplamento fraco, escalabilidade
GraphQLObtenção de dados dirigida pelo clienteQueries 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

  1. Responsabilidade única: 1 serviço = 1 funcionalidade de negócio
  2. Independência de dados: DB separado por serviço
  3. Acoplamento fraco: Comunicação via interfaces
  4. 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.

← Voltar para a lista