본문으로 건너뛰기

Fin→FinResponse 브릿지

기존 Fin<T> 기반 코드와 새로운 FinResponse<T>를 어떻게 연결할까요? Repository 계층은 Fin<T>를 반환하고, Usecase 계층은 FinResponse<T>를 반환합니다. 이 두 계층을 연결하는 브릿지ToFinResponse() 확장 메서드입니다. 이 장에서는 다양한 변환 오버로드와 사용 시나리오를 학습합니다.

계층 간 타입 흐름:
Repository Layer Usecase Layer
───────────────── ─────────────────
Fin<Product> ──→ FinResponse<Product> 직접 변환
Fin<Product> ──→ FinResponse<ProductDto> 매퍼 변환
Fin<Unit> ──→ FinResponse<string> 팩토리 변환
Fin<T> (Fail) ──→ FinResponse<T> (Fail) 실패 전파
Fin<int> ──→ FinResponse<string> 커스텀 변환

이 장을 완료하면 다음을 할 수 있습니다:

  1. Repository(Fin<T>)와 Usecase(FinResponse<T>) 계층 간 변환이 필요한 이유를 설명할 수 있습니다
  2. 상황에 맞는 ToFinResponse() 오버로드를 선택할 수 있습니다
  3. 실패 상태가 변환 시 자동으로 전파되는 메커니즘을 이해할 수 있습니다
  • Fin<T>: LanguageExt의 Result 타입. sealed struct이므로 제약 조건으로 사용 불가.
  • FinResponse<T>: IFinResponse 인터페이스 계층을 구현한 Discriminated Union. Pipeline 제약에 사용 가능.

Repository가 반환하는 Fin<T>를 Usecase의 FinResponse<T>로 변환해야 Pipeline 체인에서 타입 안전하게 처리할 수 있습니다.

2. 직접 변환: Fin<A> -> FinResponse<A>

섹션 제목: “2. 직접 변환: Fin<A> -> FinResponse<A>”

가장 단순한 변환입니다. 성공 값의 타입이 동일할 때 사용합니다.

Fin<string> fin = Fin<string>.Succ("Hello");
FinResponse<string> response = fin.ToFinResponse();

3. 매퍼 변환: Fin<A> -> FinResponse<B>

섹션 제목: “3. 매퍼 변환: Fin<A> -> FinResponse<B>”

성공 값의 타입을 변환할 때 사용합니다. 예: Entity -> DTO

Fin<string> fin = Fin<string>.Succ("Hello");
FinResponse<int> response = fin.ToFinResponse(s => s.Length);

4. 팩토리 변환: Fin<A> -> FinResponse<B>

섹션 제목: “4. 팩토리 변환: Fin<A> -> FinResponse<B>”

원본 성공 값을 무시하고 새로운 값을 생성할 때 사용합니다. 예: Fin<Unit> -> FinResponse<string>

Fin<Unit> fin = Fin<Unit>.Succ(Unit.Default);
FinResponse<string> response = fin.ToFinResponse(() => "Deleted successfully");

Fin이 실패 상태이면, 변환 방식에 관계없이 Error가 그대로 FinResponse의 Fail로 전파됩니다.

Fin<string> fin = Fin<string>.Fail(Error.New("not found"));
FinResponse<string> response = fin.ToFinResponse();
// response.IsFail == true

6. 커스텀 변환: Fin<A> -> FinResponse<B> (onSucc/onFail)

섹션 제목: “6. 커스텀 변환: Fin<A> -> FinResponse<B> (onSucc/onFail)”

성공과 실패 모두에 대해 커스텀 처리가 필요한 경우 사용합니다. 예: 성공 값을 다른 타입으로 변환하면서, 실패 시에도 별도 에러 처리를 적용하는 경우.

Fin<int> fin = Fin.Succ(42);
FinResponse<string> response = fin.ToFinResponse(
onSucc: value => FinResponse.Succ($"Value is {value}"),
onFail: error => FinResponse.Fail<string>(error));

이 오버로드는 FinMatch와 동일한 구조이므로 가장 유연하지만, 대부분의 경우 직접/매퍼/팩토리 변환으로 충분합니다.

ToFinResponse()가 제공하는 변환 오버로드를 정리하면 다음과 같습니다.

오버로드시그니처용도
직접 변환Fin<A>.ToFinResponse()동일 타입 변환
매퍼 변환Fin<A>.ToFinResponse(Func<A, B>)Entity -> DTO
팩토리 변환Fin<A>.ToFinResponse(Func<B>)Unit -> Response
커스텀 변환Fin<A>.ToFinResponse(Func<A, FinResponse<B>>, Func<Error, FinResponse<B>>)완전 제어

Q1: Repository가 Fin<T>를 반환하고 Usecase가 FinResponse<T>를 반환하는 이유는 무엇인가요?

섹션 제목: “Q1: Repository가 Fin<T>를 반환하고 Usecase가 FinResponse<T>를 반환하는 이유는 무엇인가요?”

A: Repository는 LanguageExt의 순수 함수형 타입인 Fin<T>를 사용하여 외부 라이브러리 의존 없이 성공/실패를 표현합니다. Usecase는 Pipeline 제약에 사용 가능한 FinResponse<T>를 반환해야 합니다. ToFinResponse()가 이 두 계층을 연결합니다.

Q2: 매퍼 변환과 팩토리 변환은 각각 어떤 상황에서 사용하나요?

섹션 제목: “Q2: 매퍼 변환과 팩토리 변환은 각각 어떤 상황에서 사용하나요?”

A: 매퍼 변환(Func<A, B>)은 Entity를 DTO로 변환할 때 사용합니다. 예: fin.ToFinResponse(product => new ProductDto(product)). 팩토리 변환(Func<B>)은 원본 값을 무시하고 새로운 값을 생성할 때 사용합니다. 예: Fin<Unit> 반환을 FinResponse<string>의 “삭제 성공” 메시지로 변환.

Q3: ToFinResponse()에서 실패가 자동 전파되는 원리는 무엇인가요?

섹션 제목: “Q3: ToFinResponse()에서 실패가 자동 전파되는 원리는 무엇인가요?”

A: ToFinResponse()는 내부적으로 Fin<T>Match를 호출합니다. Succ이면 변환 함수를 적용하고, Fail이면 변환 함수를 호출하지 않고 Error를 그대로 FinResponse.Fail로 전달합니다. 어떤 오버로드를 사용하든 실패 시 동일하게 동작합니다.

04-Fin-To-FinResponse-Bridge/
├── FinToFinResponseBridge/
│ ├── FinToFinResponseBridge.csproj
│ ├── BridgeExamples.cs
│ └── Program.cs
├── FinToFinResponseBridge.Tests.Unit/
│ ├── FinToFinResponseBridge.Tests.Unit.csproj
│ ├── xunit.runner.json
│ └── FinToFinResponseBridgeTests.cs
└── README.md
Terminal window
# 프로그램 실행
dotnet run --project FinToFinResponseBridge
# 테스트 실행
dotnet test --project FinToFinResponseBridge.Tests.Unit

Pipeline 제약 패턴이 완성되었습니다. Nested class 패턴으로 Request/Response/Validator/Handler를 구성하는 Command Usecase의 완전한 구현 예제를 작성합니다.

5.1장: Command Usecase 완전 예제