Áp Dụng CQRS Pattern Trong Node.js Microservice – Tối Ưu Hiệu Suất Và Khả Năng Mở Rộng

Tìm hiểu cách áp dụng CQRS Pattern trong Node.js Microservice để tối ưu hóa hiệu suất, bảo mật dữ liệu và cải thiện khả năng mở rộng. Hướng dẫn chi tiết với ví dụ thực tế sử dụng Express, MongoDB và RabbitMQ.

1. Giới Thiệu Về CQRS

CQRS (Command Query Responsibility Segregation) là một mẫu kiến trúc giúp tách biệt các thao tác ghi (Command) và đọc (Query) trong hệ thống. Điều này giúp hệ thống mở rộng dễ dàng hơn, tăng hiệu suất và bảo mật dữ liệu.

Lợi ích của CQRS

  • Tối ưu hóa hiệu suất: Tách biệt đọc và ghi giúp mỗi phần có thể tối ưu riêng.
  • Cải thiện khả năng mở rộng: Hệ thống có thể scale độc lập theo nhu cầu.
  • Tăng cường bảo mật: Ngăn chặn việc truy cập dữ liệu không mong muốn.
  • Dễ dàng áp dụng Event Sourcing: Ghi lại tất cả thay đổi của dữ liệu dưới dạng sự kiện.

2. Thiết Kế Kiến Trúc CQRS trong Node.js

Trong hệ thống Microservices, ta có thể áp dụng CQRS bằng cách tách rời service xử lý command và service xử lý query.

Mô hình tổng quan

  1. Command Service: Xử lý ghi dữ liệu (Create, Update, Delete) và gửi event.
  2. Query Service: Xử lý truy vấn dữ liệu, có thể sử dụng cơ sở dữ liệu riêng để tối ưu hiệu suất.
  3. Event Bus (Message Broker): Giao tiếp giữa các dịch vụ thông qua message queue (RabbitMQ, Kafka, NATS…).

Cấu trúc thư mục:

cqrs-microservice/
├── command-service/      # Service xử lý ghi
├── query-service/        # Service xử lý đọc
├── event-bus/            # Message Broker (Kafka, RabbitMQ,...)
└── shared/               # Code dùng chung (interface, schema,...)

3. Cài Đặt Và Cấu Hình

3.1. Khởi tạo Command Service

mkdir cqrs-microservice && cd cqrs-microservice
npx express-generator command-service
cd command-service
npm install express mongoose amqplib dotenv

Cấu hình MongoDB (command-service/src/config/db.js)

const mongoose = require('mongoose');
require('dotenv').config();

const connectDB = async () => {
    await mongoose.connect(process.env.MONGO_URI, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
    });
    console.log('MongoDB connected (Command Service)');
};

module.exports = connectDB;

Định nghĩa Model và Service (command-service/src/models/Order.js)

const mongoose = require('mongoose');

const OrderSchema = new mongoose.Schema({
    userId: String,
    product: String,
    quantity: Number,
    status: { type: String, default: 'pending' }
}, { timestamps: true });

module.exports = mongoose.model('Order', OrderSchema);

Xử lý tạo đơn hàng (command-service/src/controllers/orderController.js)

const Order = require('../models/Order');
const amqp = require('amqplib');

exports.createOrder = async (req, res) => {
    const { userId, product, quantity } = req.body;
    const order = new Order({ userId, product, quantity });
    await order.save();

    // Gửi event đến message broker
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();
    await channel.assertQueue('order_created');
    channel.sendToQueue('order_created', Buffer.from(JSON.stringify(order)));

    res.status(201).json(order);
};

3.2. Khởi tạo Query Service

npx express-generator query-service
cd query-service
npm install express mongoose amqplib dotenv

Kết nối MongoDB (query-service/src/config/db.js)

const mongoose = require('mongoose');
require('dotenv').config();

const connectDB = async () => {
    await mongoose.connect(process.env.MONGO_URI, {
        useNewUrlParser: true,
        useUnifiedTopology: true,
    });
    console.log('MongoDB connected (Query Service)');
};

module.exports = connectDB;

Lắng nghe sự kiện từ Command Service (query-service/src/events/orderEventListener.js)

const mongoose = require('mongoose');
const amqp = require('amqplib');
const Order = require('../models/Order');

const listenForEvents = async () => {
    const connection = await amqp.connect('amqp://localhost');
    const channel = await connection.createChannel();
    await channel.assertQueue('order_created');

    channel.consume('order_created', async (msg) => {
        const orderData = JSON.parse(msg.content.toString());
        const order = new Order(orderData);
        await order.save();
        console.log('Order saved in Query Service:', order);
    }, { noAck: true });
};

module.exports = listenForEvents;

Xử lý truy vấn đơn hàng (query-service/src/controllers/orderController.js)

const Order = require('../models/Order');

exports.getOrders = async (req, res) => {
    const orders = await Order.find();
    res.json(orders);
};

4. Triển Khai Và Kiểm Tra

Chạy Command Service

cd command-service
npm start

Chạy Query Service

cd query-service
npm start

Gửi Request Tạo Đơn Hàng

curl -X POST http://localhost:3000/orders -H "Content-Type: application/json" \
     -d '{"userId": "123", "product": "Laptop", "quantity": 1}'

Lấy Danh Sách Đơn Hàng (Query Service)

curl -X GET http://localhost:4000/orders

5. Kết Luận

Áp dụng CQRS giúp tách biệt rõ ràng giữa xử lý dữ liệu ghi và truy vấn, giúp tăng hiệu suất và mở rộng hệ thống dễ dàng. Bạn có thể kết hợp thêm Event Sourcing để ghi lại lịch sử thay đổi của dữ liệu, hoặc áp dụng Kafka thay vì RabbitMQ để hỗ trợ mở rộng tốt hơn.

Chúc bạn thành công!

Comments

Leave a Reply

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