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

REPR (Request-Endpoint-Response) Pattern trong .NET

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

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.