DEV Community

CQRS (Command Query Responsibility Segregation) και MediatR Pattern στη C#

1️⃣ Τι είναι το CQRS;

Το CQRS (Command Query Responsibility Segregation) είναι ένα αρχιτεκτονικό pattern που διαχωρίζει τις λειτουργίες ανάγνωσης (Query) από τις λειτουργίες εγγραφής (Command). Ο σκοπός του είναι να επιτρέπει το σύστημα να διαχειρίζεται τις εγγραφές και τις αναγνώσεις δεδομένων ανεξάρτητα, βελτιώνοντας την απόδοση, την επεκτασιμότητα και τη συντήρηση.

Βασικές Αρχές του CQRS:

Command: Χρησιμοποιείται για την τροποποίηση της κατάστασης του συστήματος (π.χ., δημιουργία, ενημέρωση, διαγραφή).
Query: Χρησιμοποιείται για την ανάκτηση δεδομένων χωρίς να μεταβάλλει την κατάσταση.
Separation of Concerns: Οι αναγνώσεις και οι εγγραφές δεν χρησιμοποιούν το ίδιο data model.


2️⃣ Τι είναι το MediatR Pattern;

Το MediatR είναι μια βιβλιοθήκη στη C# που εφαρμόζει το Mediator Pattern, επιτρέποντας την επικοινωνία μεταξύ των αντικειμένων χωρίς να χρειάζεται να είναι άμεσα συνδεδεμένα. Αυτό προάγει τη χαλαρή σύζευξη (loose coupling) και βελτιώνει τη δομή του κώδικα.

Οφέλη του MediatR:

✅ Απομάκρυνση των άμεσων εξαρτήσεων μεταξύ αντικειμένων.
✅ Καθαρός και δομημένος κώδικας με διαχωρισμό ευθυνών.
✅ Εύκολη επέκταση με νέες λειτουργίες χωρίς να επηρεάζονται άλλα κομμάτια του κώδικα.


3️⃣ Εφαρμογή του CQRS με MediatR στη C#

Βήμα 1: Εγκατάσταση του MediatR

Χρησιμοποιούμε το NuGet package manager για να εγκαταστήσουμε το MediatR:

Install-Package MediatR
Install-Package MediatR.Extensions.Microsoft.DependencyInjection
Enter fullscreen mode Exit fullscreen mode

Βήμα 2: Ορισμός του Command (Εγγραφή/Τροποποίηση Δεδομένων)

Ένα Command χρησιμοποιείται για να εκτελέσει μια ενέργεια που τροποποιεί το σύστημα. Δημιουργούμε ένα request που αντιπροσωπεύει την εντολή.

Παράδειγμα: Δημιουργία χρήστη (CreateUserCommand)

public record CreateUserCommand(string Name, string Email) : IRequest<int>;
Enter fullscreen mode Exit fullscreen mode

Το IRequest σημαίνει ότι αυτή η εντολή επιστρέφει έναν ακέραιο αριθμό (π.χ., το Id του νέου χρήστη).

Βήμα 3: Υλοποίηση του Handler για το Command

public class CreateUserCommandHandler : IRequestHandler<CreateUserCommand, int>
{
    private readonly ApplicationDbContext _dbContext;

    public CreateUserCommandHandler(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<int> Handle(CreateUserCommand request, CancellationToken cancellationToken)
    {
        var user = new User { Name = request.Name, Email = request.Email };
        _dbContext.Users.Add(user);
        await _dbContext.SaveChangesAsync(cancellationToken);
        return user.Id;
    }
}
Enter fullscreen mode Exit fullscreen mode

🔹 Ο Handler αναλαμβάνει την εκτέλεση του command και την αλληλεπίδραση με τη βάση δεδομένων.


Βήμα 4: Ορισμός του Query (Ανάγνωση δεδομένων)

Τα Queries χρησιμοποιούνται για την ανάκτηση δεδομένων.

Παράδειγμα: Ανάκτηση χρήστη βάσει ID (GetUserByIdQuery)

public record GetUserByIdQuery(int Id) : IRequest<User>;
Enter fullscreen mode Exit fullscreen mode

Βήμα 5: Υλοποίηση του Handler για το Query

public class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery, User>
{
    private readonly ApplicationDbContext _dbContext;

    public GetUserByIdQueryHandler(ApplicationDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task<User> Handle(GetUserByIdQuery request, CancellationToken cancellationToken)
    {
        return await _dbContext.Users.FindAsync(request.Id);
    }
}
Enter fullscreen mode Exit fullscreen mode

🔹 Αυτός ο handler επιστρέφει έναν χρήστη από τη βάση δεδομένων χωρίς να τροποποιεί την κατάσταση του συστήματος.


Βήμα 6: Εγγραφή του MediatR στο Dependency Injection (DI)

Στην κλάση Program.cs, καταχωρούμε το MediatR:

builder.Services.AddMediatR(typeof(Program));
Enter fullscreen mode Exit fullscreen mode

Βήμα 7: Χρήση του MediatR στο Controller

Τώρα μπορούμε να καλέσουμε τις εντολές και τα queries μέσω του MediatR.

[ApiController]
[Route("api/users")]
public class UsersController : ControllerBase
{
    private readonly IMediator _mediator;

    public UsersController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpPost]
    public async Task<IActionResult> CreateUser([FromBody] CreateUserCommand command)
    {
        var userId = await _mediator.Send(command);
        return Ok(userId);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetUser(int id)
    {
        var user = await _mediator.Send(new GetUserByIdQuery(id));
        return user != null ? Ok(user) : NotFound();
    }
}
Enter fullscreen mode Exit fullscreen mode

4️⃣ Πλεονεκτήματα της CQRS & MediatR αρχιτεκτονικής

✅ Χαλαρή σύζευξη (Loose Coupling): Τα components επικοινωνούν μέσω του MediatR, χωρίς να εξαρτώνται άμεσα μεταξύ τους.
✅ Καλύτερη συντηρησιμότητα: Η διαχείριση των εντολών και queries γίνεται με ξεκάθαρη οργάνωση.
✅ Διαχωρισμός ευθυνών (Separation of Concerns): Οι queries και οι commands δεν αναμιγνύονται.
✅ Επεκτασιμότητα: Μπορούμε να προσθέσουμε event handlers, logging, caching χωρίς να αλλάξουμε την υπάρχουσα λογική.


🔹 Συμπέρασμα

Το CQRS με MediatR είναι μια πανίσχυρη αρχιτεκτονική προσέγγιση που διαχωρίζει την ανάγνωση από την εγγραφή, βελτιώνει την οργάνωση του κώδικα και επιτρέπει ευελιξία. Είναι ιδανικό για εφαρμογές που απαιτούν υψηλή συντηρησιμότητα και επεκτασιμότητα.

Top comments (0)