Simple demo về kiến trúc CQRS với Spring Boot

CQRS là gì ?

CQRS là một pattern cho việc xử lý CRUD dữ liệu, viết tắt của từ Command Query Responsibility Segregation. Trong đó bóc tách thành 2 thành phần CommandQuery .

  • Command : là các thao tác ghi dữ liệu - WRITE
  • Query : là các thao tác đọc dữ liệu - READ

Tư tưởng của CQRS, là các thao tác Query thì cần tách biệt với các thao tác Command, để tránh xẩy ra các ảnh hưởng phụ về business process và tạo các luồng xử lý rõ ràng, chặt chẽ.

Được gói gọn như sau:

  • Query: select và trả về dữ liệu, nhưng không thay đổi trạng thái của hệ thống
  • Command : thay đổi dữ liệu/thay đổi trạng thái hệ thống, nhưng không trả về dữ liệu

Mục đích của bài viết này nhằm trình bày, khi áp dụng CQRS vào coding thì sẽ như thế nào, không nhằm giải thích các lập luận.

Vì vậy, để hiểu rõ hơn các lập luận về kiến trúc CQRS, có lẽ bước đầu tiên các bạn cần tìm hiểu một vài khái niệm về CQS (Command Query Separation) , Command, Query và Command Bus.

Các bạn có thể tham khảo tại:

CQRS với Spring Boot demo

Trong bài viết mình sử dụng Spring Boot làm một demo nhỏ.

Để tạo Spring Boot project , các bạn có thể generate từ đây https://start.spring.io/ hoặc trên IDE ( intelij chẳng hạn).

Như cấu trúc project thông thường, mình cũng sẽ có các layers như sau .

  • Service layer
    • IBookQueryService
    • IBookCommandService
  • Controller layer
    • BookQueryController
    • BookCommandController
  • Representations
    • BookQueryDto
    • BookCreateCommandDto

Service

Thao tác giữa 2 vai trò READ và WRITE đuọc tách biệt trong IBookQueryService và IBookCommandService như sau.

public interface IBookQueryService {
    
    List<Book> getAllBooks();

    Book getBookInfo(String bookId);

}
public interface IBookCommandService {
    void create(String name, Date publishDate);
}

Controller

BookQueryController sẽ gồm có các resource SELECT,cũng sẽ Autowired IBookQueryService ở Query Controller tương ứng

@Controller
public class BookQueryController {

    @Autowired
    private IBookQueryService bookQueryService;

    @GetMapping("/books")
    @ResponseBody
    public List<Book> allBooks() {
        return bookQueryService.getAllBooks();
    }

    @GetMapping("/books/{bookId}")
    @ResponseBody
    public ResponseEntity<Book> bookInfo(@PathVariable("bookId") String bookId) {
        Book bookInfo = bookQueryService.getBookInfo(bookId);
        if (bookInfo == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(bookInfo);
    }
}
@Controller
public class BookCommandController {

    @Autowired
    private IBookCommandService bookCommandService;

    @PostMapping("/book/create")
    @ResponseStatus(HttpStatus.OK)
    public void createBook(@RequestBody BookCreateCommandDto dto) {
        bookCommandService.create(dto.getName(), dto.getPublishDate());
    }
}

Representations

Tiếp theo là các data transfer object, các lớp sẽ được convert từ Entity.

Trong ví dụ này, Book như một minh họa của class Entity mapping với table trong database.

@Data
public class BookCreateCommandDto {
    private String name;
    private Date publishDate;
}

Tổng kết

Cơ bản mình đã trình bày xong tổng quát về CQRS.

Tư tưởng của CQRS rất rõ ràng, chặt chẽ. Một Query request thì cần tách biệt với Command request.

  • Query: chỉ select và trả về dữ liệu, nhưng không thay đổi data
  • Command : update thay đổi dữ liệu, nhưng không trả về dữ liệu

Bài viết có sử dụng một số hình ảnh từ internet, full source code bạn có thể download từ link dưới.

Download

Full source code demo: https://github.com/manhnv118/cqrs-simple-architecture