Zod를 C#로 포팅하기: ZodSharp – .NET용 제로 할당, 고성능 스키마 검증 라이브러리
Source: Dev.to
소개
.NET 개발자로서 나는 오랫동안 한 가지 지속적인 고충에 시달려 왔습니다. 대부분의 검증 라이브러리는 리플렉션에 크게 의존하기 때문에 성능이 느리고 할당이 끊임없이 발생합니다. API 입력, 폼, 데이터 파이프라인을 검증하든, 그 오버헤드는 특히 고처리량 시나리오에서 크게 누적됩니다.
그때 나는 Zod를 발견했습니다 — TypeScript 세계에서 스키마 검증의 금본위이며, colinhacks가 만든 것입니다. 유창한 API, 완전한 타입 추론, 뛰어난 오류 메시지, 그리고 제로‑디펜던시 설계는 마법과도 같았습니다. 하지만 C#에서는 이와 같은 것이 없었습니다.
그래서 나는 ZodSharp를 만들었습니다 — 현대 .NET에 맞게 처음부터 다시 작성한 Zod의 완전한 구현으로, 성능, 제로‑할당, 개발자 경험에 집착합니다.
왜 Zod가 TypeScript에서 표준이 되었는가
- 쓰기 즐거운 유창하고 체이닝 가능한 API
- 자동 추론을 통한 완전한 타입 안전성
- 복잡한 스키마를 위한 강력한 조합성
- 풍부한 변환 및 정제 기능
- 명확하고 구조화된 오류 보고
- 런타임 의존성 제로
.NET에는 FluentValidation 같은 견고한 옵션이 있지만, 이들은 리플렉션과 표현식에 의존합니다 — 유연성은 좋지만 성능이 중요한 경로에서는 비용이 많이 듭니다. ZodSharp는 같은 우아한 개발자 경험을 C#에 제공하면서 할당 비용을 없앱니다.
단순 포팅이 아닌 — 성능 우선 재작성
Zod의 공개 API와 의미론을 충실히 따르면서도, 내부 세부 사항은 거의 모두 .NET의 강점을 살리도록 재설계되었습니다.
제로‑할당: 핵심 설계 목표
최우선 순위: 검증은 핫 경로에서 가비지를 전혀 생성하지 않아야 합니다. 리플렉션 기반 검증은 매 호출마다 객체, 클로저, 임시 컬렉션을 생성합니다. ZodSharp는 이를 완전히 제거합니다.
핵심 기법
- 구조체 기반 검증 규칙
public readonly struct MinLengthRule : IValidationRule
{
private readonly int _min;
public MinLengthRule(int min) => _min = min;
public bool IsValid(in string value) => value.Length >= _min;
}
- 최소 오버헤드의 불변 컬렉션 (
ImmutableArray,ImmutableDictionary) ValidationResult를 구조체로 구현 — 성공이든 실패이든 힙 할당 없음- 임시 버퍼를 위한
ArrayPool사용 - 성능이 중요한 구간에서는 LINQ 대신 수동 루프 사용
- 복사 없이 문자열 작업을 위한
Span및ReadOnlySpan활용
유창한 API — Zod와 최대한 가깝게
API는 Zod 사용자에게 즉시 친숙하게 다가옵니다:
// Zod TS와 거의 동일
var schema = Z.String()
.Min(3)
.Max(50)
.Email()
.Trim()
.ToLower();
객체 스키마:
var userSchema = Z.Object()
.Field("name", Z.String().Min(1))
.Field("age", Z.Number().Min(0).Max(120))
.Field("email", Z.String().Email())
.Build();
변환, 정제, 유니언, 옵션, 리터럴 — 모두 지원됩니다.
소스 제너레이터 & DataAnnotations 통합
.NET 고유의 가장 큰 장점 중 하나: 컴파일 타임 스키마 생성.
[ZodSchema]
public class User
{
[Required, StringLength(50, MinimumLength = 3)]
public string Name { get; set; } = string.Empty;
[Range(0, 120)]
public int Age { get; set; }
[EmailAddress]
public string Email { get; set; } = string.Empty;
}
// 사용
var result = UserSchema.Validate(new User { /* ... */ });
런타임 리플렉션이 전혀 필요 없습니다.
프로젝트 구조 개요
ZodSharp/
├── src/ZodSharp/
│ ├── Core/ → 인터페이스, 기본 클래스, 결과, 오류
│ ├── Schemas/ → String, Number, Object, Array, Union 등
│ ├── Rules/ → 구조체 기반 검증 규칙
│ ├── Expressions/ → Expression Tree를 통한 컴파일된 검증기
│ ├── Json/ → Newtonsoft.Json 통합
│ ├── Optimizations/ → 제로‑할당 헬퍼
│ └── Z.cs → 정적 팩터리 (Z.String(), Z.Object() 등)
├── src/ZodSharp.SourceGenerators/
│ └── ZodSchemaGenerator.cs → [ZodSchema] 소스 제너레이터
├── example/ → 전체 사용 예시
└── README.md
대략적인 성능 향상
초기 벤치마크는 전형적인 리플렉션 기반 검증에 비해 눈에 띄는 개선을 보여줍니다:
| 시나리오 | 리플렉션 기반 | ZodSharp | 향상 정도 |
|---|---|---|---|
| 간단한 문자열 검증 | ~0.15 ms | ~0.01 ms | 약 15배 빠름 |
| 복합 객체 검증 | ~0.8 ms | ~0.05 ms | 약 16배 빠름 |
| 검증당 할당량 | 다수 | 제로 | 완전 제거 |
부하가 커질수록 차이는 더욱 커집니다.
실용적인 사용 사례
ZodSharp는 검증 성능이 중요한 어디에서든 빛을 발합니다:
- 고처리량 API (REST, gRPC, GraphQL)
- 빈번한 입력 검증이 필요한 마이크로서비스
- 실시간 시스템
- 데이터 수집 파이프라인
- 복잡한 폼을 가진 데스크톱/모바일 앱
- 런타임 비용 없이 Zod와 같은 개발자 경험을 원하는 모든 상황
현재 구현된 기능
- 유창한 스키마 빌딩
- 모든 기본 타입 지원 (string, number, boolean, array, object, union, literal)
- 변환 (
.Trim(),.ToLower()등) - 정제 (사용자 정의 검증)
- 옵션 / nullable 래퍼
- 구분 유니언
- 지연/재귀 스키마
- DataAnnotations와 함께하는 소스 제너레이터
- Newtonsoft.Json 통합
- 고급 문자열 규칙 (
.Url(),.Uuid(),.Email(),.StartsWith()…) - 고급 숫자 규칙 (
.Positive(),.MultipleOf(),.Finite()…)
앞으로의 계획
- 공개 벤치마크 스위트
- ASP.NET Core 모델 바인딩 통합
- Entity Framework 검증 훅
- 추가 JSON 직렬화 지원 (
System.Text.Json) - 향상된 오류 포맷팅
- 커뮤니티 기여
마무리 생각
ZodSharp를 만드는 과정은 매우 보람 있었습니다. 단순히 Zod를 C#에 옮기는 것이 아니라, .NET 생태계 전반에 퍼진 빠르고, 타입‑안전하며, 제로‑오버헤드 검증이라는 실질적인 문제를 해결하는 것이었습니다.
리플렉션으로 인한 속도 저하에 한숨을 쉬어본 적이 있거나, C#에서 보다 현대적인 검증 경험을 원한다면 ZodSharp를 한 번 사용해 보세요. 피드백, 스타, 이슈, PR 모두 환영합니다!
읽어 주셔서 감사합니다! 🚀