# MediatR 추상 인터페이스

MediatR의 `IRequest`와 `IRequestHandler`를 래핑한 도메인 의도를 명확히 하는 인터페이스입니다.

## 📚 제공 인터페이스

### Command (쓰기 작업)
```csharp
ICommand                                    // 반환값 없음
ICommand<TResponse>                         // 반환값 있음
ICommandHandler<TCommand>                   // 반환값 없는 Handler
ICommandHandler<TCommand, TResponse>        // 반환값 있는 Handler
```

### Query (읽기 작업)
```csharp
IQuery<TResponse>                           // 항상 반환값 있음
IQueryHandler<TQuery, TResponse>            // Query Handler
```

## 🎯 사용 방법

### 1. 반환값이 없는 Command

```csharp
// 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);
    }
}
```

### 2. 반환값이 있는 Command

```csharp
// 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;
    }
}
```

### 3. Query

```csharp
// 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 { /* ... */ };
    }
}
```

## ✅ 장점

### 1. 의도 명확화
- `ICommand` = 데이터 변경 작업
- `IQuery` = 데이터 조회 작업
- 코드를 읽는 사람이 즉시 이해 가능

### 2. CQRS 원칙 강제
- Command와 Query가 명확히 구분됨
- Query는 반드시 반환값 필요

### 3. Pipeline Behavior 구분 가능
```csharp
// Query에만 캐싱 적용
public class QueryCachingBehavior<TQuery, TResponse>
    : IPipelineBehavior<TQuery, TResponse>
    where TQuery : IQuery<TResponse> // 👈 타입 제약
{
    // ...
}

// Command에만 트랜잭션 적용
if (request is ICommand or ICommand<TResponse>)
{
    // 트랜잭션 시작
}
```

### 4. 타입 안정성
- 컴파일 타임에 타입 체크
- `where TCommand : ICommand` 제약으로 안전성 보장

## 🔄 마이그레이션 가이드

### 기존 코드
```csharp
// ❌ Before
public sealed record Command(...) : IRequest;
public sealed class Handler : IRequestHandler<Command> { }
```

### 새 코드
```csharp
// ✅ After
public sealed record Command(...) : ICommand;
public sealed class Handler : ICommandHandler<Command> { }
```

### 호환성
- 기존 `IRequest` 코드도 계속 동작함
- 새로운 Feature부터 `ICommand`/`IQuery` 사용
- 점진적 마이그레이션 가능

## 📋 적용 규칙

| 작업 유형 | 사용 인터페이스 | 예시 |
|----------|----------------|------|
| 생성 (Create) | `ICommand<TResponse>` | CreateMemberCommand |
| 수정 (Update) | `ICommand` | UpdateMemberCommand |
| 삭제 (Delete) | `ICommand` | DeleteMemberCommand |
| 조회 단건 (Get) | `IQuery<TResponse>` | GetMemberQuery |
| 조회 목록 (Search) | `IQuery<TResponse>` | SearchMembersQuery |

## 🎓 참고 자료

- [MediatR 공식 문서](https://github.com/jbogard/MediatR)
- [CQRS 패턴](https://martinfowler.com/bliki/CQRS.html)
- Clean Architecture - Robert C. Martin
