본문으로 건너뛰기

파이프라인 요구사항 정리

세 가지 접근을 시도한 결과, 각각의 한계가 드러났습니다. Pipeline 구조를 분석하여 where 제약의 역할을 확인했고(1장), Fin<T>를 직접 사용하면 리플렉션 3곳이 필요하다는 것을 발견했으며(2장), 래퍼 인터페이스로 1곳까지 줄였지만 CreateFail은 여전히 해결하지 못했습니다(3장). 이 장에서는 이 분석 결과를 바탕으로 응답 타입 시스템의 4가지 요구사항을 정리하고, 각 접근 방식이 이를 얼마나 충족하는지 비교합니다.


1. 응답 타입 시스템의 4가지 요구사항

섹션 제목: “1. 응답 타입 시스템의 4가지 요구사항”

Pipeline에서 응답 타입을 안전하게 처리하려면 다음 4가지 요구사항이 충족되어야 합니다.

R1: Pipeline에서 성공/실패 상태를 직접 읽을 수 있어야 함 (리플렉션 없이)

섹션 제목: “R1: Pipeline에서 성공/실패 상태를 직접 읽을 수 있어야 함 (리플렉션 없이)”

Logging, Tracing, Metrics Pipeline은 응답의 성공/실패 상태를 확인해야 합니다. 이 정보는 컴파일 타임에 보장된 인터페이스 멤버로 접근 가능해야 합니다.

// 요구사항: 리플렉션 없이 직접 접근
if (response.IsSucc)
LogSuccess();
else
LogFailure();

R2: Pipeline에서 실패 응답을 직접 생성할 수 있어야 함 (static abstract)

섹션 제목: “R2: Pipeline에서 실패 응답을 직접 생성할 수 있어야 함 (static abstract)”

Validation, Exception Pipeline은 실패 응답을 직접 생성해야 합니다. 이를 위해 static abstract 팩토리 메서드가 필요합니다.

// 요구사항: 타입 안전한 실패 응답 생성
return TResponse.CreateFail(Error.New("Validation failed"));

R3: Pipeline에서 에러 정보에 접근할 수 있어야 함

섹션 제목: “R3: Pipeline에서 에러 정보에 접근할 수 있어야 함”

Logging, Tracing Pipeline은 실패 시 에러 정보(에러 메시지, 에러 코드 등)에 접근해야 합니다.

// 요구사항: 에러 정보 직접 접근
if (response is IFinResponseWithError fail)
RecordError(fail.Error);

R4: sealed struct(Fin)의 성공/실패를 래핑하지 않고 직접 표현할 수 있어야 함

섹션 제목: “R4: sealed struct(Fin)의 성공/실패를 래핑하지 않고 직접 표현할 수 있어야 함”

Fin<T>를 별도의 래퍼로 감싸면 이중 인터페이스 문제가 발생합니다. 응답 타입 자체가 성공/실패를 직접 표현하는 Discriminated Union이어야 합니다.

// 요구사항: 래퍼 없이 직접 표현
FinResponse<string> success = FinResponse.Succ("OK");
FinResponse<string> fail = FinResponse.Fail<string>(Error.New("error"));

각 Pipeline이 응답 타입에 대해 필요로 하는 능력은 다릅니다. 다음 매트릭스는 Pipeline마다 R1-R4 중 어떤 능력이 필요한지를 보여주며, Part 3에서 설계할 인터페이스 계층의 분리 근거가 됩니다.

Pipeline읽기 (R1)생성 (R2)에러 접근 (R3)직접 표현 (R4)
ValidationO
ExceptionO
LoggingOO
TracingOO
MetricsOO
TransactionO
CachingO

핵심 관찰:

  • Validation/Exception: 실패 응답 생성만 필요 (Create-Only)
  • Logging/Tracing/Metrics: 성공/실패 읽기 + 에러 정보 접근 필요
  • Transaction/Caching: 성공/실패 읽기만 필요

이 차이가 Part 3에서 설계할 인터페이스 계층의 근거가 됩니다.

실제 Functorium에서는 Custom Pipeline 슬롯도 제공합니다. Custom Pipeline의 필요 능력(R1-R4)은 구현에 따라 다르며, IFinResponse 계층의 인터페이스를 조합하여 원하는 제약을 적용합니다.


