Микросервисы на JavaScript: архитектура и практика от А до Я

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему микросервисы на JavaScript?

Современная веб-разработка всё чаще отходит от монолитных приложений в пользу микросервисной архитектуры. JavaScript, а точнее его серверная среда Node.js, идеально подходит для построения микросервисов благодаря асинхронности, событийно-ориентированной модели и огромному экосистемному сообществу. В этой статье мы разберём, как спроектировать, разработать и развернуть микросервисы на JavaScript — от теории до практических примеров кода.

1. Основы архитектуры микросервисов

Микросервисная архитектура — это подход, при котором приложение разбивается на небольшие, независимые сервисы. Каждый сервис отвечает за свою бизнес-логику, имеет собственное хранилище данных и может быть развёрнут, масштабирован и обновлён отдельно.

Преимущества микросервисов на Node.js

  • Масштабируемость: Node.js легко обрабатывает тысячи одновременных соединений благодаря неблокирующему вводу-выводу.
  • Быстрое прототипирование: npm-пакеты и фреймворки (Express, Fastify, NestJS) позволяют быстро создавать REST/GraphQL API.
  • Изоляция ошибок: Сбой в одном сервисе не парализует всё приложение.
  • Технологическая гибкость: Разные сервисы могут использовать разные версии Node.js или даже разные языки.

Ключевые компоненты

  • API Gateway — единая точка входа, маршрутизирующая запросы к нужным сервисам.
  • Брокер сообщений (RabbitMQ, Redis, Kafka) — для асинхронного обмена данными.
  • Сервисный реестр (Consul, etcd) — для обнаружения сервисов.
  • Контейнеризация (Docker, Kubernetes) — для стандартизации окружения.

2. Практика: Создаём два микросервиса

Разберём пример системы заказов. У нас будет два микросервиса: user-service (управление пользователями) и order-service (управление заказами). Они будут общаться через REST API и асинхронно через RabbitMQ.

2.1. Базовый сервис на Express

Начнём с user-service. Установите зависимости:

npm init -y
npm install express body-parser

Создайте файл index.js:

const express = require('express');
const bodyParser = require('body-parser');
const app = express();
const PORT = process.env.PORT || 3001;

app.use(bodyParser.json());

const users = [];

app.post('/users', (req, res) => {
    const { name, email } = req.body;
    const user = { id: users.length + 1, name, email };
    users.push(user);
    res.status(201).json(user);
});

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(PORT, () => {
    console.log(`User service running on port ${PORT}`);
});

Аналогично создайте order-service на порту 3002. Он будет содержать массив заказов и два эндпоинта: POST /orders и GET /orders/:id.

2.2. API Gateway

Создадим простой шлюз на порту 3000, который перенаправляет запросы в зависимости от пути:

const express = require('express');
const httpProxy = require('http-proxy-middleware');
const app = express();

app.use('/users', httpProxy.createProxyMiddleware({ target: 'http://localhost:3001', changeOrigin: true }));
app.use('/orders', httpProxy.createProxyMiddleware({ target: 'http://localhost:3002', changeOrigin: true }));

app.listen(3000, () => console.log('API Gateway on port 3000'));

Теперь все запросы к /users идут на user-service, а к /orders — на order-service.

2.3. Асинхронное взаимодействие с RabbitMQ

Допустим, при создании заказа нужно проверить, существует ли пользователь. Вместо синхронного вызова (который замедлит ответ) используем RabbitMQ. Установите amqplib:

npm install amqplib

В order-service при создании заказа отправляем сообщение в очередь:

const amqp = require('amqplib');
async function sendValidation(userId) {
    const conn = await amqp.connect('amqp://localhost');
    const ch = await conn.createChannel();
    const queue = 'user_validation';
    await ch.assertQueue(queue);
    ch.sendToQueue(queue, Buffer.from(JSON.stringify({ userId })));
    console.log('Sent validation request');
}
// Вызываем в POST /orders

В user-service запускаем потребителя:

async function consume() {
    const conn = await amqp.connect('amqp://localhost');
    const ch = await conn.createChannel();
    const queue = 'user_validation';
    await ch.assertQueue(queue);
    ch.consume(queue, msg => {
        const { userId } = JSON.parse(msg.content.toString());
        const user = users.find(u => u.id === userId);
        if (user) console.log(`User ${userId} exists`);
        else console.log(`User ${userId} NOT found`);
        ch.ack(msg);
    });
}
consume();

Таким образом, сервисы общаются асинхронно, не блокируя друг друга.

3. Контейнеризация и развёртывание

Для надёжного запуска микросервисов используем Docker. Создайте Dockerfile для каждого сервиса:

FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3001
CMD ["node", "index.js"]

Соберите образы и запустите через docker-compose.yml:

version: '3.8'
services:
  user-service:
    build: ./user-service
    ports:
      - "3001:3001"
  order-service:
    build: ./order-service
    ports:
      - "3002:3002"
  gateway:
    build: ./gateway
    ports:
      - "3000:3000"
    depends_on:
      - user-service
      - order-service
  rabbitmq                

Блоги

Рекомендаций по книгам