Microsserviços em JavaScript: arquitetura e prática
A arquitetura de microsserviços (MSA) tornou-se o padrão de facto para construir aplicações complexas, escaláveis e tolerantes a falhas. Ao contrário do monolito, onde todo o código está ligado em um único processo, os microsserviços dividem o sistema em serviços pequenos e independentes. JavaScript, especificamente a plataforma Node.js, devido ao seu modelo orientado a eventos e ao seu enorme ecossistema, é uma excelente escolha para implementar microsserviços. Neste artigo, vamos analisar os princípios-chave da arquitetura, escrever alguns serviços funcionais e considerar os aspetos práticos da sua interação.
Princípios fundamentais da arquitetura de microsserviços
Antes de escrever código, é importante entender o que torna um microsserviço um "microsserviço". Os princípios fundamentais são:
- Responsabilidade Única (Single Responsibility): Cada serviço resolve uma tarefa de negócio específica (por exemplo, apenas autenticação ou apenas gestão de pedidos).
- Independência: Os serviços são desenvolvidos, testados e implantados independentemente uns dos outros.
- Isolamento de dados: Cada serviço tem a sua própria base de dados. Não existe um repositório comum.
- Baixo Acoplamento (Loose Coupling): Os serviços comunicam através da rede (API), e não através de chamadas diretas de funções.
- Tolerância a Falhas: A falha de um serviço não deve derrubar todo o sistema.
Node.js é ideal para estas tarefas devido à sua natureza não bloqueante e assíncrona, o que é crítico quando há um grande número de chamadas de rede entre serviços.
Prática: Criando dois microsserviços simples
Vamos escrever dois serviços simples em Express.js: User Service (gestão de utilizadores) e Order Service (gestão de pedidos).
1. User Service (serviço de utilizadores)
Crie o ficheiro user-service/server.js:
const express = require('express');const app = express();app.use(express.json());
const users = [{ id: 1, name: 'Alice', email: 'alice@example.com' }];
app.get('/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user);});
app.listen(3001, () => console.log('User Service running on port 3001'));2. Order Service (serviço de pedidos)
Crie o ficheiro order-service/server.js:
const express = require('express');const app = express();app.use(express.json());
const orders = [ { id: 1, userId: 1, product: 'Laptop', amount: 1500 }];
app.get('/orders/user/:userId', (req, res) => { const userOrders = orders.filter(o => o.userId === parseInt(req.params.userId)); res.json(userOrders);});
app.listen(3002, () => console.log('Order Service running on port 3002'));Agora temos dois serviços independentes, a funcionar em portas diferentes. Esta é a base da arquitetura de microsserviços.
Interação entre serviços: REST e Message Broker
Os serviços precisam de comunicar. Vamos considerar duas abordagens principais.
3.1 Interação síncrona (REST API)
Um serviço chama diretamente a API de outro. Por exemplo, para obter pedidos com o nome do utilizador, vamos criar um API Gateway ou um serviço compositor.
// api-gateway.jsconst axios = require('axios');
app.get('/users/:id/orders', async (req, res) => { try { const userResponse = await axios.get(`http://localhost:3001/users/${req.params.id}`); const ordersResponse = await axios.get(`http://localhost:3002/orders/user/${req.params.id}`); res.json({ user: userResponse.data, orders: ordersResponse.data }); } catch (error) { res.status(500).json({ error: 'Failed to fetch data' }); }});3.2 Interação assíncrona (Message Broker)
Para um acoplamento mais fraco e melhor tolerância a falhas, os serviços podem comunicar através de um Message Broker (por exemplo, RabbitMQ, Kafka). Um serviço publica eventos e outros serviços os consomem.
// Event Publisher (User Service)const amqp = require('amqplib');
async function publishUserCreated(user) { const connection = await amqp.connect('amqp://localhost'); const channel = await connection.createChannel(); const exchange = 'user_events'; await channel.assertExchange(exchange, 'fanout', { durable: false }); channel.publish(exchange, '', Buffer.from(JSON.stringify(user))); console.log('Event published: User Created');}
// Event Consumer (Order Service)const amqp = require('amqplib');
async function consumeUserCreated() { const connection = await amqp.connect('amqp://localhost'); const channel = await connection.createChannel(); const exchange = 'user_events'; await channel.assertExchange(exchange, 'fanout', { durable: false }); const q = await channel.assertQueue('', { exclusive: true }); channel.bindQueue(q.queue, exchange, ''); channel.consume(q.queue, (msg) => { const user = JSON.parse(msg.content.toString()); console.log('Received User:', user); // Processar o evento (ex: criar um carrinho para o novo utilizador) }, { noAck: true });}Conclusão
Os microsserviços em JavaScript (Node.js) oferecem uma forma poderosa de construir sistemas escaláveis e resilientes. Começamos com serviços pequenos e independentes, comunicando através de REST ou de um Message Broker. Lembre-se dos princípios: responsabilidade única, independência e isolamento de dados. Na prática, ferramentas como Docker, Kubernetes e sistemas de monitorização (Prometheus, Grafana) são frequentemente utilizadas para gerir o ciclo de vida e a saúde dos microsserviços. A arquitetura de microsserviços não é uma bala de prata, mas para sistemas complexos, é frequentemente a escolha certa.
```