Apply 병렬 검증
사용자가 회원가입 폼에서 이메일, 비밀번호, 이름, 나이를 모두 잘못 입력했다고 가정합니다. Bind 패턴을 사용하면 이메일 오류만 보고하고 중단됩니다. 사용자는 이메일을 고치고, 다시 제출하고, 이번에는 비밀번호 오류를 받고… 이 과정을 네 번 반복해야 합니다. Apply 패턴은 모든 검증을 동시에 실행하여 모든 에러를 한 번에 수집합니다.
학습 목표
섹션 제목: “학습 목표”- 서로 독립적인 검증 규칙들을 동시에 실행하는 Apply 연산자의 병렬 실행 메커니즘을 이해할 수 있습니다.
- 모든 검증 실패를 한 번에 수집하여 사용자에게 완전한 피드백을 제공하는 에러 수집 패턴을 구현할 수 있습니다.
- 복수개의 에러를 구조화된 방식으로 처리하는 ManyErrors 타입을 활용할 수 있습니다.
왜 필요한가?
섹션 제목: “왜 필요한가?”이전 단계인 Bind 순차 검증에서는 의존적인 검증 규칙들을 순차적으로 실행하는 방법을 학습했습니다. 하지만 서로 독립적인 정보들을 검증해야 하는 상황에서는 다른 접근이 필요합니다.
사용자 경험 측면에서, 여러 필드에 잘못된 값을 입력했을 때 모든 문제점을 한 번에 보여주는 것이 훨씬 효율적입니다. 이메일 형식이 틀렸다고 해서 비밀번호나 이름 검증을 중단할 이유가 없습니다. 서로 독립적인 검증들을 동시에 실행하면 전체 검증 시간도 단축할 수 있고, 모든 검증 실패를 한꺼번에 수집하여 구조화된 에러 정보를 제공할 수도 있습니다.
Apply 병렬 검증 패턴은 독립적인 검증 규칙들을 병렬로 실행하여 이 모든 요구를 충족합니다.
핵심 개념
섹션 제목: “핵심 개념”Apply 병렬 실행 메커니즘
섹션 제목: “Apply 병렬 실행 메커니즘”Apply는 서로 의존성이 없는 검증 규칙들을 동시에 실행합니다. 어느 하나가 실패해도 나머지 검증은 계속 진행되며, 실패한 결과들은 모두 수집됩니다.
다음 코드는 순차 실행 방식의 한계를 보여줍니다.
// 이전 방식 (문제가 있는 방식) - 순차적으로 실행하여 비효율적public static Validation<Error, UserRegistration> ValidateOld(string email, string password, string name, string ageInput){ var emailResult = ValidateEmail(email); if (emailResult.IsFail) return emailResult; // 조기 중단으로 다른 검증 생략
var passwordResult = ValidatePassword(password); if (passwordResult.IsFail) return passwordResult; // 조기 중단으로 다른 검증 생략 // 사용자가 모든 문제를 한 번에 파악할 수 없음}Apply 구현 방법 비교
섹션 제목: “Apply 구현 방법 비교”LanguageExt에서 Apply 병렬 검증을 구현하는 두 가지 방법이 있습니다.
방법 1: 튜플 기반 Apply (권장)
섹션 제목: “방법 1: 튜플 기반 Apply (권장)”여러 Validation을 튜플로 묶어서 한 번에 Apply를 호출하는 방식입니다.
public static Validation<Error, (string Email, string Password, string Name, int Age)> Validate( string email, string password, string name, string ageInput) => (ValidateEmailFormat(email), ValidatePasswordStrength(password), ValidateNameFormat(name), ValidateAgeFormat(ageInput)) .Apply((validEmail, validPassword, validName, validAge) => (Email: validEmail, Password: validPassword, Name: validName, Age: validAge)) .As();간결하고 직관적이며, 검증 개수가 명확하게 드러나므로 대부분의 상황에서 권장됩니다.
방법 2: fun 기반 개별 Apply
섹션 제목: “방법 2: fun 기반 개별 Apply”fun 함수를 사용하여 Currying 방식으로 개별 Apply를 체이닝하는 방식입니다.
using static LanguageExt.Prelude;
public static Validation<Error, (string Email, string Password, string Name, int Age)> Validate( string email, string password, string name, string ageInput) => fun((string e, string p, string n, int a) => (Email: e, Password: p, Name: n, Age: a)) .Map(f => Success<Error, Func<string, string, string, int, (string, string, string, int)>>(f)) .Apply(ValidateEmailFormat(email)) .Apply(ValidatePasswordStrength(password)) .Apply(ValidateNameFormat(name)) .Apply(ValidateAgeFormat(ageInput));또는 Pure를 사용하여 더 간결하게 작성할 수 있습니다.
public static Validation<Error, (string Email, string Password, string Name, int Age)> Validate( string email, string password, string name, string ageInput) => Pure<Validation<Error>, Func<string, string, string, int, (string, string, string, int)>>( fun((string e, string p, string n, int a) => (Email: e, Password: p, Name: n, Age: a))) .Apply(ValidateEmailFormat(email)) .Apply(ValidatePasswordStrength(password)) .Apply(ValidateNameFormat(name)) .Apply(ValidateAgeFormat(ageInput));이 방식은 Currying을 통한 단계적 적용으로 유연성을 확보하며, 동적으로 검증 개수를 조절할 때 유용합니다.
두 방법 비교
섹션 제목: “두 방법 비교”다음 표는 두 가지 Apply 구현 방법의 특성을 비교합니다.
| 구분 | 튜플 기반 Apply | fun 기반 개별 Apply |
|---|---|---|
| 코드 간결성 | 간결하고 직관적 | 상대적으로 장황함 |
| 타입 추론 | 자동 추론 | fun이 타입 추론 지원 |
| 유연성 | 고정된 검증 개수 | 동적 검증 개수 가능 |
| 사용 시기 | 대부분의 경우 | 고급 합성, 동적 파라미터 |
| 학습 곡선 | 낮음 | Currying 이해 필요 |
권장사항: 일반적인 경우 튜플 기반 Apply를 사용하세요. fun 기반 개별 Apply는 동적으로 검증을 조합해야 하거나 함수형 프로그래밍 패턴을 깊이 활용할 때 고려하세요.
에러 수집 및 ManyErrors 처리
섹션 제목: “에러 수집 및 ManyErrors 처리”Apply는 모든 검증 실패를 ManyErrors 타입으로 수집합니다. 다음 코드는 수집된 에러를 순회하며 사용자에게 표시하는 방법을 보여줍니다.
// ManyErrors를 통한 복수개 에러 처리if (error is ManyErrors manyErrors){ Console.WriteLine($" → 총 {manyErrors.Errors.Count}개의 검증 실패:"); for (int i = 0; i < manyErrors.Errors.Count; i++) { var individualError = manyErrors.Errors[i]; if (individualError is ErrorCodeExpected errorCodeExpected) { Console.WriteLine($" {i + 1}. 에러 코드: {errorCodeExpected.ErrorCode}"); Console.WriteLine($" 현재 값: '{errorCodeExpected.ErrorCurrentValue}'"); } }}실전 지침
섹션 제목: “실전 지침”예상 출력
섹션 제목: “예상 출력”=== 독립 검증 (Independent Validation) 예제 ===사용자 등록 값 객체의 모든 검증 규칙을 병렬로 실행합니다.
--- 유효한 사용자 등록 ---이메일: 'newuser@example.com'비밀번호: 'newpass123'이름: '홍길동'나이: '25'성공: 사용자 등록이 유효합니다. → 등록된 사용자: 홍길동 (newuser@example.com) → 모든 독립 검증 규칙을 통과했습니다.
--- 모든 검증 동시 실패 (Apply의 핵심) ---이메일: ''비밀번호: 'short'이름: 'A'나이: 'abc'실패: → 총 4개의 검증 실패: 1. 에러 코드: DomainErrors.UserRegistration.EmailMissingAt 현재 값: '' 2. 에러 코드: DomainErrors.UserRegistration.PasswordTooShort 현재 값: 'short' 3. 에러 코드: DomainErrors.UserRegistration.NameTooShort 현재 값: 'A' 4. 에러 코드: DomainErrors.UserRegistration.AgeNotNumeric 현재 값: 'abc'핵심 구현 포인트
섹션 제목: “핵심 구현 포인트”구현 시 세 가지 포인트에 주목합니다. 여러 검증을 튜플로 묶어서 Apply로 병렬 실행하고, 모든 검증 실패를 ManyErrors로 수집하며, 사용자에게 모든 문제점을 한 번에 표시합니다.
프로젝트 설명
섹션 제목: “프로젝트 설명”프로젝트 구조
섹션 제목: “프로젝트 구조”02-Apply-Parallel-Validation/├── Program.cs # 메인 실행 파일├── ValueObjects/│ └── UserRegistration.cs # 사용자 등록 값 객체 (Apply 패턴 구현)├── ApplyParallelValidation.csproj└── README.md # 메인 문서핵심 코드
섹션 제목: “핵심 코드”UserRegistration 값 객체는 Apply를 사용하여 이메일, 비밀번호, 이름, 나이를 동시에 검증합니다.
public sealed class UserRegistration : ValueObject{ public string Email { get; } public string Password { get; } public string Name { get; } public int Age { get; }
// Apply를 통한 병렬 검증 구현 public static Validation<Error, (string Email, string Password, string Name, int Age)> Validate( string email, string password, string name, string ageInput) => // 핵심 검증 규칙들을 병렬로 실행 (독립적 유효성 검사) (ValidateEmailFormat(email), ValidatePasswordStrength(password), ValidateNameFormat(name), ValidateAgeFormat(ageInput)) .Apply((validEmail, validPassword, validName, validAge) => (Email: validEmail, Password: validPassword, Name: validName, Age: validAge)) .As();
// 독립적인 검증 메서드들 private static Validation<Error, string> ValidateEmailFormat(string email) => !string.IsNullOrWhiteSpace(email) && email.Contains("@") && email.Contains(".") ? email : DomainErrors.EmailMissingAt(email);
private static Validation<Error, string> ValidatePasswordStrength(string password) => password.Length >= 8 ? password : DomainErrors.PasswordTooShort(password);}한눈에 보는 정리
섹션 제목: “한눈에 보는 정리”다음 표는 Bind 순차 검증과 Apply 병렬 검증의 차이를 비교합니다.
| 구분 | Bind 순차 검증 | Apply 병렬 검증 |
|---|---|---|
| 실행 방식 | 순차적으로 체이닝하여 실행 | 모든 검증을 동시에 실행 |
| 에러 처리 | 첫 번째 실패에서 조기 중단 | 모든 실패를 수집하여 반환 |
| 성능 | 조기 중단으로 효율적 | 병렬 실행으로 빠름 |
| 사용자 경험 | 한 번에 하나의 문제만 확인 | 모든 문제를 한 번에 확인 |
다음 표는 Apply 병렬 검증의 장단점을 정리합니다.
| 장점 | 단점 |
|---|---|
| 모든 문제점을 한 번에 확인 가능 | 에러 처리가 복잡함 |
| 병렬 실행으로 빠른 검증 | 모든 에러를 메모리에 보관 |
| 모든 검증 실패를 수집 | 검증 규칙이 독립적이어야 함 |
FAQ
섹션 제목: “FAQ”Q1: Apply와 Bind를 언제 선택해야 하나요?
섹션 제목: “Q1: Apply와 Bind를 언제 선택해야 하나요?”A: 검증 규칙들 간의 의존성을 확인하세요. 서로 독립적이라면 Apply를, 이전 결과가 다음 검증에 영향을 미친다면 Bind를 사용합니다.
Q2: ManyErrors는 어떻게 처리하나요?
섹션 제목: “Q2: ManyErrors는 어떻게 처리하나요?”A: ManyErrors 타입을 확인한 뒤 복수개의 에러를 순회하면서 각각을 처리합니다. ErrorCodeExpected 타입인지 확인하여 에러 코드와 현재 값을 표시하는 것이 좋습니다.
Q3: 모든 검증이 실패하는 경우는 어떻게 처리하나요?
섹션 제목: “Q3: 모든 검증이 실패하는 경우는 어떻게 처리하나요?”A: ManyErrors를 통해 모든 실패를 수집하여 사용자에게 완전한 피드백을 제공합니다. 각 에러를 명확하게 구분하여 표시하면 사용자가 모든 문제점을 한 번에 파악하고 수정할 수 있습니다.
지금까지 Bind(순차)와 Apply(병렬)를 각각 독립적으로 살펴보았습니다. 하지만 실제 도메인에서는 독립적인 필드와 의존적인 필드가 하나의 객체에 공존합니다. 다음 장에서는 Apply와 Bind를 조합하여 이런 복합적인 검증 요구사항을 해결합니다.