Microsserviços: Do Básico ao Avançado Já leu

Fundamentos de Microsserviços Microsserviços representam um padrão arquitetural onde uma aplicação é construída como um conjunto de serviços pequenos, independentes e que se comunicam entre si. Diferente da arquitetura monolítica tradicional, cada microsserviço executa um processo específico do negócio e pode ser desenvolvido, implantado e escalado de forma independente. A principal vantagem está na autonomia: equipes podem trabalhar em serviços diferentes simultaneamente usando tecnologias distintas, desde que respeitem os contratos de comunicação. Isso acelera o desenvolvimento e facilita a manutenção. Porém, essa abordagem introduz complexidade na comunicação entre serviços, gerenciamento de dados distribuídos e monitoramento. Características Essenciais Independência: Cada serviço possui seu próprio banco de dados e ciclo de vida Comunicação: Utilizam protocolos leves como HTTP/REST, gRPC ou mensageria assíncrona Descentralização: Governança e dados descentralizados por domínio Resiliência: Falhas isoladas não comprometem todo o sistema Escalabilidade: Escala horizontal independente por serviço Implementação Prática com Node.js Vamos construir dois microsserviços básicos: um para gerenciar usuários e outro para pedidos, comunicando-se via

Fundamentos de Microsserviços

Microsserviços representam um padrão arquitetural onde uma aplicação é construída como um conjunto de serviços pequenos, independentes e que se comunicam entre si. Diferente da arquitetura monolítica tradicional, cada microsserviço executa um processo específico do negócio e pode ser desenvolvido, implantado e escalado de forma independente.

A principal vantagem está na autonomia: equipes podem trabalhar em serviços diferentes simultaneamente usando tecnologias distintas, desde que respeitem os contratos de comunicação. Isso acelera o desenvolvimento e facilita a manutenção. Porém, essa abordagem introduz complexidade na comunicação entre serviços, gerenciamento de dados distribuídos e monitoramento.

Características Essenciais

  • Independência: Cada serviço possui seu próprio banco de dados e ciclo de vida
  • Comunicação: Utilizam protocolos leves como HTTP/REST, gRPC ou mensageria assíncrona
  • Descentralização: Governança e dados descentralizados por domínio
  • Resiliência: Falhas isoladas não comprometem todo o sistema
  • Escalabilidade: Escala horizontal independente por serviço

Implementação Prática com Node.js

Vamos construir dois microsserviços básicos: um para gerenciar usuários e outro para pedidos, comunicando-se via HTTP.

Serviço de Usuários

// users-service/index.js
const express = require('express');
const app = express();
app.use(express.json());

const users = new Map([
  [1, { id: 1, name: 'João Silva', email: 'joao@email.com' }],
  [2, { id: 2, name: 'Maria Santos', email: 'maria@email.com' }]
]);

app.get('/users/:id', (req, res) => {
  const user = users.get(parseInt(req.params.id));
  if (!user) return res.status(404).json({ error: 'Usuário não encontrado' });
  res.json(user);
});

app.post('/users', (req, res) => {
  const { name, email } = req.body;
  const id = users.size + 1;
  const newUser = { id, name, email };
  users.set(id, newUser);
  res.status(201).json(newUser);
});

const PORT = 3001;
app.listen(PORT, () => console.log(`Users service running on port ${PORT}`));

Serviço de Pedidos

// orders-service/index.js
const express = require('express');
const axios = require('axios');
const app = express();
app.use(express.json());

const orders = new Map();

app.post('/orders', async (req, res) => {
  try {
    const { userId, product, amount } = req.body;

    // Comunicação entre microsserviços
    const userResponse = await axios.get(`http://localhost:3001/users/${userId}`);
    const user = userResponse.data;

    const orderId = orders.size + 1;
    const order = {
      id: orderId,
      userId,
      userName: user.name,
      product,
      amount,
      status: 'pending'
    };

    orders.set(orderId, order);
    res.status(201).json(order);
  } catch (error) {
    if (error.response?.status === 404) {
      return res.status(400).json({ error: 'Usuário não existe' });
    }
    res.status(500).json({ error: 'Erro ao criar pedido' });
  }
});

app.get('/orders/:id', (req, res) => {
  const order = orders.get(parseInt(req.params.id));
  if (!order) return res.status(404).json({ error: 'Pedido não encontrado' });
  res.json(order);
});

const PORT = 3002;
app.listen(PORT, () => console.log(`Orders service running on port ${PORT}`));

Este exemplo demonstra comunicação síncrona entre serviços. O serviço de pedidos valida a existência do usuário antes de criar um pedido, ilustrando dependência entre microsserviços.

Padrões Avançados e Boas Práticas

API Gateway

Um API Gateway atua como ponto de entrada único, roteando requisições para os microsserviços apropriados. Implementação básica:

// api-gateway/index.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();

// Roteamento para microsserviços
app.use('/api/users', createProxyMiddleware({
  target: 'http://localhost:3001',
  pathRewrite: { '^/api/users': '/users' },
  changeOrigin: true
}));

