☰ Mục lục series
Tài liệu NestJS tiếng Việt
  1. Nhập môn (Overview)
    1. Giới thiệu
    2. First steps
    3. Controllers
    4. Providers
    5. Modules
    6. Middleware
    7. Exception filters
    8. Pipes
    9. Guards
    10. Interceptors
    11. Custom decorators
  2. Nền tảng (Fundamentals)
    1. Custom providers
    2. Asynchronous providers
    3. Dynamic modules
    4. Injection scopes
    5. Circular dependency
    6. Module reference
    7. Lazy-loading modules
    8. Execution context
    9. Lifecycle events
    10. Discovery service
    11. Platform agnosticism
    12. Testing
  3. Kỹ thuật (Techniques)
    1. Configuration
    2. Database (SQL)
    3. MongoDB
    4. Validation
    5. Caching
    6. Serialization
    7. Versioning
    8. Task scheduling
    9. Queues
    10. Logging
    11. Cookies
    12. Events
    13. Compression
    14. File upload
    15. Streaming files
    16. HTTP module
    17. Session
    18. MVC
    19. Performance (Fastify)
    20. Server-Sent Events
  4. Bảo mật (Security)
    1. Authentication
    2. Authorization
    3. Encryption & Hashing
    4. Helmet
    5. CORS
    6. CSRF
    7. Rate limiting
    8. Vòng đời request
  5. OpenAPI (Swagger)
    1. Introduction
    2. Types and parameters
    3. Operations
    4. Security
    5. Mapped types
    6. Decorators
    7. CLI plugin
    8. Other features
  6. Recipes
    1. REPL
    2. CRUD generator
    3. SWC (fast compiler)
    4. Passport (auth)
    5. Hot reload
    6. MikroORM
    7. Router module
    8. Health checks (Terminus)
    9. CQRS
    10. Prisma
    11. Serve static
    12. Commander
    13. Async local storage
  7. WebSockets
    1. Gateways
    2. Exception filters
    3. Pipes
    4. Guards
    5. Interceptors
    6. Adapters
  8. Microservices
    1. Overview
    2. Redis
    3. MQTT
    4. NATS
    5. RabbitMQ
    6. Kafka
    7. gRPC
    8. Custom transporters
    9. Exception filters
    10. Pipes
    11. Pre-request hooks
    12. Guards
    13. Interceptors
  9. GraphQL
    1. Quick start
    2. Resolvers
    3. Mutations
    4. Subscriptions
    5. Scalars
    6. Directives
    7. Interfaces
    8. Unions and enums
    9. Field middleware
    10. Mapped types
    11. Plugins
    12. Complexity
    13. Extensions
    14. CLI plugin
    15. Generating SDL
    16. Sharing models
    17. Other features
    18. Federation
  10. CLI & triển khai
    1. CLI overview
    2. Workspaces
    3. Libraries
    4. Usage
    5. Scripts
    6. Deployment
    7. Standalone apps
  11. FAQ
    1. Serverless
    2. HTTP adapter
    3. Keep-Alive connections
    4. Global path prefix
    5. Raw body
    6. Hybrid application
    7. HTTPS & multiple servers
    8. Common errors
  12. Phụ lục
    1. Bảng thuật ngữ
Microservices

NATS

Bản dịch từ docs.nestjs.com/microservices/nats

NATS là một hệ thống messaging mã nguồn mở đơn giản, an toàn và có hiệu năng cao dành cho các ứng dụng cloud native, messaging IoT và kiến trúc microservices. NATS server được viết bằng ngôn ngữ Go, nhưng các thư viện client để giao tiếp với server thì có sẵn cho hàng chục ngôn ngữ lập trình phổ biến. NATS hỗ trợ cả hai kiểu delivery At Most Once và At Least Once. Nó có thể chạy ở bất cứ đâu, từ server lớn và cloud instance, cho tới edge gateway và cả các thiết bị Internet of Things.

Cài đặt

Để bắt đầu xây dựng microservice dựa trên NATS, trước tiên hãy cài package cần thiết:

$ npm i --save @nats-io/transport-node

Cảnh báo Kể từ NestJS v12, transporter NATS nhắm tới NATS v3 và dùng driver @nats-io/transport-node. Nếu bạn đang upgrade từ phiên bản cũ hơn, hãy gỡ package nats cũ (npm uninstall nats) và cài @nats-io/transport-node thay vào đó. Xem migration guide để biết chi tiết.

Tổng quan

Để dùng transporter NATS, truyền object options sau vào method createMicroservice():

const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
  transport: Transport.NATS,
  options: {
    servers: ['nats://localhost:4222'],
  },
});
const app = await NestFactory.createMicroservice(AppModule, {
  transport: Transport.NATS,
  options: {
    servers: ['nats://localhost:4222'],
  },
});

