Zod를 C#로 포팅하기: ZodSharp – .NET용 제로 할당, 고성능 스키마 검증 라이브러리

발행: (2025년 12월 14일 오전 09:05 GMT+9)
8 min read
원문: Dev.to

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 대신 수동 루프 사용
  • 복사 없이 문자열 작업을 위한 SpanReadOnlySpan 활용

유창한 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 모두 환영합니다!

읽어 주셔서 감사합니다! 🚀

Back to Blog

관련 글

더 보기 »