app.use('/api/orders', createProxyMiddleware({
  target: 'http://localhost:3002',
  pathRewrite: { '^/api/orders': '/orders' },
  changeOrigin: true
}));

// Autenticação centralizada
app.use((req, res, next) => {
  const token = req.headers['authorization'];
  if (!token && req.path.includes('/orders')) {
    return res.status(401).json({ error: 'Token necessário' });
  }
  next();
});

const PORT = 3000;
app.listen(PORT, () => console.log(`API Gateway on port ${PORT}`));

Circuit Breaker Pattern

Protege o sistema contra falhas em cascata, evitando chamadas repetidas a serviços que estão falhando:

const axios = require('axios');

class CircuitBreaker {
  constructor(threshold = 5, timeout = 60000) {
    this.failureCount = 0;
    this.threshold = threshold;
    this.timeout = timeout;
    this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
    this.nextAttempt = Date.now();
  }

  async call(url) {
    if (this.state === 'OPEN') {
      if (Date.now() < this.nextAttempt) {
        throw new Error('Circuit breaker está OPEN');
      }
      this.state = 'HALF_OPEN';
    }

    try {
      const response = await axios.get(url, { timeout: 3000 });
      this.onSuccess();
      return response.data;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  onSuccess() {
    this.failureCount = 0;
    this.state = 'CLOSED';
  }

  onFailure() {
    this.failureCount++;
    if (this.failureCount >= this.threshold) {
      this.state = 'OPEN';
      this.nextAttempt = Date.now() + this.timeout;
      console.log('Circuit breaker OPEN - suspendendo chamadas');
    }
  }
}

// Uso prático
const breaker = new CircuitBreaker(3, 30000);

async function getUserWithProtection(userId) {
  try {
    return await breaker.call(`http://localhost:3001/users/${userId}`);
  } catch (error) {
    // Retorna dados em cache ou resposta padrão
    return { id: userId, name: 'Usuário Indisponível' };
  }
}

Mensageria Assíncrona

Para operações que não exigem resposta imediata, use mensageria. Exemplo com RabbitMQ:

const amqp = require('amqplib');

// Producer (Orders Service)
async function publishOrderCreated(order) {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  const queue = 'order_created';

  await channel.assertQueue(queue, { durable: true });
  channel.sendToQueue(queue, Buffer.from(JSON.stringify(order)), {
    persistent: true
  });

  console.log('Evento order_created publicado:', order.id);
  setTimeout(() => connection.close(), 500);
}

// Consumer (Notifications Service)
async function consumeOrderEvents() {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  const queue = 'order_created';

  await channel.assertQueue(queue, { durable: true });
  channel.prefetch(1);

  console.log('Aguardando eventos de pedidos...');

  channel.consume(queue, (msg) => {
    const order = JSON.parse(msg.content.toString());
    console.log(`Enviando notificação para pedido ${order.id}`);

    // Simula envio de email/SMS
    setTimeout(() => {
      console.log(`Notificação enviada: Pedido ${order.id} criado`);
      channel.ack(msg);
    }, 1000);
  });
}

Service Discovery e Health Checks

Microsserviços precisam se registrar e descobrir outros serviços dinamicamente:

// health-check endpoint
app.get('/health', (req, res) => {
  const health = {
    uptime: process.uptime(),
    timestamp: Date.now(),
    status: 'UP',
    checks: {
      database: 'UP',
      memory: process.memoryUsage().heapUsed < 500000000 ? 'UP' : 'DOWN'
    }
  };

  const statusCode = health.checks.database === 'UP' ? 200 : 503;
  res.status(statusCode).json(health);
});

Observabilidade e Monitoramento

Em ambientes distribuídos, rastreabilidade é crucial. Implemente logging estruturado e tracing distribuído:

const winston = require('winston');
const { v4: uuidv4 } = require('uuid');

// Logger estruturado
const logger = winston.createLogger({
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.json()
  ),
  transports: [
    new winston.transports.File({ filename: 'service.log' })
  ]
});

// Middleware para correlation ID
app.use((req, res, next) => {
  req.correlationId = req.headers['x-correlation-id'] || uuidv4();
  res.setHeader('x-correlation-id', req.correlationId);

  logger.info('Request received', {
    correlationId: req.correlationId,
    method: req.method,
    path: req.path,
    service: 'orders-service'
  });

  next();
});

// Propagar correlation ID entre serviços
async function callUserService(userId, correlationId) {
  return axios.get(`http://localhost:3001/users/${userId}`, {
    headers: { 'x-correlation-id': correlationId }
  });
}

Conclusão

  • Microsserviços oferecem escalabilidade e independência, mas introduzem complexidade na comunicação, consistência de dados e observabilidade que deve ser cuidadosamente gerenciada
  • Implemente padrões como API Gateway, Circuit Breaker e mensageria assíncrona para construir sistemas resilientes e preparados para produção
  • Invista em observabilidade desde o início: logging estruturado, correlation IDs e health checks são essenciais para operar microsserviços com confiança

Referências


Artigos relacionados