본문으로 건너뛰기

ApplyT 미적용 코드 전수 조사 보고서

ApplyT는 여러 Fin<T> 결과를 합성하여 FinT<IO, R>로 리프팅하는 확장 메서드입니다. LINQ from 첫 구문에서 사용하면 Unwrap 없이 VO를 합성하고 에러를 수집합니다.

적용 완료: 6개 Command 핸들러 (CreateProduct, UpdateProduct, CreateCustomer, DeductStock + ecommerce-ddd 동일) 미적용: 아래 4가지 범주 (구조적 이유로 ApplyT 적용 불가)


범주 1: 컬렉션 순회 + 다단계 Aggregate 생성

섹션 제목: “범주 1: 컬렉션 순회 + 다단계 Aggregate 생성”
프로젝트파일Unwrap 수
Tests.HostsCreateOrderCommand.cs5개
Tests.HostsCreateOrderWithCreditCheckCommand.cs5개
ecommerce-dddCreateOrderCommand.cs5개
ecommerce-dddCreateOrderWithCreditCheckCommand.cs5개
ecommerce-dddPlaceOrderCommand.cs3개

Order 핸들러는 OrderLineRequest 컬렉션을 순회하면서:

  1. 각 항목의 ProductId와 Quantity로 가격 조회
  2. 가격과 수량으로 OrderLine.Create() 호출
  3. 전체 OrderLine 목록으로 Order.Create() 호출
// 컬렉션 순회 — ApplyT는 고정 tuple만 지원
foreach (var (productId, quantity) in orderLineData)
{
var unitPrice = priceLookup[productId];
orderLines.Add(OrderLine.Create(productId, quantity, unitPrice).Unwrap());
}
var order = Order.Create(customerId, orderLines, shippingAddress).Unwrap();

ApplyT 불가 이유: ApplyT는 2~5 tuple의 고정 크기 합성만 지원. 컬렉션 크기가 동적이므로 tuple Apply 패턴을 사용할 수 없음. 또한 중간에 priceLookup 조회가 필요하여 순차 실행이 불가피.


범주 2: 루프 내 개별 항목 VO 합성

섹션 제목: “범주 2: 루프 내 개별 항목 VO 합성”
프로젝트파일Unwrap 수
Tests.HostsBulkCreateProductsCommand.cs1개
foreach (var item in request.Products)
{
var vos = (Create(), Create(), Create())
.Apply((n, d, p) => (n, d, p)).Unwrap(); // Fin<R>.Unwrap()
products.Add(Product.Create(vos.Name, vos.Desc, vos.Price));
}

ApplyT 불가 이유: ApplyTFinT<IO, R>을 반환하여 LINQ 체인 시작점으로 사용. 그러나 이 코드는 foreach 루프 내부에서 동기적으로 VO를 생성하므로 FinT<IO> 컨텍스트가 아닌 명령형 코드. Apply() + Unwrap()으로 에러 수집 후 추출하는 것이 현재 가능한 최선.


범주 3: Query BuildSpecification (명령형 코드)

섹션 제목: “범주 3: Query BuildSpecification (명령형 코드)”
프로젝트파일Unwrap 수
Tests.HostsSearchProductsQuery.cs3개
Tests.HostsSearchProductsWithStockQuery.cs2개
Tests.HostsSearchProductsWithOptionalStockQuery.cs2개
Tests.HostsSearchInventoryQuery.cs1개
ecommerce-dddSearchProductsQuery.cs3개
ecommerce-dddSearchProductsWithStockQuery.cs2개
ecommerce-dddSearchProductsWithOptionalStockQuery.cs2개
ecommerce-dddSearchInventoryQuery.cs1개
// BuildSpecification — 조건부 Specification 조합
if (!string.IsNullOrEmpty(request.Name))
spec &= new ProductNameSpec(ProductName.Create(request.Name).Unwrap());
if (request.MinPrice > 0 && request.MaxPrice > 0)
spec &= new PriceRangeSpec(
Money.Create(request.MinPrice).Unwrap(),
Money.Create(request.MaxPrice).Unwrap());

ApplyT 불가 이유: BuildSpecificationFinT<IO> LINQ 체인이 아닌 명령형 if 분기. 각 조건이 독립적이므로 tuple Apply로 합성할 수 없음. VO 생성이 조건부(if 가드)이므로 고정 tuple 크기를 결정할 수 없음.


프로젝트파일Unwrap 수
ecommerce-dddDetectLowStockOnStockDeductedHandler.cs1개
private static readonly Quantity DefaultThreshold = Quantity.Create(10).Unwrap();

ApplyT 불가 이유: 클래스 수준 정적 필드 초기화. LINQ 체인이 아니며, 상수 값이므로 실패 불가. Unwrap() 유지가 적절.


범주Tests.Hostsecommerce-ddd합계
ApplyT 적용 완료4개 핸들러4개 핸들러8개
컬렉션 순회 (범주 1)2개3개5개
루프 내 개별 (범주 2)1개0개1개
Query 명령형 (범주 3)4개4개8개
정적 초기화 (범주 4)0개1개1개

ApplyT 미적용 코드는 구조적으로 tuple Apply 패턴이 불가능한 경우에 한정됩니다:

  • 동적 크기 컬렉션 순회
  • 조건부 분기 (if 가드)
  • 명령형 코드 (FinT LINQ 체인 아님)
  • 정적 필드 초기화

이들은 Unwrap() 유지가 적절하며, 파이프라인 Validator가 검증을 완료한 후 Create()가 반드시 성공하는 컨텍스트에서 안전하게 사용됩니다.

기준UnwrapApplyT
VO 개수1~2개3개 이상
에러 처리첫 에러에서 즉시 반환모든 에러를 병렬 수집
코드 스타일명령형 (var x = ...)선언형 (LINQ from)
학습 곡선낮음높음 (모나드 트랜스포머)
적합한 상황간단한 Command, 내부 서비스사용자 입력 폼, 복잡한 검증

판단 기준: VO가 1~2개이고 에러를 병렬 수집할 필요가 없으면 Unwrap이 더 간결합니다. VO가 3개 이상이거나 사용자에게 모든 검증 오류를 한 번에 보여줘야 하면 ApplyT를 사용합니다.