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