Chuyển đến nội dung chính

CQRS là gì?

Giới thiệu

CQRS được lấy cảm hứng từ mô hình Command Query Separation (CQS) do Bertrand Meyer đề xuất trong cuốn sách "Object Oriented Software Construction". Triết lý chính của CQS là

A method should either change state of an object, or return a result, but not both. In other words, asking the question should not change the answer. More formally, methods should return a value only if they are referentially transparent and hence possess no side effects.” (Wikipedia)

Dựa trên nguyên tắc này, CQS phân chia các phương thức thành hai nhóm:

  • Command: Thay đổi trạng thái của đối tượng hoặc toàn bộ hệ thống (còn gọi là modifiers hoặc mutators).
  • Query: Trả về kết quả và không thay đổi trạng thái của đối tượng.

Trong thực tế, việc phân biệt ra Query và Command khá đơn giản. Bạn có thể tìm hiểu thêm bài viết tại đây

CQRS: Bản nâng cấp của CQS

CQRS (hay Command Query Responsibility Segregation) là một design pattern phân tách các hoạt động read và write dữ liệu. Trong đó chia việc tương tác với dữ liệu thành 2 thành phần Command và Query. Hai thành phần này tách biệt và độc lập với nhau.

Để giao tiếp với database, chúng ta thường dùng mô hình thông dụng là Repository với 4 phương thức cơ bản Insert, Get, Update, Delete (CRUD). Data được lưu trữ một chỗ và ứng dụng tương tác với chỗ đó để cả tạo, đọc, sửa, xóa. CQRS thì khác, CQRS tách thành hai mô hình Command và Query (tương tự như CQS) nhưng model Read và Write độc lập với nhau.


CQRS đang cố gắng giải quyết vấn đề gì?

Khi thiết kế hệ thống, chúng ta thường bắt đầu với việc thiết kế lưu trữ dữ liệu. Đầu tiên là chuẩn hóa dữ liệu (database normalization), thêm primary key và foreign keys để thực thi tính toàn vẹn tham chiếu, thêm index, để đảm bảo tối ưu hóa cho việc read-write. Hoặc chúng ta suy nghĩ về case read, thêm data vào database trước...

Không có cách nào là sai. Vấn đề là sự cân bằng giữa read và write không được đảm bảo. CQRS giải quyết vấn đề này bằng cách tách biệt mô hình Read và Write

Mediator Pattern

Mediator Pattern là một mô hình thiết kế hành vi giúp giảm thiểu sự phụ thuộc trực tiếp giữa các đối tượng trong hệ thống. Nó hoạt động như một người trung gian để truyền tải thông tin và điều phối các tương tác giữa các đối tượng.


Tháp điều khiển tại sân bay có kiểm soát là một ví dụ về hoạt động của Mediator pattern. Các phi công của các máy bay đang cất cánh hoặc hạ cánh kết nối với tháp chứ không phải giao tiếp rõ ràng với nhau. Những khó khăn về việc ai có thể cất hoặc hạ cánh được thi hành bởi tháp điều khiển. Điều quan trọng cần lưu ý là tháp không kiểm soát toàn bộ chuyến bay. Nó tồn tại chỉ để thực thi các quy định an toàn trong lúc cất và hạ cánh.


Ở hình trên, Some Service sẽ gởi message tới Mediator, Mediator sẽ invoke service để handle message. Không có dependency trực tiếp giữa các blue component.

MediatR

Là một thư viện mã nguồn mở được xây dựng dựa trên Mediator Pattern cho phép bạn dễ dàng áp dụng pattern này vào các ứng dụng .NET.

Cách sử dụng MediatR

Cài đặt

dotnet add package MediatR
Đăng ký các dịch vụ MediatR:
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
Trường hợp các Query, Command, và Handler ở project khác, bạn tạo method và gọi hàm đăng ký như sau:
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));

Setup ASP.NET Core với MediatR

Để bắt đầu đơn giản nhất, chúng ta sẽ lần lượt thực hiện các bước sau:

  • Tạo Model class: Author và Book
  • 2 model này đại diện cho 2 entity mà chúng ta sẽ khai báo trong Entity Framework
  • Cài đặt package EF Core InMemory NuGet.
  • Tạo DbContext class
  • Khai báo Repository class: IAuthorRepository và AuthorRepository
  • Khai báo Dependency Injection
  • Cài đặt và khai báo Swagger
  • Khai báo Controller

Model Author và Book

