# MediatR 추상 인터페이스 MediatR의 `IRequest`와 `IRequestHandler`를 래핑한 도메인 의도를 명확히 하는 인터페이스입니다. ## 📚 제공 인터페이스 ### Command (쓰기 작업) ```csharp ICommand // 반환값 없음 ICommand // 반환값 있음 ICommandHandler // 반환값 없는 Handler ICommandHandler // 반환값 있는 Handler ``` ### Query (읽기 작업) ```csharp IQuery // 항상 반환값 있음 IQueryHandler // Query Handler ``` ## 🎯 사용 방법 ### 1. 반환값이 없는 Command ```csharp // Command public sealed record DeleteMemberCommand(int MemberID) : ICommand; // Handler public sealed class Handler(IAppDbContext db) : ICommandHandler { public async Task Handle(DeleteMemberCommand request, CancellationToken ct) { await db.Member .Where(x => x.ID == request.MemberID) .ExecuteDeleteAsync(ct); } } ``` ### 2. 반환값이 있는 Command ```csharp // Command public sealed record CreateMemberCommand( string Email, string Name ) : ICommand; // 생성된 ID 반환 // Handler public sealed class Handler(IAppDbContext db) : ICommandHandler { public async Task Handle(CreateMemberCommand request, CancellationToken ct) { var member = Member.Create(request.Email); await db.Member.AddAsync(member, ct); await db.SaveChangesAsync(ct); return member.ID; } } ``` ### 3. Query ```csharp // Query public sealed record GetMemberQuery(int MemberID) : IQuery; // Handler public sealed class Handler(IAppDbContext db) : IQueryHandler { public async Task Handle(GetMemberQuery request, CancellationToken ct) { var member = await db.Member .AsNoTracking() .FirstOrDefaultAsync(x => x.ID == request.MemberID, ct); if (member is null) throw new KeyNotFoundException(); return new MemberResponse { /* ... */ }; } } ``` ## ✅ 장점 ### 1. 의도 명확화 - `ICommand` = 데이터 변경 작업 - `IQuery` = 데이터 조회 작업 - 코드를 읽는 사람이 즉시 이해 가능 ### 2. CQRS 원칙 강제 - Command와 Query가 명확히 구분됨 - Query는 반드시 반환값 필요 ### 3. Pipeline Behavior 구분 가능 ```csharp // Query에만 캐싱 적용 public class QueryCachingBehavior : IPipelineBehavior where TQuery : IQuery // 👈 타입 제약 { // ... } // Command에만 트랜잭션 적용 if (request is ICommand or ICommand) { // 트랜잭션 시작 } ``` ### 4. 타입 안정성 - 컴파일 타임에 타입 체크 - `where TCommand : ICommand` 제약으로 안전성 보장 ## 🔄 마이그레이션 가이드 ### 기존 코드 ```csharp // ❌ Before public sealed record Command(...) : IRequest; public sealed class Handler : IRequestHandler { } ``` ### 새 코드 ```csharp // ✅ After public sealed record Command(...) : ICommand; public sealed class Handler : ICommandHandler { } ``` ### 호환성 - 기존 `IRequest` 코드도 계속 동작함 - 새로운 Feature부터 `ICommand`/`IQuery` 사용 - 점진적 마이그레이션 가능 ## 📋 적용 규칙 | 작업 유형 | 사용 인터페이스 | 예시 | |----------|----------------|------| | 생성 (Create) | `ICommand` | CreateMemberCommand | | 수정 (Update) | `ICommand` | UpdateMemberCommand | | 삭제 (Delete) | `ICommand` | DeleteMemberCommand | | 조회 단건 (Get) | `IQuery` | GetMemberQuery | | 조회 목록 (Search) | `IQuery` | SearchMembersQuery | ## 🎓 참고 자료 - [MediatR 공식 문서](https://github.com/jbogard/MediatR) - [CQRS 패턴](https://martinfowler.com/bliki/CQRS.html) - Clean Architecture - Robert C. Martin