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