# Messaging 추상 인터페이스 (사내 CQRS 디스패처) MediatR 의존성 없이 CQRS 의도를 명확히 표현하는 인터페이스입니다. (2026-07 MediatR 제거 — 라이선스 회피 + 순수 C# 우선) 호출부는 `ISender` / `IMediator`를 주입받아 `Send(command/query, ct)`로 디스패치하며, 실제 디스패치는 `Application/Messaging/Sender.cs`(리플렉션 + 캐싱)가 처리합니다. ## 📚 제공 인터페이스 ### 마커 / 디스패처 ```csharp IRequest // 반환값 없는 요청 공통 마커 IRequest // 반환값 있는 요청 공통 마커 ISender / IMediator // 디스패처 (Sender가 구현, IMediator : ISender) ``` ### Command (쓰기 작업) ```csharp ICommand // 반환값 없음 (: IRequest) ICommand // 반환값 있음 (: IRequest) ICommandHandler // Task Handle(...) ICommandHandler // Task Handle(...) ``` ### Query (읽기 작업) ```csharp IQuery // 항상 반환값 있음 (: IRequest) IQueryHandler // Task Handle(...) ``` > `TResponse`는 Handler가 **실제로 반환하는 타입**입니다. `Result` 래핑 여부는 > 각 Command/Query가 직접 선언합니다. 예: `ICommand>` → Handler는 `Task>` 반환. ## 🎯 사용 방법 ### 1. 반환값이 없는 Command ```csharp public sealed record DeleteMemberCommand(int MemberID) : ICommand; 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 public sealed record CreateMemberCommand(string Email, string Name) : ICommand; 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 public sealed record GetMemberQuery(int MemberID) : IQuery; public sealed class Handler(IAppDbContext db) : IQueryHandler { public async Task Handle(GetMemberQuery request, CancellationToken ct) { // ... } } ``` ### 호출 (Endpoint / Razor Page) ```csharp // 둘 다 동일하게 동작합니다 (IMediator : ISender) var result = await sender.Send(new GetMemberQuery(memberID), ct); await mediator.Send(new DeleteMemberCommand(memberID), ct); ``` ## ⚙️ 동작 원리 - `AddApplication()`이 **리플렉션 스캔**으로 `ICommandHandler<>` / `ICommandHandler<,>` / `IQueryHandler<,>` 구현체를 모두 **Scoped** 등록하고, `ISender` / `IMediator`(= 단일 `Sender`)를 등록합니다. - `Sender`는 요청의 **런타임 타입**으로 Handler를 찾아(타입별 1회 생성 후 캐시) 호출하며, 처리 시간 / 느린 요청(500ms) / 실패를 로깅합니다. - Handler 라우팅은 **클래스 이름이 아니라 `ICommand` / `IQuery` 상속**으로 결정됩니다. 요청은 둘 중 **정확히 하나만** 구현해야 하며, 아니면 명확한 예외로 즉시 실패합니다. ## ✅ 적용 규칙 | 작업 유형 | 사용 인터페이스 | 예시 | |----------|----------------|------| | 생성 (Create) | `ICommand` | CreateMemberCommand | | 수정 (Update) | `ICommand` | UpdateMemberCommand | | 삭제 (Delete) | `ICommand` | DeleteMemberCommand | | 조회 (Get/Search) | `IQuery` | GetMemberQuery | ## 🎓 참고 - 디스패처 구현: `Application/Messaging/Sender.cs` - 등록: `Application/DependencyInjection.cs` - CQRS 패턴: https://martinfowler.com/bliki/CQRS.html