public class Author
{
	public int Id { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
	public List<Book> Books { get; set; }
}
public class Book
{
	public int Id { get; set; }
	public string Title { get; set; }
	public Author Author { get; set; }
}
Install package
Install-Package Microsoft.EntityFrameworkCore.InMemory
Khai báo BookContext
public class BookContext : DbContext
{
    public DbSet<Author> Authors { get; set; }
    public DbSet<Book> Books { get; set; }
    public BookContext(DbContextOptions<BookContext> options) : base(options)
    {

    }

}
Khai báo Model trả về
public class AuthorModel
{
	public int Id { get; set; }
	public string FirstName { get; set; }
	public string LastName { get; set; }
}
Khai báo Repository
public interface IAuthorRepository
{
	public List<AuthorModel> GetAuthors(int pageSize, int page);
}
public class AuthorRepository: IAuthorRepository
{
	private readonly BookContext _dbContext;
	public AuthorRepository(BookContext dbContext)
	{
		_dbContext = dbContext;
	}
	public List<AuthorModel> GetAuthors(int pageSize, int page)
	{
		var list = _dbContext.Authors.Skip(pageSize * page).Take(pageSize).Select(t => new AuthorModel
		{
			FirstName = t.FirstName,
			LastName = t.LastName,
			Id = t.Id,
		}).ToList();
		return list;
	}
}

Mình sẽ lược qua đoạn đăng ký Dependency Injection và Swagger

Ở controller, bạn viết như sau:
[Route("api/[controller]")]
[ApiController]
public class AuthorsController : ControllerBase
{
	private readonly IAuthorRepository _authorRepository;
	public AuthorsController(IAuthorRepository authorRepository)
	{
		_authorRepository = authorRepository;
	}
	[HttpGet]
	public ActionResult<List<Author>> Get(int pageSize, int pageIndex)
	{
		return Ok(_authorRepository.GetAuthors(pageSize, pageIndex));
	}
}

Controller vẫn có dependency vào Repository. Chúng ta sẽ thêm MediatR vào và gọi Query thay vì gọi trực tiếp authorRepository

Install Package cho CqrsSample và CqrsSample.Application

dotnet add package MediatR
Thêm đoạn đăng ký sau vào Program.cs
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
Ở CqrsSample.Application, thêm class sau:
public class ApplicationServiceRegistration
{
    public void Register(IServiceCollection services)
    {
        services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly()));
    }
}
Gọi hàm đăng ký ở Program.cs
var applicationModule = new ApplicationServiceRegistration();
applicationModule.Register(builder.Services);
Ở Project CqrsSample.Application, tạo file GetAuthorQuery và GetAuthorQueryHandler
using MediatR;

namespace CqrsSample.Application.Queries;
public class GetAuthorQuery : IRequest<List<AuthorModel>>
{
    public int PageSize { get; set; }
    public int PageIndex { get; set; } = 10;
}
using MediatR;

namespace CqrsSample.Application.Queries.Handlers
{
    public class GetAuthorQueryHandler : IRequestHandler<GetAuthorQuery, List<AuthorModel>>
    {
        private readonly IAuthorRepository _authorRepository;

        public GetAuthorQueryHandler(IAuthorRepository authorRepository)
        {
            _authorRepository = authorRepository;
        }

        public async Task<List<AuthorModel>> Handle(GetAuthorQuery request, CancellationToken cancellationToken)
        {
            return _authorRepository.GetAuthors(request.PageSize, request.PageIndex);
        }
    }
}
Sửa file AuthorController lại như sau
[Route("api/[controller]")]
[ApiController]
public class AuthorsController : ControllerBase
{
    private readonly IMediator _mediator;
    public AuthorsController(IMediator mediator)
    {
        _mediator = mediator;
    }
    [HttpGet]
    public async Task<ActionResult<List<Author>>> Get(int pageSize, int pageIndex)
    {
        var authorRequest = new GetAuthorQuery
        {
            PageIndex = pageIndex,
            PageSize = pageSize
        };
        var result = await _mediator.Send(authorRequest);
        return Ok(result);
    }
}
Kiểm tra kết quả ở trang Swagger. Bạn có thể làm tương tự với Command.

Nhận xét

  1. System.InvalidOperationException: No service for type ‘MediatR.IRequestHandler`[nameofyourclass]’ has been registered.

    Solution

    This could mean that your DI container still cannot find the handler. For instance because it is in another project.

    The trick here is simply to add ‘Containing’ to RegisterServicesFromAssembly

    builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining(typeof(MoneyTransferHandler)));

    Trả lờiXóa

Đăng nhận xét

Bài đăng phổ biến từ blog này

[ASP.NET MVC] Authentication và Authorize

Một trong những vấn đề bảo mật cơ bản nhất là đảm bảo những người dùng hợp lệ truy cập vào hệ thống. ASP.NET đưa ra 2 khái niệm: Authentication và Authorize Authentication xác nhận bạn là ai. Ví dụ: Bạn có thể đăng nhập vào hệ thống bằng username và password hoặc bằng ssh. Authorization xác nhận những gì bạn có thể làm. Ví dụ: Bạn được phép truy cập vào website, đăng thông tin lên diễn đàn nhưng bạn không được phép truy cập vào trang mod và admin.

ASP.NET MVC: Cơ bản về Validation

Validation (chứng thực) là một tính năng quan trọng trong ASP.NET MVC và được phát triển trong một thời gian dài. Validation vắng mặt trong phiên bản đầu tiên của asp.net mvc và thật khó để tích hợp 1 framework validation của một bên thứ 3 vì không có khả năng mở rộng. ASP.NET MVC2 đã hỗ trợ framework validation do Microsoft phát triển, tên là Data Annotations. Và trong phiên bản 3, framework validation đã hỗ trợ tốt hơn việc xác thực phía máy khách, và đây là một xu hướng của việc phát triển ứng dụng web ngày nay.

Tổng hợp một số kiến thức lập trình về Amibroker

Giới thiệu về Amibroker Amibroker theo developer Tomasz Janeczko được xây dựng dựa trên ngôn ngữ C. Vì vậy bộ code Amibroker Formula Language sử dụng có syntax khá tương đồng với C, ví dụ như câu lệnh #include để import hay cách gói các object, hàm trong các block {} và kết thúc câu lệnh bằng dấu “;”. AFL trong Amibroker là ngôn ngữ xử lý mảng (an array processing language). Nó hoạt động dựa trên các mảng (các dòng/vector) số liệu, khá giống với cách hoạt động của spreadsheet trên excel.