C#

C# 기초부터 고급까지 Chapter 3.9. CQRS + MediatR 패턴 실전 예제

Juan_ 2025. 5. 13. 22:34
728x90

📘 Chapter 3.9: CQRS + MediatR 패턴 실전 예제


✅ 이 챕터에서 배울 것

  • CQRS란 무엇인가? 왜 필요한가?
  • Command vs Query 구조 분리
  • MediatR이란? 왜 쓰는가?
  • 실무 예제: 사용자 등록 + 사용자 조회
  • 구조 설계 (핸들러, 요청 객체, 응답 객체)
  • 테스트와 확장에 유리한 구조

1️⃣ CQRS란?

Command Query Responsibility Segregation
→ 명령과 조회를 책임에 따라 분리하자!
구분설명
Command데이터를 변경하는 요청 (등록, 수정, 삭제)
Query데이터를 읽기 위한 요청 (검색, 조회)

✅ 서로 다른 목적이므로 모델, 핸들러, 구조 분리하는 게 유리함


2️⃣ MediatR이란?

요청(Request)을 보내면, 중재자(Mediator)가 적절한 처리자(Handler)에게 전달해주는 구조
Controller → MediatR.Send(request) → 해당 Handler 실행

✅ 핵심:

  • 요청 객체 (Command or Query)
  • 핸들러 클래스 (핵심 로직 담당)
  • 의존성 낮고 테스트하기 쉬움!

3️⃣ 실전 예제: 사용자 등록 + 사용자 조회

🧩 도메인 시나리오

  • POST /users → 사용자 등록
  • GET /users/{id} → 사용자 정보 조회

📁 구조 개요

/Application
  /Users
    - CreateUserCommand.cs
    - CreateUserHandler.cs
    - GetUserQuery.cs
    - GetUserHandler.cs

/Domain
  - User.cs

/Infrastructure
  - UserRepository.cs

/API
  - UsersController.cs

4️⃣ 코드 예제 – Command & Handler

✅ CreateUserCommand.cs

public record CreateUserCommand(string Name, string Email) 
    : IRequest<Guid>;

✅ CreateUserHandler.cs

public class CreateUserHandler : IRequestHandler<CreateUserCommand, Guid>
{
    private readonly IUserRepository _repo;

    public CreateUserHandler(IUserRepository repo)
    {
        _repo = repo;
    }

    public async Task<Guid> Handle(CreateUserCommand request, CancellationToken ct)
    {
        var user = new User(request.Name, request.Email);
        await _repo.AddAsync(user);
        return user.Id;
    }
}

✅ User.cs (Domain Entity)

public class User
{
    public Guid Id { get; private set; } = Guid.NewGuid();
    public string Name { get; private set; }
    public string Email { get; private set; }

    public User(string name, string email)
    {
        Name = name;
        Email = email;
    }
}

5️⃣ 코드 예제 – Query & Handler

✅ GetUserQuery.cs

public record GetUserQuery(Guid Id) 
    : IRequest<UserDto>;

✅ GetUserHandler.cs

public class GetUserHandler : IRequestHandler<GetUserQuery, UserDto>
{
    private readonly IUserRepository _repo;

    public GetUserHandler(IUserRepository repo)
    {
        _repo = repo;
    }

    public async Task<UserDto> Handle(GetUserQuery request, CancellationToken ct)
    {
        var user = await _repo.GetByIdAsync(request.Id);
        return new UserDto(user.Id, user.Name, user.Email);
    }
}

✅ UserDto.cs

public record UserDto(Guid Id, string Name, string Email);

6️⃣ API 연결 – UsersController.cs

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

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

    [HttpPost]
    public async Task<IActionResult> Create(CreateUserCommand cmd)
    {
        var id = await _mediator.Send(cmd);
        return Ok(id);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid id)
    {
        var user = await _mediator.Send(new GetUserQuery(id));
        return Ok(user);
    }
}

7️⃣ 장점 요약

항목장점
구조 분리Command/Query 목적 명확화
확장성핸들러만 추가하면 끝
테스트 용이각 유즈케이스 단위 테스트 가능
의존성 낮음Controller는 단순 중개자 역할만
이벤트 처리 연동도 쉬움MediatR + Notification 조합 가능!

✅ 정리 요약

항목설명
CQRS읽기/쓰기 분리로 설계 명확화
MediatR요청-응답 구조의 중심 중재자 역할
Command데이터 변경 책임 (등록, 수정, 삭제)
Query데이터 읽기 책임
실무 장점유지보수, 테스트, 확장성 ↑

📢 다음 챕터 예고 🎓

다음 챕터주제
Chapter 3.10.NET Core로 REST API 설계 – 보안, 인증 포함
728x90