Микросервисы на JavaScript: архитектура и практика для Node.js

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

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

Начать курс

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

Микросервисная архитектура (MSA) стала стандартом де-факто для построения сложных, масштабируемых и отказоустойчивых приложений. В отличие от монолита, где весь код связан в одном процессе, микросервисы разбивают систему на небольшие, независимые сервисы. JavaScript, а именно платформа Node.js, благодаря своей событийно-ориентированной модели и огромной экосистеме, является отличным выбором для реализации микросервисов. В этой статье мы разберем ключевые принципы архитектуры, напишем несколько работающих сервисов и рассмотрим практические аспекты их взаимодействия.

Основные принципы архитектуры микросервисов

Прежде чем писать код, важно понять, что делает микросервис "микросервисом". Основные принципы:

  • Единственная ответственность (Single Responsibility): Каждый сервис решает одну конкретную бизнес-задачу (например, только авторизация или только управление заказами).
  • Независимость: Сервисы разрабатываются, тестируются и развертываются независимо друг от друга.
  • Изолированность данных: Каждый сервис имеет свою собственную базу данных. Нет общего репозитория.
  • Слабая связность (Loose Coupling): Сервисы общаются через сеть (API), а не через прямые вызовы функций.
  • Отказоустойчивость: Сбой одного сервиса не должен обрушить всю систему.

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

Практика: Создание двух простых микросервисов

Давайте напишем два простых сервиса на Express.js: User Service (управление пользователями) и Order Service (управление заказами).

1. User Service (сервис пользователей)

Создайте файл 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 (сервис заказов)

Создайте файл 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'));

Теперь у нас есть два независимых сервиса, работающих на разных портах. Это основа микросервисной архитектуры.

Взаимодействие между сервисами: REST и Message Broker

Сервисы должны общаться. Рассмотрим два основных подхода.

3.1 Синхронное взаимодействие (REST API)

Один сервис напрямую вызывает API другого. Например, чтобы получить заказы с именем пользователя, создадим API Gateway или сервис-композитор.

// api-gateway.js
const 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: 'Service unavailable' });
    }
});

Плюсы: простота, привычный REST.
Минусы: высокая связность, каскадные сбои (если User Service упал, Order Service тоже не работает).

3.2 Асинхронное взаимодействие (Message Broker)

Для отказоустойчивости лучше использовать асинхронную коммуникацию через брокер сообщений, например, RabbitMQ или Kafka. Это позволяет сервисам общаться без прямой зависимости.

Установим amqplib:

npm install amqplib

Publisher (User Service после создания пользователя):

const amqp = require('amqplib');

async function publishUserCreated(user) {
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();
    const queue = 'user_created';
    await channel.assertQueue(queue, { durable: false });
    channel.sendToQueue(queue, Buffer.from(JSON.stringify(user)));
    console.log('Sent user:', user);
}

// Использование в POST /users
app.post('/users', (req, res) => {
    const user = { id: users.length + 1, ...req.body };
    users.push(user);
    publishUserCreated(user); // Асинхронно
    res.status(201).json(user);
});

Consumer (Order Service слушает очередь):

const amqp = require('amqplib');

async function consumeUserCreated() {
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();
    const queue = 'user_created';
    await channel.assertQueue(queue, { durable: false });
    console.log('Waiting for messages...');
    channel.consume(queue, (msg) => {
        const user = JSON.parse(msg.content.toString());
        console.log('Received user:', user.name);
        // Здесь можно инициализировать пустой профиль заказов для нового пользователя
        channel.ack(msg);
    });
}

consumeUserCreated();

Плюсы: слабая связность, высокая отказоустойчивость, масштабирование.
Минусы: сложность отладки, eventual consistency (не мгновенная консистентность).

Развертывание и оркестрация (Docker + Docker Compose)

Для управления несколькими сервисами на практике используют контейнеризацию. Создайте docker-compose.yml:

version: '3'
services:
  user-service:
    build: ./user-service
    ports:
      - "3001:3001"                

Блоги

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