Архітектура, орієнтована на події в MERN: використання RabbitMQ для асинхронної комунікації

Сучасні додатки все більше спираються на архітектури, орієнтовані на події (Event-Driven Architectures, EDA), для створення масштабованих, чутливих і розв'язаних систем. У додатках на стеку MERN (MongoDB, Express, React, Node.js) реалізація архітектури, орієнтованої на події, за допомогою RabbitMQ може значно покращити комунікацію між сервісами на бекенді, використовуючи черги повідомлень для асинхронної обробки.

У цій статті ми покажемо, як реалізувати архітектуру, орієнтовану на події, з RabbitMQ у бекенді на Node.js, написаному на TypeScript, з MongoDB як базою даних. Ми зосередимося виключно на бекенді, демонструючи, як налаштувати RabbitMQ, генерувати події та споживати повідомлення.

Попередні вимоги

Перед тим як розпочати роботу з додатком, переконайтеся, що у вас є:

  1. Базові знання Node.js, TypeScript та Express.
  2. Робочий екземпляр RabbitMQ (локальний або хостований).
  3. Встановлений і запущений MongoDB (локальний або хостований).
    4.
    текст перекладу
    Інсталяція Node.js

Налаштування RabbitMQ

Інсталяція RabbitMQ:

  • Для Ubuntu: sudo apt-get install rabbitmq-server
  • Для macOS: brew install rabbitmq

Запуск RabbitMQ:

rabbitmq-server

Доступ до інтерфейсу управління RabbitMQ (необов'язково):

За замовчуванням він доступний за адресою http://localhost:15672 (ім'я користувача: guest, пароль: guest).

Створення бекенду на Express та TypeScript

Крок 1: Ініціалізація проєкту

Спочатку створіть новий Node.js проєкт:

mkdir event-driven-backend && cd event-driven-backend  
npm init -y

Встановіть необхідні залежності:

npm install express amqplib mongoose dotenv  
npm install --save-dev typescript ts-node ts-node-dev @types/node @types/express @types/amqplib @types/mongoose

Ініціалізуйте TypeScript:

npx tsc --init

Крок 2: Налаштування змінних середовища

Створіть файл .env для зберігання конфігурацій:

PORT=5000  
MONGO_URI=mongodb://localhost:27017/eventdb  
RABBITMQ_URL=amqp://localhost

Крок 3: Підключення до MongoDB

Створіть файл src/db.ts для підключення до MongoDB:

import mongoose from 'mongoose';  

export const connectDB = async (): Promise => {  
 try {  
 await mongoose.connect(process.env.MONGO_URI as string);  

 console.log(`MongoDB підключено до бази даних '${mongoose.connection.name}'...`);  
 } catch (error) {  
 console.error("Помилка підключення до MongoDB:", error);  
 process.exit(1);  
 }  
};

Крок 4: Налаштування підключення до RabbitMQ

Створіть файл src/rabbitmq.ts:

import amqp from 'amqplib';  

let ch: amqp.Channel | null = null;  
let con: amqp.Connection | null = null;  

export const connectRMQ = async (): Promise => {  
 try {  
 con = await amqp.connect(process.env.RABBITMQ_URL as string);  
 ch = await con.createChannel();  
 console.log(`RabbitMQ підключено...`);  
 } catch (error) {  
 console.error('Помилка підключення до RabbitMQ: ', error);  
 process.exit(1);  
 }  
};  

export const getChannel = (): amqp.Channel | null => ch;

Крок 5: Оголошення продюсера та консюмера

Продюсер
Створіть файл src/producer.ts для публікації подій:

import { getChannel } from "./rabbitmq"  

export const publishToQueue = async (name: string, message: object): Promise => {  
 const ch = getChannel();  
 if (!ch) throw new Error('Канал RabbitMQ недоступний');  

 await ch.assertQueue(name, { durable: true });  
 ch.sendToQueue(name, Buffer.from(JSON.stringify(message)));  
 console.log(`Повідомлення надіслано в чергу ${name}:`, message);  
};

Консумер
Створіть файл src/consumer.ts для обробки подій:

import { getChannel } from "./rabbitmq";  
import Order from './schema';  

export const consumeFromQueue = async (name: string): Promise => {  
 const ch = getChannel();  
 if (!ch) throw new Error('Канал RabbitMQ недоступний');  

 await ch.assertQueue(name, { durable: true });  

 ch.consume(name, async (msg) => {  
 if (msg) {  
 const event = JSON.parse(msg.content.toString());  
 console.log(`Повідомлення отримано з черги ${name}:`, event);  

 const order = new Order(event);  
 await order.save();  

 ch.ack(msg);  
 };  
 });  
};

Крок 6: Модель MongoDB

Створіть файл src/schema.ts для зберігання замовлень:

import mongoose, { Schema, Document } from 'mongoose';  

export interface IOrder extends Document {  
 productId: string;  
 quantity: number;  
 status: string;  
}  

const orderSchema: Schema = new Schema({  
 productId: { type: String, required: true },  
 quantity: { type: Number, required: true },  
 status: { type: String, default: "pending" },  
});  

export default mongoose.model('Order', orderSchema);

Крок 7: Об'єднання всього разом

У файлі src/index.ts ініціалізуйте застосунок:

import dotenv from 'dotenv';  
import express, { Request, Response } from 'express';  
import { connectDB } from './db';  
import { connectRMQ } from './rabbitmq';  
import { consumeFromQueue } from './consumer';

текст перекладу

import { publishToQueue } from './producer';

dotenv.config();

const app = express();

app.use(express.json());

connectDB();
connectRMQ().then(() => consumeFromQueue('orderQueue'));

app.get('/', (req: Request, res: Response) => {
res.send('Welcome to Server!');
});

app.post('/order', async (req, res) => {
const { productId, quantity } = req.body;

try {
const event = { productId, quantity };
await publishToQueue('orderQueue', event);
res.status(200).send({ message: 'Order event published' });
} catch (error) {
console.error(error);
res.status(500).send({ error: 'Failed to publish event' });
}
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(Server is running on http://localhost:${PORT}...));
```

Тестування застосунку

  • Запустіть сервери RabbitMQ та MongoDB
  • Запустіть сервер Node.js
npx ts-node src/index.ts
  • Використовуйте API-клієнт (наприклад, Postman) для тестування ендпоінту /order.
  • Перевірте, чи з'явилось повідомлення в черзі RabbitMQ.
  • Консумер (Consumer) обробить повідомлення, і замовлення буде збережено в MongoDB.

Висновок

Ця реалізація демонструє, як RabbitMQ дозволяє організувати асинхронну комунікацію в Node.js застосунку.
текст перекладу
Розділивши продюсерів (Producers) та консюмерів (Consumers), ви можете досягти кращої масштабованості та стійкості до відмов, забезпечивши, щоб ваша система залишалася чутливою навіть під великим навантаженням. Розширте цю конфігурацію, додавши більше черг, консюмерів або використовуючи складніші патерни, такі як черги мертвих листів (dead-letter queues), для створення надійної архітектури для виробничих середовищ.

Перекладено з: Event-Driven Architecture in MERN: Using RabbitMQ for Asynchronous Communication

Leave a Reply

Your email address will not be published. Required fields are marked *