Gợi ý Enum Transport được import từ package @nestjs/microservices.

Options

Object options phụ thuộc vào transporter bạn chọn. Transporter NATS hỗ trợ các thuộc tính được mô tả tại đây cùng với các thuộc tính sau:

queue Queue mà server của bạn nên subscribe vào (để undefined nếu không cần dùng setting này). Đọc thêm về NATS queue group ở phần dưới.
gracefulShutdown Bật graceful shutdown. Khi bật, server sẽ unsubscribe khỏi tất cả channel trước khi đóng connection. Mặc định là false.
gracePeriod Thời gian (tính bằng milliseconds) chờ server sau khi unsubscribe khỏi tất cả channel. Mặc định là 10000 ms.

Client

Giống các transporter microservice khác, bạn có nhiều cách để tạo một instance ClientProxy cho NATS.

Một cách để tạo instance là dùng ClientsModule. Để tạo client instance bằng ClientsModule, import nó và dùng method register() để truyền một object options có các thuộc tính giống như đã trình bày ở trên trong method createMicroservice(), cùng với thuộc tính name dùng làm injection token. Đọc thêm về ClientsModule tại đây.

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'MATH_SERVICE',
        transport: Transport.NATS,
        options: {
          servers: ['nats://localhost:4222'],
        }
      },
    ]),
  ]
  ...
})

Bạn cũng có thể dùng các cách khác để tạo client (ClientProxyFactory hoặc @Client()). Bạn có thể đọc thêm về chúng tại đây.

Request-response

Với kiểu message request-response (đọc thêm), transporter NATS không dùng cơ chế Request-Reply có sẵn của NATS. Thay vào đó, một “request” được publish lên một subject nhất định bằng method publish() kèm theo một tên reply subject duy nhất, và các responder lắng nghe trên subject đó rồi gửi response tới reply subject. Reply subject được định hướng ngược lại tới bên gửi request một cách động, bất kể vị trí của cả hai bên.

Event-based

Với kiểu message event-based (đọc thêm), transporter NATS dùng cơ chế Publish-Subscribe có sẵn của NATS. Một publisher gửi message lên một subject và mọi subscriber đang active lắng nghe trên subject đó sẽ nhận được message. Subscriber cũng có thể đăng ký quan tâm tới các subject dùng wildcard, hoạt động khá giống regular expression. Pattern một-tới-nhiều này đôi khi được gọi là fan-out.

Queue group

NATS cung cấp một tính năng load balancing có sẵn gọi là distributed queue. Để tạo một queue subscription, dùng thuộc tính queue như sau:

const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
  transport: Transport.NATS,
  options: {
    servers: ['nats://localhost:4222'],
    queue: 'cats_queue',
  },
});

Ghi chú người dịch Queue group ở đây tương đương consumer group trong Kafka hoặc một queue trong RabbitMQ: nhiều instance của service cùng subscribe vào một queue name, nhưng NATS chỉ gửi mỗi message tới một instance trong group đó (load balancing), thay vì broadcast tới tất cả như pub-sub thông thường. Đây là cách bạn scale horizontal các consumer mà không xử lý trùng message.

Context

Trong các trường hợp phức tạp hơn, bạn có thể cần truy cập thêm thông tin về request đến. Khi dùng transporter NATS, bạn có thể truy cập object NatsContext.

@MessagePattern('notifications')
getNotifications(@Payload() data: number[], @Ctx() context: NatsContext) {
  console.log(`Subject: ${context.getSubject()}`);
}
@Bind(Payload(), Ctx())
@MessagePattern('notifications')
getNotifications(data, context) {
  console.log(`Subject: ${context.getSubject()}`);
}

Gợi ý @Payload(), @Ctx() và NatsContext được import từ package @nestjs/microservices.

Wildcard

Một subscription có thể trỏ tới một subject cụ thể, hoặc có thể dùng wildcard.

@MessagePattern('time.us.*')
getDate(@Payload() data: number[], @Ctx() context: NatsContext) {
  console.log(`Subject: ${context.getSubject()}`); // ví dụ: "time.us.east"
  return new Date().toLocaleTimeString(...);
}
@Bind(Payload(), Ctx())
@MessagePattern('time.us.*')
getDate(data, context) {
  console.log(`Subject: ${context.getSubject()}`); // ví dụ: "time.us.east"
  return new Date().toLocaleTimeString(...);
}

Record builder

Để cấu hình message options, bạn có thể dùng class NatsRecordBuilder (lưu ý: cách này cũng dùng được cho luồng event-based). Ví dụ, để thêm header x-version, dùng method setHeaders như sau:

import * as nats from '@nats-io/nats-core';

// đâu đó trong code của bạn
const headers = nats.headers();
headers.set('x-version', '1.0.0');

