본문으로 건너뛰기

제약 조건 vs 대안

제네릭 제약 외에 다른 대안은 없을까요? 이 부록에서는 Functorium이 선택한 인터페이스 제약 조건 방식과 다른 대안들을 비교합니다. 각 접근 방식의 장단점을 분석하여, 왜 인터페이스 계층 + 제네릭 제약이 최선의 선택인지 이해합니다.


1. 인터페이스 제약 조건 (Functorium 방식)

섹션 제목: “1. 인터페이스 제약 조건 (Functorium 방식)”
where TResponse : IFinResponse, IFinResponseFactory<TResponse>
항목평가
타입 안전성컴파일 타임에 보장
리플렉션불필요 (0곳)
성능최적 (static dispatch)
코드 복잡도인터페이스 계층 설계 필요
확장성새 인터페이스 추가로 확장 가능
IDE 지원자동 완성, 리팩토링 완전 지원
// Pipeline 내부에서 런타임 타입 검사
var isSuccProp = typeof(TResponse).GetProperty("IsSucc");
var isSucc = (bool)isSuccProp!.GetValue(response)!;
// CreateFail 호출도 리플렉션 필요
var createFail = typeof(TResponse).GetMethod("CreateFail", BindingFlags.Static | BindingFlags.Public);
var failResponse = (TResponse)createFail!.Invoke(null, [error])!;
항목평가
타입 안전성런타임에만 검증 (컴파일 타임 보장 없음)
리플렉션다수 필요 (3곳 이상)
성능리플렉션 오버헤드 (매 요청마다)
코드 복잡도Pipeline 내부가 복잡해짐
확장성새 속성/메서드 추가 시 리플렉션 코드도 변경 필요
IDE 지원문자열 기반이라 리팩토링 시 누락 위험
public TResponse Handle(dynamic request, Func<TResponse> next)
{
dynamic response = next();
if (response.IsSucc) { ... }
return response;
}
항목평가
타입 안전성없음 (모든 검사가 런타임)
리플렉션내부적으로 리플렉션 사용
성능리플렉션 + DLR 오버헤드
코드 복잡도간단하지만 안전하지 않음
확장성오타 발견 불가, 런타임 에러
IDE 지원자동 완성 불가
// Source Generator가 Pipeline 코드를 자동 생성
[GeneratePipeline]
public partial class ValidationPipeline<TResponse> { }
항목평가
타입 안전성생성된 코드는 타입 안전
리플렉션불필요
성능최적 (컴파일 타임 생성)
코드 복잡도Generator 자체가 복잡
확장성Generator 수정 필요 (학습 곡선 높음)
IDE 지원Generator에 따라 다름
public object Handle(object request, Func<object> next)
{
var response = next();
if (response is IFinResponse fin && fin.IsSucc) { ... }
return response;
}
항목평가
타입 안전성부분적 (캐스팅 실패 가능)
리플렉션불필요하지만 박싱 발생
성능박싱/언박싱 오버헤드
코드 복잡도캐스팅 코드가 산재
확장성새 타입 추가 시 캐스팅 코드 변경 필요
IDE 지원제한적

다섯 가지 접근 방식을 핵심 기준별로 비교하면 다음과 같습니다.

기준인터페이스 제약리플렉션dynamicSource Genobject 캐스팅
컴파일 타임 안전성OXXO부분
리플렉션 없음OXXOO
성능 최적OXXO부분
설계 비용
유지보수OXXX
IDE 지원OXX부분

왜 인터페이스 제약을 선택했는가?

섹션 제목: “왜 인터페이스 제약을 선택했는가?”

모든 요청이 Pipeline을 거치므로, 리플렉션이나 dynamic의 성능 오버헤드는 누적됩니다.

2. 실수를 컴파일러가 잡아야 한다

섹션 제목: “2. 실수를 컴파일러가 잡아야 한다”

Pipeline 제약이 잘못되면 런타임 예외가 발생합니다. 인터페이스 제약은 이를 컴파일 타임에 방지합니다.

3. CRTP로 static abstract 호출이 가능

섹션 제목: “3. CRTP로 static abstract 호출이 가능”

C# 11의 static abstract 멤버와 CRTP 패턴을 결합하면, 인터페이스에서 정적 팩토리 메서드를 호출할 수 있습니다. 이것이 리플렉션 없는 CreateFail 호출의 핵심입니다.

각 Pipeline이 필요한 능력만 제약으로 요구하므로, 불필요한 의존성이 없습니다. Validation Pipeline은 CreateFail만 필요하므로 IFinResponseFactory<TResponse>만 제약합니다.


FinResponse<A>의 Map, Bind 체인이 구현하는 Railway Oriented Programming 패턴과, Pipeline과 ROP의 관계를 살펴봅니다.

부록 C: Railway Oriented Programming 참조