MediatR의 IRequest와 IRequestHandler를 래핑한 도메인 의도를 명확히 하는 인터페이스입니다.
ICommand // 반환값 없음
ICommand<TResponse> // 반환값 있음
ICommandHandler<TCommand> // 반환값 없는 Handler
ICommandHandler<TCommand, TResponse> // 반환값 있는 Handler
IQuery<TResponse> // 항상 반환값 있음
IQueryHandler<TQuery, TResponse> // Query Handler
// Command
public sealed record DeleteMemberCommand(int MemberID) : ICommand;
// Handler
public sealed class Handler(IAppDbContext db)
: ICommandHandler<DeleteMemberCommand>
{
public async Task Handle(DeleteMemberCommand request, CancellationToken ct)
{
await db.Member
.Where(x => x.ID == request.MemberID)
.ExecuteDeleteAsync(ct);
}
}
// Command
public sealed record CreateMemberCommand(
string Email,
string Name
) : ICommand<int>; // 생성된 ID 반환
// Handler
public sealed class Handler(IAppDbContext db)
: ICommandHandler<CreateMemberCommand, int>
{
public async Task<int> Handle(CreateMemberCommand request, CancellationToken ct)
{
var member = Member.Create(request.Email);
await db.Member.AddAsync(member, ct);
await db.SaveChangesAsync(ct);
return member.ID;
}
}
// Query
public sealed record GetMemberQuery(int MemberID) : IQuery<MemberResponse>;
// Handler
public sealed class Handler(IAppDbContext db)
: IQueryHandler<GetMemberQuery, MemberResponse>
{
public async Task<MemberResponse> 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 { /* ... */ };
}
}
ICommand = 데이터 변경 작업IQuery = 데이터 조회 작업// Query에만 캐싱 적용
public class QueryCachingBehavior<TQuery, TResponse>
: IPipelineBehavior<TQuery, TResponse>
where TQuery : IQuery<TResponse> // 👈 타입 제약
{
// ...
}
// Command에만 트랜잭션 적용
if (request is ICommand or ICommand<TResponse>)
{
// 트랜잭션 시작
}
where TCommand : ICommand 제약으로 안전성 보장// ❌ Before
public sealed record Command(...) : IRequest;
public sealed class Handler : IRequestHandler<Command> { }
// ✅ After
public sealed record Command(...) : ICommand;
public sealed class Handler : ICommandHandler<Command> { }
IRequest 코드도 계속 동작함ICommand/IQuery 사용| 작업 유형 | 사용 인터페이스 | 예시 |
|---|---|---|
| 생성 (Create) | ICommand<TResponse> |
CreateMemberCommand |
| 수정 (Update) | ICommand |
UpdateMemberCommand |
| 삭제 (Delete) | ICommand |
DeleteMemberCommand |
| 조회 단건 (Get) | IQuery<TResponse> |
GetMemberQuery |
| 조회 목록 (Search) | IQuery<TResponse> |
SearchMembersQuery |