접근 방식R1R2R3R4리플렉션
Fin<T> 직접 (2장)XXXO3곳
IFinResponse 래퍼 (3장)XOX1곳
IFinResponse 계층 (Part 3)OOOO0곳
  • 장점: Fin<T> 자체가 성공/실패를 직접 표현 (R4 충족)
  • 단점: sealed struct라 제약 불가, 리플렉션 3곳 필요
  • 장점: 리플렉션을 1곳(is 캐스팅)으로 감소, 에러 접근 가능 (R3 충족)
  • 단점: CreateFail 불가 (R2 미충족), 이중 인터페이스 (R4 미충족)
  • R1이 △인 이유: is 캐스팅으로 접근 가능하지만, 컴파일 타임 보장이 아님
  • 장점: 4가지 요구사항 모두 충족, 리플렉션 0곳
  • 핵심 아이디어: 인터페이스 분리 원칙(ISP) + static abstract 멤버 + CRTP 패턴

Part 3에서는 다음 인터페이스 계층을 하나씩 설계합니다:

인터페이스충족 요구사항핵심 멤버
IFinResponseR1IsSucc, IsFail
IFinResponse<out A>R1 + 공변성값 접근
IFinResponseFactory<TSelf>R2static abstract CreateFail(Error)
IFinResponseWithErrorR3Error 속성
FinResponse<A>R4Succ/Fail Discriminated Union

각 인터페이스는 하나의 요구사항을 해결하며, Pipeline은 필요한 인터페이스만 제약 조건으로 사용합니다.

// Create-Only: Validation, Exception
where TResponse : IFinResponseFactory<TResponse>
// Read + Create: Logging, Tracing, Metrics, Transaction, Caching
where TResponse : IFinResponse, IFinResponseFactory<TResponse>

Q1: 4가지 요구사항(R1~R4)을 모두 충족하지 않으면 어떤 문제가 생기나요?

섹션 제목: “Q1: 4가지 요구사항(R1~R4)을 모두 충족하지 않으면 어떤 문제가 생기나요?”

A: R1이 없으면 성공/실패 확인에 리플렉션이 필요하고, R2가 없으면 실패 응답 생성에 리플렉션이 필요합니다. R3이 없으면 에러 정보에 접근할 수 없고, R4가 없으면 이중 인터페이스로 설계가 복잡해집니다. 4가지를 모두 충족해야 리플렉션 0곳의 타입 안전한 Pipeline이 가능합니다.

Q2: R1(읽기)과 R2(생성)를 하나의 인터페이스로 합치면 안 되나요?

섹션 제목: “Q2: R1(읽기)과 R2(생성)를 하나의 인터페이스로 합치면 안 되나요?”

A: 합칠 수는 있지만, 인터페이스 분리 원칙(ISP)에 위배됩니다. Validation Pipeline은 생성만 필요하고, Transaction Pipeline은 읽기만 필요합니다. 하나로 합치면 불필요한 능력까지 제약하게 되어, Pipeline의 의도가 코드에 드러나지 않습니다.

Q3: 래퍼 방식의 R1이 왜 △(삼각형)인가요?

섹션 제목: “Q3: 래퍼 방식의 R1이 왜 △(삼각형)인가요?”

A: 래퍼 방식에서 is IFinResponseWrapper 캐스팅으로 성공/실패에 접근할 수 있지만, 이는 런타임 타입 검사입니다. where 제약처럼 컴파일 타임에 접근을 보장하는 것이 아니므로, 완전한 충족(O)이 아닌 부분 충족(△)으로 평가됩니다.

Q4: Part 3에서 설계할 인터페이스 계층은 몇 개의 인터페이스로 구성되나요?

섹션 제목: “Q4: Part 3에서 설계할 인터페이스 계층은 몇 개의 인터페이스로 구성되나요?”

A: 5개입니다. IFinResponse(비제네릭 마커), IFinResponse<out A>(공변 인터페이스), IFinResponseFactory<TSelf>(CRTP 팩토리), IFinResponseWithError(에러 접근), FinResponse<A>(Discriminated Union)로, 각각 하나의 요구사항을 해결합니다.


Part 3의 첫 번째 단계로, 요구사항 R1(성공/실패 읽기)을 해결하는 비제네릭 마커 인터페이스 IFinResponse를 설계합니다.

3.1장: IFinResponse 비제네릭 마커