Введение в микросервисную архитектуру на JavaScript
Микросервисная архитектура — это подход к разработке программного обеспечения, при котором приложение строится как набор небольших, независимо развертываемых сервисов. Каждый сервис реализует одну бизнес-функцию и взаимодействует с другими через легковесные протоколы (обычно HTTP/REST или очереди сообщений). JavaScript, а именно среда выполнения Node.js, идеально подходит для создания микросервисов благодаря асинхронной модели I/O, огромному экосистеме пакетов (npm) и высокой скорости разработки.
В этой статье мы разберем ключевые аспекты создания микросервисов на JavaScript: от проектирования архитектуры до развертывания в контейнерах. Вы узнаете, как организовать обмен данными, обеспечить отказоустойчивость и тестировать распределенную систему.
1. Проектирование архитектуры микросервисов
Первый шаг — декомпозиция монолитного приложения на сервисы. Каждый микросервис должен владеть собственной базой данных (database per service) и быть полностью автономным. Рассмотрим пример интернет-магазина:
- Service Users — регистрация, аутентификация, профили.
- Service Products — каталог товаров, цены, остатки.
- Service Orders — создание и обработка заказов.
- Service Payments — интеграция с платежными шлюзами.
- Service Notifications — отправка email/SMS.
Для взаимодействия между сервисами используем два подхода: синхронный (REST/gRPC) для запросов, требующих немедленного ответа, и асинхронный (очереди сообщений) для событий, которые не требуют блокировки.
2. Создание микросервиса на Node.js с Express
Начнем с простого микросервиса для управления пользователями. Установим Express и библиотеку для работы с UUID:
npm init -y
npm install express uuid
Создадим файл users-service.js:
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const app = express();
app.use(express.json());
const users = [];
// Создание пользователя
app.post('/users', (req, res) => {
const { name, email } = req.body;
const user = { id: uuidv4(), name, email };
users.push(user);
res.status(201).json(user);
});
// Получение списка пользователей
app.get('/users', (req, res) => {
res.json(users);
});
// Получение пользователя по ID
app.get('/users/:id', (req, res) => {
const user = users.find(u => u.id === req.params.id);
if (!user) return res.status(404).json({ error: 'User not found' });
res.json(user);
});
app.listen(3001, () => console.log('Users service running on port 3001'));
Запустите сервис: node users-service.js. Теперь у вас есть работающий микросервис с REST API. Аналогично создаются сервисы для продуктов и заказов (на портах 3002 и 3003 соответственно).
3. Асинхронное взаимодействие через RabbitMQ
Для событий, таких как "создан заказ" или "пользователь зарегистрирован", используем очереди сообщений. Установим клиент для RabbitMQ:
npm install amqplib
Создадим утилиту для публикации сообщений (event-bus.js):
const amqp = require('amqplib');
let channel;
async function connect() {
const connection = await amqp.connect('amqp://localhost');
channel = await connection.createChannel();
await channel.assertExchange('order_events', 'topic', { durable: true });
}
async function publishEvent(routingKey, data) {
if (!channel) await connect();
channel.publish('order_events', routingKey, Buffer.from(JSON.stringify(data)));
console.log(`Event published: ${routingKey}`);
}
module.exports = { publishEvent };
Теперь в сервисе заказов (orders-service.js) при создании заказа публикуем событие:
const { publishEvent } = require('./event-bus');
app.post('/orders', async (req, res) => {
const order = { id: uuidv4(), userId: req.body.userId, items: req.body.items };
orders.push(order);
// Публикуем событие
await publishEvent('order.created', order);
res.status(201).json(order);
});
Сервис уведомлений (Notifications) подписывается на событие order.created и отправляет email пользователю:
const amqp = require('amqplib');
async function consume() {
const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();
await channel.assertExchange('order_events', 'topic', { durable: true });
const q = await channel.assertQueue('', { exclusive: true });
await channel.bindQueue(q.queue, 'order_events', 'order.created');
channel.consume(q.queue, msg => {
const order = JSON.parse(msg.content.toString());
console.log(`Sending notification for order ${order.id} to user ${order.userId}`);
// Здесь вызов email-сервиса
channel.ack(msg);
});
}
consume();
4. Контейнеризация и оркестрация с Docker
Для изоляции и упрощения развертывания каждый микросервис упаковывается в Docker-контейнер. Создадим Dockerfile для сервиса пользователей:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3001
CMD ["node", "users-service.js"]
Соберите образ: docker build -t users-service .. Для запуска всех сервисов используем Docker Compose (docker-compose.yml):
version: '3'
services:
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
users-service:
build: ./users-service
ports:
- "3001:3001"
depends_on:
- rabbitmq
orders-service:
build: ./orders-service
ports:
- "3002:3002"
depends_on:
- rabbitmq
notifications-service:
build: ./notifications-service
depends_on:
- rabbitmq
Запустите всю систему командой docker-compose up. Теперь все сервисы работают в изолированных контейнерах и общаются через RabbitMQ.
5. Тестирование и мониторинг микросервисов
Для тестирования микрос