Minimal API 비동기 검증과 OpenAPI 3.2 — .NET 11에서 달라지는 것
"이 이메일은 이미 사용 중입니다." 회원가입 화면에서 이 문구 하나를 띄우려고 코드를 어디에 둘지 한참 고민했던 기억이 있습니다. 형식이 이메일인지 확인하는 건 어트리뷰트 한 줄이면 끝나는데, 이미 가입된 주소인지 확인하려면 데이터베이스를 봐야 합니다. 그리고 이 두 가지는 성격이 전혀 다릅니다.
Minimal API를 쓰기 시작한 뒤로 이 구분이 더 선명해졌습니다. 형식 검증은 프레임워크가 알아서 해주는데, DB가 필요한 검증은 갈 곳이 애매해서 결국 핸들러 안으로 들어가곤 했습니다. .NET 11에서는 이 지점이 다뤄질 예정입니다. 다만 현재 프리뷰 기준이며 GA까지 바뀔 수 있습니다. 구체적인 메서드 이름이나 시그니처는 출시 시점의 공식 문서에서 확인하시는 편이 좋습니다.
지금까지 검증 코드를 두던 세 자리
제가 실제로 써봤거나 코드에서 마주친 방식은 대체로 셋으로 나뉩니다.
방식장점불편했던 점
핸들러 안에서 직접 검사흐름이 눈에 보이고 의존성이 없음엔드포인트가 늘수록 같은 코드가 반복됨
FluentValidation규칙이 한곳에 모이고 테스트가 쉬움별도 등록과 파이프라인 연결이 필요함
엔드포인트 필터공통 처리로 빼기 좋음타입별 분기를 직접 관리해야 함
메리톡톡에서는 세 방식을 시기별로 다 써봤습니다. 처음에는 핸들러 안에서 if로 확인했고, 엔드포인트가 스무 개를 넘어가면서 FluentValidation으로 옮겼습니다. 규칙이 한곳에 모이는 것만으로도 훨씬 나아졌지만, 비동기 규칙은 여전히 손이 갔습니다. 동기 규칙과 비동기 규칙이 섞이면 호출 방식이 달라지고, 어느 쪽으로 검증했는지 헷갈리는 순간이 생깁니다.
비동기 검증이 들어오면 간단해지는 것
가장 크게 달라지는 지점은 DB나 외부 호출이 필요한 검증을 규칙 자리에 그대로 둘 수 있다는 점입니다. 지금까지는 이런 검증이 대체로 핸들러 본문으로 흘러들어 갔습니다. 결과적으로 아래 두 가지가 뒤섞였습니다.
• 입력이 유효한가: 값의 형식과 범위, 다른 데이터와의 정합성을 판단하는 일입니다.
• 요청을 처리하는 일: 실제로 저장하고 결과를 만드는 일입니다.
이 둘이 한 메서드에 섞여 있으면 테스트가 무거워집니다. 검증 하나를 확인하려고 저장 경로까지 준비해야 하기 때문입니다. 비동기 검증이 규칙 계층에 들어가면 두 관심사를 다시 나눌 수 있습니다.
대표적인 사례가 앞서 말한 중복 확인입니다. 아래는 개념을 보여주기 위한 짧은 예시이며, 실제 API 형태는 프리뷰 진행에 따라 달라질 수 있습니다.
// 개념 예시 — 실제 시그니처는 공식 문서 확인
public class SignUpRequest
{
[Required, EmailAddress]
public string Email { get; set; } = "";
[Required, MinLength(8)]
public string Password { get; set; } = "";
}
// DB 조회가 필요한 규칙은 비동기로 분리
public class SignUpValidator(IUserRepository users)
{
public async Task<bool> IsEmailAvailableAsync(
string email, CancellationToken ct)
=> !await users.ExistsByEmailAsync(email, ct);
}
중요한 것은 문법이 아니라 배치입니다. 형식 검증은 모델에, 데이터가 필요한 검증은 규칙 객체에, 처리 로직은 핸들러에 두는 구도가 만들어지면 나중에 규칙이 늘어도 핸들러가 두꺼워지지 않습니다.
비동기 검증에서 주의할 점
편해지는 만큼 조심할 부분도 생깁니다. 제가 미리 신경 쓰려는 항목은 셋입니다.
• DB 왕복 횟수: 규칙마다 조회를 하면 요청 하나에 쿼리가 여러 번 나갑니다. 같은 데이터를 여러 규칙이 본다면 한 번만 읽고 공유하는 편이 낫습니다.
• 취소 토큰 전달: 클라이언트가 끊은 요청에서 검증 쿼리가 계속 도는 상황을 막으려면 토큰을 끝까지 넘겨야 합니다.
• 경합 상태: 중복 확인이 통과해도 저장 직전에 다른 요청이 같은 값을 넣을 수 있습니다. 검증은 사용자 경험용이고, 최종 방어는 데이터베이스의 고유 제약이어야 합니다.
세 번째는 실제로 겪었습니다. 검증을 통과했는데 저장에서 예외가 나는 경우였습니다. 그때 배운 것은 단순합니다. 검증은 안내 문구를 위한 것이고, 정합성 보장은 제약 조건의 몫입니다.
OpenAPI 3.2가 문서와 클라이언트에 주는 영향
.NET 11에서는 Minimal API의 OpenAPI 문서 생성이 3.2를 기준으로 동작할 예정입니다. 버전 숫자만 보면 사소해 보이지만, 문서를 소비하는 쪽에서는 체감이 있습니다.
• 문서 표현이 최신 명세를 따릅니다: 스펙이 다루는 범위가 넓어지면 그동안 주석이나 별도 설명으로 채우던 부분을 문서 자체가 표현할 수 있게 됩니다.
• 클라이언트 코드 생성 도구와의 궁합을 확인해야 합니다: 생성 도구가 3.2를 아직 소화하지 못하면 결과물이 이상해질 수 있습니다. 업그레이드 전에 쓰는 도구의 지원 범위를 먼저 보시는 편이 좋습니다.
• 프런트엔드와의 계약이 명확해집니다: 저는 Next.js 쪽 타입을 문서에서 뽑아 쓰는데, 이 경로가 깨지면 화면 작업이 통째로 밀립니다. 그래서 이 항목을 업그레이드 점검 목록에 넣어두었습니다.
실무에서 확인할 순서는 단순합니다. 먼저 생성된 문서를 파일로 뽑아 이전 버전과 비교하고, 그다음 클라이언트 생성 도구를 돌려 산출물 차이를 봅니다. 이 두 단계에서 문제가 없으면 대체로 넘어갑니다.
마치며
정리하면 이렇습니다. 형식 검증은 원래도 쉬웠고, 어려웠던 것은 데이터를 봐야 하는 검증이었습니다. 비동기 검증이 규칙 계층으로 들어오면 그동안 핸들러에 눌어붙어 있던 코드를 제자리로 옮길 수 있습니다. OpenAPI 3.2는 문서 자체보다 그 문서를 먹는 도구들과의 호환을 먼저 확인하는 것이 실무 순서입니다.
다시 말씀드리면 이 내용은 현재 프리뷰 기준이며 GA까지 바뀔 수 있습니다. 지금 할 수 있는 한 가지는 지금 프로젝트에서 검증 코드가 흩어져 있는 자리를 한 번 세어보는 일입니다. 핸들러 안에 남은 if 문이 몇 개인지 확인해두면, 새 기능이 나왔을 때 무엇을 옮길지 바로 정할 수 있습니다.