REPR là gì?
REPR (đọc là "Reaper") là một mẫu thiết kế API mới, tập trung vào việc tổ chức các endpoint thay vì sử dụng controller truyền thống. Được giới thiệu bởi Steve "ardalis" Smith, REPR giúp cải thiện cách thức tổ chức các endpoint trong API, giúp chúng dễ dàng hơn trong việc tìm kiếm, điều hướng và chỉnh sửa.
Với mẫu thiết kế này, mỗi endpoint sẽ được định nghĩa trong một lớp riêng biệt, mỗi lớp sẽ có một phương thức duy nhất để xử lý các yêu cầu đến. Mục tiêu của REPR là tổ chức ứng dụng theo một kiến trúc tập trung vào endpoint, tạo ra ba thành phần chính:
- Request
- Endpoint
- Response
Như vậy, khi nhận một yêu cầu từ người dùng, chúng ta sẽ xử lý yêu cầu đó tại endpoint, sau đó trả về một đối tượng response. Phương pháp này giúp quá trình nhận, xử lý và trả về kết quả trở nên đơn giản và rõ ràng hơn.
Tại sao cần phải có REPR?
Khi phát triển API theo kiến trúc MVC, chúng ta bắt đầu với những controller nhỏ gọn và đơn giản. Tuy nhiên, khi ứng dụng phát triển, các controller này có thể trở nên quá lớn, chứa nhiều phương thức và có quá nhiều trách nhiệm. Điều này dẫn đến việc các controller trở thành các đối tượng phức tạp, khó bảo trì và dễ gặp phải xung đột khi nhiều người cùng làm việc trên cùng một controller.
Một controller quá lớn có thể vi phạm nguyên lý Single Responsibility Principle (SRP), vì mỗi controller có thể chứa quá nhiều phương thức, xử lý nhiều loại tác vụ không liên quan. Điều này làm cho việc mở rộng và bảo trì ứng dụng trở nên khó khăn hơn.
Với REPR, mỗi endpoint sẽ được tách ra thành các lớp riêng biệt, giúp giải quyết vấn đề này. Việc chia nhỏ các endpoint như vậy giúp ứng dụng của bạn modular, dễ bảo trì và mở rộng, đồng thời tránh được những vấn đề phức tạp mà các controller truyền thống thường gặp.
Ví dụ: Giả sử ban đầu controller chỉ có 2 action:
GetBookById(Guid id)
CreateBook(CreateBookRequest request)
Nhưng khi các tính năng được phát triển thêm, controller có thể cần hỗ trợ:
UpdateBook(Guid id, UpdateBookRequest request)
DeleteBook(Guid id)
GetBooksByAuthor(string author)
GetBooksByCategory(string category)
GetRecommendedBooks(Guid userId)
Dần dần, controller trở thành một "God Object" chứa quá nhiều trách nhiệm, vi phạm SRP.
God object
A God Object (or God Class) is a class that holds too much responsibility in a system. It violates key software design principles, such as the Single Responsibility Principle (SRP), which states that a class should only have one reason to change.
Ngay cả khi bạn chia nhỏ controller theo resource (BooksController, OrdersController...), các controller đó vẫn có thể chứa các phương thức xử lý business logic phức tạp.
Ví dụ:
- PlaceOrder() không chỉ tạo Order mà còn cập nhật Inventory, gửi email, ...
- RegisterUser() có thể tạo tài khoản, gửi email xác nhận, và cấp quyền truy cập.
Như vậy, SRP bị vi phạm vì một class không chỉ xử lý một trách nhiệm duy nhất.
Triển khai REPR Pattern
Chúng ta có thể tự triển khai REPR Pattern trong dự án .NET của mình bằng cách loại bỏ các controllers truyền thống. Thay vào đó, tất cả các endpoints sẽ được di chuyển sang các file riêng biệt. Trong mỗi file endpoint, chúng ta sẽ sử dụng MediatR để xử lý tất cả các yêu cầu đến endpoint đó.
Tuy nhiên, nếu ứng dụng có nhiều endpoints, cách làm này có thể trở nên phức tạp và khó duy trì. Việc triển khai các lệnh xử lý yêu cầu (command-handling) theo cách thủ công cũng không hề đơn giản.
Hiện nay, có hai thư viện phổ biến giúp chúng ta áp dụng REPR Pattern vào ứng dụng:
- ApiEndpoints, được phát triển bởi chính Steve Smith.
- FastEndpoints, một thư viện có nhiều tính năng hơn và là lựa chọn được Steve Smith khuyến nghị.
Trong bài viết này, chúng ta sử dụng library FastEndpoints
Để tạo một project REPRPattern, bạn có thể làm theo các bước dưới đây.
- Chọn File → New → Project.
- Trong cửa sổ Create a new project, tìm kiếm ASP.NET Core Empty.
- Nhấn Next => ..=>Chọn .NET 9 => Finish
Dưới đây là 1 đoạn code API đơn giản
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "Hello, World!");
app.Run();
Cài đặt nuget package FastEndpoints
dotnet add package FastEndpoints
Cập nhật lại đoạn code sau, sử dụng FastEndpoints
using FastEndpoints;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddFastEndpoints(); //add FastEndpoints
var app = builder.Build();
app.UseFastEndpoints(); //use FastEndpoints
app.MapGet("/", () => "Hello World!");
app.Run();
Tổ chức folder
├── Endpoints/
│ ├── Product/
│ │ ├── CreateProductEndpoint.cs
│ │ ├── UpdateProductEndpoint.cs
│ │ ├── DeleteProductEndpoint.cs
├── Requests/
│ ├── CreateProductRequest.cs
│ ├── UpdateProductRequest.cs
├── Responses/
│ ├── CreateProductResponse.cs
│ ├── ProductDetailsResponse.cs
CreateProductEndpoint
namespace ReprPattern.Features.Products.Endpoints;
public class CreateProductEndpoints : Endpoint<CreateProductRequest, CreateProductResponse>
{
public override void Configure()
{
Post("/api/products");
AllowAnonymous();
}
public override async Task HandleAsync(CreateProductRequest req, CancellationToken ct)
{
var product = new CreateProductResponse(Guid.NewGuid(), req.Name, req.Price);
await Task.Delay(100, ct);
await SendCreatedAtAsync<CreateProductEndpoints>($"/products/{product.Id}", product, cancellation: ct);
}
}
CreateProductRequest.cs
namespace ReprPattern.Features.Products.Requests;
public record CreateProductRequest(string Name, decimal Price);
CreateProductResponse
namespace ReprPattern.Features.Products.Responses;
public record CreateProductResponse(Guid Id, string Name, decimal Price);
Trả về response theo chuẩn REST
FastEndpoints có các phương thức tiện ích như SendCreatedAtAsync, SendOkAsync, SendNotFoundAsync giúp tuân thủ chuẩn REST mà không cần viết thủ công.
Created (201): Khi tạo mới:await SendCreatedAtAsync<CreateProductEndpoints>($"/products/{product.Id}", product, cancellation: ct);
Trả về: HTTP Status: 201 Created
{
"id": "a9190bd0-38c0-48c2-b28d-a2760810345e",
"name": "Đông Châu liệt quốc",
"price": 15
}
Tham khảo
How to Use the REPR (Request-Endpoint-Response) Pattern in .NET
SOLID Principles in C# – Single Responsibility Principle
FastEndpoints - Misc Conveniences
Nhận xét
Đăng nhận xét