[Из песочницы] Пишем первый микросервис на Node.js с общением через RabbitMQ

habr.png

Со временем, каждый проект растет и реализовывать новый функционал в существующий монолит становится все сложнее, дольше и дороже для бизнеса.

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

В этой статье будет написан простейший микросервис на Nodejs & RabbitMQ, а также показан процесс миграции монолита на микросервисы.


  1. Gateway. Главный сервер, который принимает запросы и перенаправляет их нужному микросервису. Чаще всего, в gateway нет никакой бизнес-логики.
  2. Microservice. Сам микросервис, который обрабатывают запросы пользователей с четко заданной бизнес-логикой.


Начало

Для начала реализуем простой gateway, который будет принимать запросы по HTTP, слушая определенный порт.

Разворачиваем RabbitMQ (через него наши микросервисы и gateway будут общаться):

$ docker run -d -p 5672:5672 rabbitmq

Инициализируем проект и устанавливаем NPM-пакет micromq:

$ npm init -y
$ npm i micromq -S 


Пишем gateway

// импортируем класс Gateway из раннее установленного пакета micromq
const Gateway = require('micromq/gateway');

// создаем экземпляр класса Gateway
const app = new Gateway({
  // названия микросервисов, к которым мы будем обращаться
  microservices: ['users'],
  // настройки rabbitmq
  rabbit: {
    // ссылка для подключения к rabbitmq (default: amqp://guest:guest@localhost:5672)
    url: process.env.RABBIT_URL,
  },
});

// создаем два эндпоинта /friends & /status на метод GET
app.get(['/friends', '/status'], async (req, res) => {
  // делегируем запрос в микросервис users
  await res.delegate('users');
});

// начинаем слушать порт
app.listen(process.env.PORT);

Как это будет работать:


  1. Запускается сервер, он начинает слушать порт и получать запросы
  2. Пользователь отправляет запрос на https://mysite.com/friends
  3. Gateway, согласно логике, которую мы описали, делегирует запрос:
    3.1 Идет отправка сообщения (параметры запроса, заголовки, информация о коннекте и др.) в очередь RabbitMQ
    3.2. Микросервис слушает эту очередь, обрабатывает новый запрос
    3.3. Микросервис отправляет ответ в очередь
    3.4. Gateway слушает очередь ответов, получает ответ от микросервиса
    3.5. Gateway отправляет ответ клиенту
  4. Пользователь получает ответ


Пишем микросервис

// импортируем класс MicroService из раннее установленного пакета micromq
const MicroMQ = require('micromq');

// создаем экземпляр класса MicroService
const app = new MicroMQ({
  // название микросервиса (оно должно быть таким же, как указано в Gateway)
  name: 'users',
  // настройки rabbitmq
  rabbit: {
    // ссылка для подключения к rabbitmq (default: amqp://guest:guest@localhost:5672)
    url: process.env.RABBIT_URL,
  },
});

// создаем эндпоинт /friends для метода GET
app.get('/friends', (req, res) => {
  // отправляем json ответ
  res.json([
    {
      id: 1,
      name: 'Mikhail Semin',
    },
    {
      id: 2,
      name: 'Ivan Ivanov',
    },
  ]);
});

// создаем эндпоинт /status для метода GET
app.get('/status', (req, res) => {
  // отправляем json ответ
  res.json({
    text: 'Thinking...',
  });
});

// начинаем слушать очередь запросов
app.start();

Как это будет работать:


  1. Микросервис запускается, начинает слушать очередь запросов, в которую будет писать Gateway
  2. Микросервис получает запрос, обрабатывает его, прогоняя через все имеющиеся middlewares
  3. Микросервис отправляет ответ в Gateway
    3.1. Идет отправка сообщения (заголовки, HTTP-код тело ответа) в очередь RabbitMQ
    3.2. Gateway слушает эту очередь, получает сообщение, находит клиента, которому нужно отправить ответ
    3.3 Gateway отправляет ответ клиенту


Миграция монолита на микросервисную архитектуру

Предположим, что у нас уже есть приложение на express, и мы хотим начать его переносить на микросервисы.

Оно выглядит следующим образом:

const express = require('express');

const app = express();

app.get('/balance', (req, res) => {
  res.json({
    amount: 500,
  });
});

app.get('/friends', (req, res) => {
  res.json([
    {
      id: 1,
      name: 'Mikhail Semin',
    },
    {
      id: 2,
      name: 'Ivan Ivanov',
    },
  ]);
});

app.get('/status', (req, res) => {
  res.json({
    text: 'Thinking...',
  });
});

app.listen(process.env.PORT);

Мы хотим вынести из него 2 эндпоинта: /friends и /status. Что нам для этого нужно сделать?


  1. Вынести бизнес-логику в микросервис
  2. Реализовать делегирование запросов на эти два эндпоинта в микросервис
  3. Получать ответ из микросервиса
  4. Отправлять ответ клиенту

В примере выше, когда мы создавали микросервис users, мы реализовали два метода /friends и /status, который делают то же самое, что делает наш монолит.

Для того, чтобы проксировать запросы в микросервис из gateway, мы воспользуемся тем же пакетом, подключив middleware в наше express приложение:

const express = require('express');

// импортируем класс MicroService из раннее установленного пакета micromq
const MicroMQ = require('micromq');

const app = express();

// создаем экземпляр класса MicroService
const gateway = new MicroMQ({
  // название микросервиса (оно должно быть таким же, как указано в Gateway)
  name: 'users',
  // настройки rabbitmq
  rabbit: {
    // ссылка для подключения к rabbitmq (default: amqp://guest:guest@localhost:5672)
    url: process.env.RABBIT_URL,
  },
});

// подключаем middleware в монолит, который позволит нам делегировать запросы
app.use(gateway.middleware());

// не трогаем этот эндпоинт, потому что мы не планируем переносить его в микросервис
app.get('/balance', (req, res) => {
  res.json({
    amount: 500,
  });
});

// создаем два эндпоинта /friends & /status на метод GET
app.get(['/friends', '/status'], async (req, res) => {
  // делегируем запрос в микросервис users
  // метод res.delegate появился благодаря middleware, которую мы подключили выше
  await res.delegate('users');
});

// слушаем порт
app.listen(process.env.PORT);

Это работает так же, как в примере выше, где мы писали чистый Gateway. В этом примере разница лишь в том, что запросы принимает не Gateway, а монолит, написанный на express.


Что дальше


  1. RPC (удаленный вызов действия) из микросервиса в монолит/gateway (например, для авторизации)
  2. Общаться между микросервисами через очереди RabbitMQ для получения дополнительной информации, ибо у каждого микросервиса своя база данных

Это я расскажу в следующей статье, если читателям понравится эта.


© Habrahabr.ru