Command and Query Responsibility Segregation(CQRS) là một pattern phân tách read và write dữ liệu. Query sẽ tiến hành đọc dữ liệu và Command sẽ thực hiện các lệnh đến database như Insert/Update hoặc Delete.
Một vài ưu điểm của CQRS:
- Dễ mở rộng
- Tối ưu các dữ liệu được truyền tải
- Mở rộng độc lập các thành phần (Read/Write)
Triển khai CQRS bằng thư viện MediatR giúp ta triển khai Mediator pattern trong .Net. Đây là một behavioral pattern giúp giảm sự phụ thuộc giữa các đối tượng bằng cách hạn chế giao tiếp giữa các đối tượng bằng cách giao tiếp qua các đối tượng trung gian.
Bắt đầu
Mở Visual Studio, tạo project với ASP.NET Core Web API.
Cài đặt các packages cần thiết
1 |
Install-Package MediatR |
1 |
Install-Package MediatR.Extensions.Microsoft.DependencyInjection |
1 |
Install-Package Swashbuckle.AspNetCore |
Tạo structure cho project
Trong thư mục Models, chúng ta tạo class Student
1 2 3 4 5 6 7 |
public class Student { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public double Age { get; set; } } |
Trong thư mục DataAccess, chúng ta sẽ tiến hành viết class xử lý dữ liệu, để đơn giản, chúng ta sẽ viết các phương thức giả lập thao tác với database.
1 2 3 4 5 6 |
public interface IDataAccess { List<Student> GetStudents(); Student AddStudent(string firstName, string lastName, double age); Student GetStudentById(int id); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class DataAccess : IDataAccess { private List<Student> student = new List<Student>(); public DataAccess() { student.Add(new Student { Id = 1, FirstName = "Jhon", LastName = "Doe", Age = 18}); student.Add(new Student { Id = 2, FirstName = "Amelia", LastName = "Amy", Age = 16 }); } public Student GetStudentById(int id) { var stu = student.Where(t => t.Id == id).FirstOrDefault(); return stu; } public List<Student> GetStudents() { return student; } public Student AddStudent(string firstName, string lastName, double age) { Student s = new Student(); s.FirstName = firstName; s.LastName = lastName; s.Age = age; s.Id = student.Count() + 1; student.Add(s); return s; } } |
Tạo Query
Trong thư mục Queries, chúng ta tiến hành viết tất cả query trong này. Tiến hành thêm class GetStudentListQuery bên trong thư mục Queries.
1 2 3 |
public class GetStudentListQuery : IRequest<List<Student>> { } |
GetStudenListQuery sẽ implements interface IRequest để thư viện MediatR biết là class này là một query hay command. GetStudentListQuery sẽ trả về cho chúng ta danh sách Student, nên ta sẽ truyền List<Student> vào generic interface.
Tạo Handler
Mỗi request (Query/Command) đều sẽ cần một handler cho chính nó. Handler sẽ xác định các việc cần làm khi nhận yêu cầu từ client. Chúng ta sẽ tiến hành tạo GetStudentListHandler trong thư mục Handlers để định nghĩa handler cho GetStudentListQuery.
1 2 3 4 5 6 7 8 9 10 11 12 |
public class GetStudentListHandler : IRequestHandler<GetStudentListQuery, List<Student>> { private readonly IDataAccess _data; public GetStudentListHandler(IDataAccess data) { _data = data; } public Task<List<Student>> Handle(GetStudentListQuery request, CancellationToken cancellationToken) { return Task.FromResult(_data.GetStudents()); } } |
Handler sẽ implement IRequestHandler với hai tham số, tham số đầu tiên là tên request sẽ xử lý, tham số thứ hai sẽ là kết quả trả về của request.
Tạo Command
Trong thư mục Commands, chúng ta sẽ tạo AddStudentCommand để thực hiện insert student.
1 |
public record AddStudentCommand(string firstName, string lastName, double age) : IRequest<Student>; |
Tiến hành tạo handler cho cho command
1 2 3 4 5 6 7 8 9 10 11 12 |
public class AddStudentHandler : IRequestHandler<AddStudentCommand, Student> { private readonly IDataAccess _data; public AddStudentHandler(IDataAccess data) { _data = data; } public Task<Student> Handle(AddStudentCommand request, CancellationToken cancellationToken) { return Task.FromResult(_data.AddStudent(request.firstName, request.lastName, request.age)); } } |
Cấu hình dependency
Mở class startup.cs tại phương thức ConfigureServices() chúng ta sẽ tiến hành đăng ký services cho MediatR.
1 2 3 4 5 6 7 8 9 10 |
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "CQRS_Mediator", Version = "v1" }); }); services.AddSingleton<IDataAccess, DataAccess.DataAccess>(); services.AddMediatR(typeof(LibraryEntrypoint).Assembly); } |
Thêm Controller
Bây giờ chúng ta sẽ tạo StudentController.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[Route("api/[controller]")] [ApiController] public class StudentController : ControllerBase { private readonly IMediator _mediator; public StudentController(IMediator mediator) { _mediator = mediator; } [HttpGet] public async Task<List<Student>> Get() { return await _mediator.Send(new GetStudentListQuery()); } [HttpPost] public async Task<Student> Post([FromBody] Student value) { var model = new AddStudentCommand(value.FirstName, value.LastName, value.Age); return await _mediator.Send(model); } } |
Khi gọi phương thức _mediator.Send() và truyền vào tên request, mediator sẽ gọi tới handler tương ứng của request. Về cơ bản, controller sẽ không cần biết bất kỳ implement, điều đó giúp controller sẽ clean hơn, mọi xử lý phức tạp đẵ được Mediator quản lý.
Run app với swagger và xem thành quả