Cho đến nay, trong ứng dụng của chúng ta, chúng ta đã sử dụng pattern Controller sử dụng  Service để xử lý dữ liệu. Mặc dù đó là một cách xử lý rất bình thường và phổ thông, nhưng vẫn có những cách khác đáng để thử.

NestJS đề xuất command-query responsibility segregation ( CQRS ). Trong bài viết này, chúng ta xem sẽ cùng nhau tìm hiểu và triển khai nó trong dự án nha.

Thay vì giữ logic của chúng ta trong services, với CQRS, chúng ta sử dụng commands để cập nhật dữ liệu và queries để lấy nó. Do đó, chúng tôi có sự tách biệt giữa việc thực hiện các hành động và trích xuất dữ liệu. Mặc dù điều này có thể không có lợi cho các ứng dụng CRUD đơn giản, nhưng CQRS có thể giúp việc kết hợp logic nghiệp vụ phức tạp trở nên dễ dàng hơn.

Theo dõi source code tại đâybranch dev nha

Link postman để test cho dễ.

Triển khai Command-Query Responsibility Segregation với NestJS

Đầu tiên ta cần cài thư viện để thực hiện:

npm install --save @nestjs/cqrs

Ở đây chúng ta sẽ xử lý việc tạo và truy vấn những bài viết, sử dụng lại model Post đã khởi tạo trước đó ( đừng quên tiếp tục sử dụng trên repo cũ).

Executing commands

Với CQRS, chúng ta thực hiện các hành động bằng cách thực hiện các lệnh. Trước tiên chúng ta cần tạo file command. Bên trong module Post, tạo folder commands:

import { User } from '../../user/models/user.model';
import { CreatePostDto } from '../dto/post.dto';

export class CreatePostCommand {
  constructor(
    public readonly user: User,
    public readonly createPostDto: CreatePostDto,
  ) {}
}

Để thực thi lệnh ở trên, chúng ta sử dụng  command bus. Theo như trước giờ, phần xử lý logic chúng ta sẽ khai báo bên trong service. Nhưng giờ lười quá mình viết trực tiếp bên trong controller luôn:

...
import { CommandBus } from '@nestjs/cqrs';
import { CreatePostCommand } from '../commands/createPost.command';

@Controller('post')
export class PostController {
  constructor(
    private readonly postService: PostService,
    private readonly commandBus: CommandBus,
  ) {}
  ...

  @UseGuards(AuthGuard('jwt'))
  @Post('create-by-command')
  async createByCommand(@Req() req: any, @Body() post: CreatePostDto) {
    return this.commandBus.execute(new CreatePostCommand(req.user, post));
  }
  ...
}

Khi lệnh được thực thi sẽ có nơi để xử lý câu lệnh đó, chúng ta khai báo command handler.

import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { CreatePostCommand } from '../commands/createPost.command';
import { PostRepository } from '../repositories/post.repository';

@CommandHandler(CreatePostCommand)
export class CreatePostHandler implements ICommandHandler<CreatePostCommand> {
  constructor(private postRepository: PostRepository) {}

  async execute(command: CreatePostCommand) {
    command.createPostDto.user = command.user._id;
    return await this.postRepository.create(command.createPostDto);
  }
}

Ở đây chúng ta sử dụng @CommandHandler(CreatePostCommand) để nó tự động match vào. Chỉ cần khai báo vào module thì nó sẽ tự hiểu nha.

Querying data

Tương tự như phần xử lý dữ liệu, chúng ta tiến hành truy vấn dữ liệu.

Tạo câu query:

export class GetPostQuery {
  constructor(public readonly post_id: string) {}
}

Ở trên thì dùng CommandBus, bây giờ dùng QueryBus để thực thi câu lệnh:

...
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { CreatePostCommand } from '../commands/createPost.command';
import { GetPostQuery } from '../queries/getPost.query';

@Controller('post')
export class PostController {
  constructor(
    private readonly postService: PostService,
    private readonly commandBus: CommandBus,
    private readonly queryBus: QueryBus,
  ) {}

  @Get(':id/get-by-query')
  async getDetailPostByQuery(@Param() { id }: FindPostDto) {
    return this.queryBus.execute(new GetPostQuery(id));
  }
  ...
}

Tiếp theo ta lại tạo handler cho câu query này:

import { ICommandHandler, QueryHandler } from '@nestjs/cqrs';
import { PostRepository } from '../repositories/post.repository';
import { GetPostQuery } from '../queries/getPost.query';

@QueryHandler(GetPostQuery)
export class GetPostHandler implements ICommandHandler<GetPostQuery> {
  constructor(private postRepository: PostRepository) {}

  async execute(command: GetPostQuery) {
    return await this.postRepository.findById(command.post_id);
  }
}

Sau cùng, và quan trọng nhất là import mọi thứ vào module, chứ không là lỗi ngay:

...
import { CqrsModule } from '@nestjs/cqrs';
import { CreatePostHandler } from './handler/createPost.handler';
import { GetPostHandler } from './handler/getPost.handler';

@Module({
  imports: [
    ...
    CqrsModule,
  ],
  controllers: [PostController, CategoryController],
  providers: [
    ...
    CreatePostHandler,
    GetPostHandler,
  ],
})
export class PostModule {}

Kết luận

Bài viết này đã giới thiệu khái niệm về CQRS và triển khai một ví dụ đơn giản trong ứng dụng NestJS của chúng ta. Vẫn còn nhiều chủ đề khác để đề cập khi nói đến CQRS, chẳng hạn như events và sagas. Các patterns khác cũng hoạt động rất tốt với CQRS, chẳng hạn như Event Sourcing. Mình sẽ giới thiệu sớm nhất có thể.

Biết những điều cơ bản về CQRS, chúng ta biết có một công cụ khác có thể áp dụng khi thiết kế kiến ​​trúc hệ thống.

Rất mong được sự ủng hộ của mọi người để mình có động lực ra những bài viết tiếp theo.
{\__/}
( ~.~ )
/ > ♥️ I LOVE YOU 3000

JUST DO IT!