const record = new NatsRecordBuilder(':cat:').setHeaders(headers).build();
this.client.send('replace-emoji', record).subscribe(...);

Gợi ý Class NatsRecordBuilder được export từ package @nestjs/microservices.

Serializer và deserializer tùy chỉnh

Kể từ NestJS v12, Nest serialize NATS packet dưới dạng JSON string, và các deserializer NATS tùy chỉnh nhận vào object message NATS đầy đủ thay vì một Uint8Array thô. Nếu bạn đã viết một deserializer tùy chỉnh, hãy đọc payload qua msg.json() thay vì tự decode byte theo cách thủ công:

import { Deserializer, IncomingRequest } from '@nestjs/microservices';

export class CustomNatsDeserializer implements Deserializer {
  deserialize(msg: any): IncomingRequest {
    // Trước đây: JSON.parse(new TextDecoder().decode(msg));
    return msg.json();
  }
}

Ghi chú người dịch Đây là một breaking change thực tế cần lưu ý khi upgrade lên v12: nếu bạn có custom deserializer viết theo tài liệu cũ (decode Uint8Array bằng tay), code cũ sẽ lỗi hoặc parse sai dữ liệu vì msg bây giờ là object message có sẵn method .json(), không còn là raw bytes nữa.

Và bạn cũng có thể đọc các header này ở phía server, bằng cách truy cập NatsContext, như sau:

@MessagePattern('replace-emoji')
replaceEmoji(@Payload() data: string, @Ctx() context: NatsContext): string {
  const headers = context.getHeaders();
  return headers['x-version'] === '1.0.0' ? '🐱' : '🐈';
}
@Bind(Payload(), Ctx())
@MessagePattern('replace-emoji')
replaceEmoji(data, context) {
  const headers = context.getHeaders();
  return headers['x-version'] === '1.0.0' ? '🐱' : '🐈';
}

Trong một số trường hợp bạn có thể muốn cấu hình header cho nhiều request, bạn có thể truyền chúng như options cho ClientProxyFactory:

import { Module } from '@nestjs/common';
import { ClientProxyFactory, Transport } from '@nestjs/microservices';

@Module({
  providers: [
    {
      provide: 'API_v1',
      useFactory: () =>
        ClientProxyFactory.create({
          transport: Transport.NATS,
          options: {
            servers: ['nats://localhost:4222'],
            headers: { 'x-version': '1.0.0' },
          },
        }),
    },
  ],
})
export class ApiModule {}

Cập nhật trạng thái instance

Để nhận cập nhật real-time về connection và trạng thái của driver instance bên dưới, bạn có thể subscribe vào stream status. Stream này cung cấp các cập nhật trạng thái đặc thù cho driver đang dùng. Với driver NATS, stream status phát ra các event connected, disconnected và reconnecting.

this.client.status.subscribe((status: NatsStatus) => {
  console.log(status);
});

Gợi ý Type NatsStatus được import từ package @nestjs/microservices.

Tương tự, bạn có thể subscribe vào stream status của server để nhận thông báo về trạng thái của server.

const server = app.connectMicroservice<MicroserviceOptions>(...);
server.status.subscribe((status: NatsStatus) => {
  console.log(status);
});

Lắng nghe event của Nats

Trong một số trường hợp, bạn có thể muốn lắng nghe các event nội bộ được phát ra bởi microservice. Ví dụ, bạn có thể lắng nghe event error để kích hoạt các xử lý bổ sung khi có lỗi xảy ra. Để làm điều này, dùng method on(), như minh họa dưới đây:

this.client.on('error', (err) => {
  console.error(err);
});

Tương tự, bạn có thể lắng nghe các event nội bộ của server:

server.on<NatsEvents>('error', (err) => {
  console.error(err);
});

Gợi ý Type NatsEvents được import từ package @nestjs/microservices.

Truy cập driver bên dưới

Với các use case nâng cao hơn, bạn có thể cần truy cập trực tiếp driver instance bên dưới. Điều này có thể hữu ích cho các trường hợp như tự đóng connection bằng tay hoặc dùng các method đặc thù của driver. Tuy nhiên, hãy nhớ rằng trong hầu hết trường hợp, bạn không nên cần truy cập driver trực tiếp.

Để làm vậy, bạn có thể dùng method unwrap(), method này trả về driver instance bên dưới. Generic type parameter nên chỉ định type của driver instance mà bạn mong đợi.

const natsConnection =
  this.client.unwrap<import('@nats-io/transport-node').NatsConnection>();

Tương tự, bạn có thể truy cập driver instance bên dưới của server:

const natsConnection =
  server.unwrap<import('@nats-io/transport-node').NatsConnection>();

Nội dung gốc: NestJS Documentation © Kamil Myśliwiec, giấy phép MIT. Bản dịch và ghi chú: Lê Văn Dũng.