Со временем, каждый проект растет и реализовывать новый функционал в существующий монолит становится все сложнее, дольше и дороже для бизнеса.
Один из вариантов решения данной проблемы — использование микросервисной архитектуры. Для новичков или для тех, кто впервые сталкиваются с данной архитектурой, может быть сложно понять, с чего начать, что нужно делать, а что делать не стоит.
В этой статье будет написан простейший микросервис на Nodejs & RabbitMQ, а также показан процесс миграции монолита на микросервисы.
Что есть в микросервисной архитектуре?
- Gateway. Главный сервер, который принимает запросы и перенаправляет их нужному микросервису. Чаще всего, в gateway нет никакой бизнес-логики.
- 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);
Как это будет работать:
- Запускается сервер, он начинает слушать порт и получать запросы
- Пользователь отправляет запрос на https://mysite.com/friends
- Gateway, согласно логике, которую мы описали, делегирует запрос:
3.1 Идет отправка сообщения (параметры запроса, заголовки, информация о коннекте и др.) в очередь RabbitMQ
3.2. Микросервис слушает эту очередь, обрабатывает новый запрос
3.3. Микросервис отправляет ответ в очередь
3.4. Gateway слушает очередь ответов, получает ответ от микросервиса
3.5. Gateway отправляет ответ клиенту - Пользователь получает ответ
Пишем микросервис
// импортируем класс 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();
Как это будет работать:
- Микросервис запускается, начинает слушать очередь запросов, в которую будет писать Gateway
- Микросервис получает запрос, обрабатывает его, прогоняя через все имеющиеся middlewares
- Микросервис отправляет ответ в 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. Что нам для этого нужно сделать?
- Вынести бизнес-логику в микросервис
- Реализовать делегирование запросов на эти два эндпоинта в микросервис
- Получать ответ из микросервиса
- Отправлять ответ клиенту
В примере выше, когда мы создавали микросервис users, мы реализовали два метода /friends и /status, который делают то же самое, что делает наш монолит.
Для того, чтобы проксировать запросы в микросервис из gateway, мы воспользуемся тем же пакетом, подключив middleware в наше express приложение:
const express = require('express');
// импортируем класс MicroService из раннее установленного пакета micromq
const MicroMQ = require('micromq');
const app = express();
// создаем экземпляр класса MicroService
const app = 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.
Что дальше
- RPC (удаленный вызов действия) из микросервиса в монолит/gateway (например, для авторизации)
- Общаться между микросервисами через очереди RabbitMQ для получения дополнительной информации, ибо у каждого микросервиса своя база данных
Это я расскажу в следующей статье, если читателям понравится эта.
Автор: Mikhail Semin