RESTful API validation 자동화 하기

ByungJoon Lee
야놀자클라우드 Tech Blog
11 min readJan 11, 2022

안녕하세요. 야놀자 클라우드 키오스크 서버팀 이병준입니다.

저는 클라우드 키오스크 RESTful API 서버 개발을 담당하고 있습니다. RESTful API 개발 업무에서 Validation은 안정적인 서버 운영을 위해서 꼭 필요하지만 치밀한 검증으로 안정성을 높이면 개발 비용이 지나치게 올라가고 느슨한 검증으로 개발 비용을 줄이면 안정성이 낮아져 고민을 많이 하게 됩니다. 그래서 저는 이 부분을 최대한 많이 자동화해서 치밀한 검증을 하더라도 최소한의 비용을 들이는 방법을 많이 고민하여 그 결과를 공유하려고 합니다.

자동화를 하려고 하는 부분은 어떤 부분인가?

TypeScript 인터페이스에 적절한 주석을 추가하면 validation과 Swagger.io 문서, TypeDoc(≒ JSDoc) 문서 생성을 자동화할 수 있습니다.

  1. 무엇이 문제인가?
  2. ajv
  3. JSONSchema 자동생성
  4. Swagger.io 문서 자동생성
  5. TypeDoc 문서 자동생성
  6. 제약사항
  7. 결론

본 문서는 이와 같은 순서로 문서 자동화 관련 내용을 다룹니다.

무엇이 문제인가?

사용자 입력값 검증은 안정적인 서버 운영을 위해서 필수입니다. 하지만 검증을 꼼꼼하게 하면 개발 비용이 많이 증가하고 느슨하게 하면 검증되지 않은 부분에서 장애가 발생할 가능성이 있습니다.

validation은 도와주는 패키지는 이미 많이 있습니다. Joi, Yup, validator.js, JSONSchema 등이 검색 결과 상위에 위치하는데 어떤 것을 사용할지 고민이 될 때는 npmtrends.com를 보면 도움이 됩니다.

ajv(another JSONSchema validator)의 다운로드 수가 다른 패키지에 비해서 높은 것을 볼 수 있습니다.

ajv

JSON은 형식은 RESTful API에서 de-facto가 되었습니다. 하지만 스키마를 설정할 수 없기 때문에 validation 기능은 없다고 해도 과언이 아닙니다. JSONSchema는 JSON에 좀 더 정교한 규격을 작성할 수 있게 해주는 약속입니다.

나이는 음수를 입력할 수 없고 최대 200살까지 입력 가능하다

이런 규칙을 JSONSchema로 정의하면 아래와 같이 됩니다.

minimum, maximum은 number 타입에 최솟값과 최댓값을 정의합니다. 이와 같이 JSONSchema는 JSON에 스키마를 설정해서 입력값을 검증하기 위한 스펙이며 ajv는 JSONSchema를 전달받아 Request가 JSONSchema에 부합하는지 검사(validation)하는 패키지입니다.

JSONSchema 자동생성

저는 TypeScript를 선호합니다. TypeScript를 왜 사용하는가에 대한 질문이 하실 수도 있는데 그 내용을 다루면 너무 긴 문서가 될 테니 2018년 JSConf EU에서 Ryan Dhal이 발표한 자료의 내용을 인용하고 다음 내용을 설명하겠습니다.

제 생각에 서버를 개발할 때 진정으로 모든 부분을 통제하고 싶다면 정적 타이핑이 필요할 것입니다 (I think if you’re building a server, and you want to really control every aspect of it, absolutely, you want it to be statically typed)

TypeScript는 사용자 입력 값을 Request를 다음과 같은 인터페이스로 정의할 수 있습니다.

위 예제는 간단한 호텔 생성 Request입니다. 개별 Request를 추적하기 위한 trackingId를 추가하고 인증을 위한 Authorization도 추가, Body에는 숙소 이름과 위경도, 설명 등이 추가된 것을 볼 수 있습니다.

Request 인터페이스를 JSONSchema로 변경할 수 없을까?

JSONSchema 자동 생성을 위해서 추가 정보를 작성했습니다. 각 값의 이름을 주석으로 추가하고 JSDoc 태그 형식으로 세부규격인 최솟값, 최댓값 설정을 추가했습니다. 참고로 이렇게 인터페이스에 주석을 잘 정리하면 vscode(Visual Studio Code)는 마우스를 올리면 코드와 주석을 팝업으로 보여주고 Go to definition 기능을 사용해서 인터페이스 선언 코드로 이동할 수 있습니다.

추가 정보가 작성된 TypeScript 인터페이스를 어떻게 변경하지?

해답은 간단합니다. TypeScript 인터페이스 변환기를 사용하면 됩니다. 제가 확인한 JSONSchema 변환기는 typescript-json-schemats-json-schema-generator가 있습니다. 둘의 차이점은 ts-json-schema-generator는 TypeScript 컴파일러 API를 사용해서 더 복잡하고 정교한 변환이 가능하지만 조금 느립니다. 둘 다 사용해보고 원하는 툴을 선택해서 사용하면 됩니다.

두 패키지는 자체적으로 cli를 제공합니다. 그런데 제가 사용해보니 변환할 인터페이스 이름을 타이핑하는 불편함이 있어서 simple-tjscli를 사용합니다.

npx simple-tjscli

simple-tjscli는 위 사진과 같이 대화형 인터페이스로 파일 이름을 입력하고 파일에 여러 인터페이스가 있을 때 그중에 하나를 선택할 수도 있습니다.

성공적으로 JSONSchema를 생성했습니다. 몇 가지 내용을 볼까요?

name: {
type: 'string',
description: '숙소이름',
minLength: 2,
maxLength: 256,
},

먼저 숙소 이름입니다. 추가한 설명과 최솟값, 최댓값 검증 규격이 잘 추가된 것을 볼 수 있습니다.

location: {
type: 'object',
properties: {
lat: {
type: 'number',
description: '위도',
minimum: -90,
maximum: 90,
},
lon: {
type: 'number',
description: '경도',
minimum: -180,
maximum: 180,
},
},
required: ['lat', 'lon'],
additionalProperties: false,
description: '위경도',
},

ts-json-schema-generator는 IGeoCoord 인터페이스를 변환할 때 별도 definition으로 추출하지 않고 JSONSchema로 만들었습니다. 이 이유는 다음 내용에서 살펴보겠습니다. 그리고 앞서 설명했던 minimum, maximum 값이 추가되었고 required 값을 보면 description과 같은 optional value를 제외하고 추가된 것을 확인할 수 있습니다. 이제 우리는 이렇게 생성된 값을 ajv에 입력해서 Request validation을 자동화할 수 있습니다. fastify.js 웹 프레임워크는 ajv를 내장하고 있어서 JSONSchema를 route 단계에서 설정할 수 있습니다.

참고로 body, querystring, params, header 모두 인터페이스를 분리할 수 있지만 저는 합쳐 두는 것을 선호합니다. JSONSchema 변환 횟수를 줄일 수 있고 너무 많은 파일이 생성되는 것도 방지할 수 있습니다.

Swagger.io 문서 자동생성

swagger-ui-express 또는 loopback.js은 문서 파일을 별도로 작성해서 전달해야 하고 nest.js는 Swagger.io 추출을 위해서 개별 변수에 decorator를 추가해야 합니다. 다른 프레임워크는 Swagger.io 문서를 최신 상태로 유지하려면 개발 외적인 수고가 많이 듭니다. 하지만 JSONSchema와 fastify.js에 fastify-swagger 플러그인을 등록하면 validation을 위한 JSONSchema 등록만으로 Swagger.io 문서가 자동 생성됩니다. 그래서 문서를 최신 상태로 유지하는 비용이 최소화됩니다.

위 예제 19번째 줄을 보면 definition 값이 빈 오브젝트로 전달되는 것을 볼 수 있습니다. IGeoCoord 인터페이스를 기억하시나요? IGeoCoord 인터페이스를 별도 인터페이스로 별도 JSONSchema로 생성했다면 fastify-swagger 플러그인에게도 알려줘야 합니다. 하지만 JSONSchema 파일이 많아지면 이렇게 별도 관리하는 것이 부담이 커서 저는 별도 추출을 선호하지 않습니다.

TypeDoc 문서 자동생성

validation 자동화 및 Swagger.io 문서를 생성하기 위해 풍부한 주석을 추가했기 때문에 이 부분은 TypeDoc 문서 완성도를 높입니다. 그래서 TypeDoc 문서 생성할 때 Request, Response 인터페이스에 대해서 높은 품질의 문서가 생성됩니다.

제약사항

이러한 validation 자동화 과정이 완벽한 것은 아닙니다. 단점도 분명 존재합니다.

첫 번째로 연결고리가 취약합니다. 앞서 언급했던 definition과 같은 부분은 시행착오를 거치지 않는다면 알기 어렵습니다. 문서에 사용했던 도구들은 다른 프레임워크와 같이 동작하기 위해 개발된 것이 아니라서 JSONSchema, fastify.js, ajv 등 개별 패키지에 대한 정확한 지식이 없으면 올바른 동작을 시키기 위한 진입장벽이 높습니다.

두 번째로 JSONSchema 규격 변경 가능성입니다. 2020년 12월 마지막 draft가 발행되었지만 draft라서 변경될 수 있습니다. ajv 패키지 변경도 있습니다. ajv 패키지는 6.x 이전과 이 후로 큰 변화가 있습니다. TypeScript 지원이 확대되었는데 fastify.js에서 타입 선언 변경 부담으로 아직 6.x를 사용하고 있습니다. fastify.js 4.x에서 ajv 8.x를 내장하기 전까지 ajv 6.x에 맞춰서 ts-json-schema-generator, JSONSchema를 사용해야 합니다. 이런 부분도 복잡한 점이라고 볼 수 있습니다.

세 번째로 ts-json-schema-generator는 mapped access 접근을 변환할 때 오류가 있습니다.

위 예제를 보면 이벤트 호텔 인터페이스에서 호텔 인터페이스 사진 객체 타입을 사용합니다. mapped access를 사용한 참조 타입은 기존 타입을 재활용할 수 있으며 원본 인터페이스 변경사항이 참조 인터페이스에 영향을 주기 때문에 side-effect 파악에도 용이합니다. 하지만 ts-json-schema-generator에서 이 부분 오류가 있어 Request, Response 객체에서 참조 타입을 사용할 수 없습니다.

마지막으로 JSONSchema는 Schema라서 validation을 위한 별도 코드를 추가할 수 없습니다.

IVisitPeople 인터페이스에서 방문객을 IPerson의 배열로 저장합니다. 방문객 나이가 100살 이하여야 한다는 조금 억지스러운 검증 규칙을 추가한다고 가정하면 Joi, Yup는 커스텀 규칙을 사용해서 검증할 수 있지만 JSONSchema는 불가능합니다.

결론

제약사항 부분의 설명이 길었기 때문에 단점이 큰 것처럼 보이지만 실제로 사용해보면 정말 편리합니다. DDD(Domain Driven Design)에서 도메인을 정의하듯이 Request, Response 인터페이스를 관리하면 항상 최신 클래스 정의, API 정의 문서가 제공됩니다. 저는 다음과 같은 장점이 있다고 생각합니다.

  1. 문서를 최신 상태로 유지할 수 있다
  2. 개발자가 잊어버릴 위험이 없다. 설명이 없어도 기본 문서는 생성된다
  3. 타 팀과의 협업에 도움이 된다

야놀자 클라우드에서는 배포 자동화 솔루션으로 Jenkins를 채택하고 있어서 배포 과정에서 생성된 문서를 S3에 곧바로 업로드합니다. 그래서 1, 2, 3번 장점을 상시 누릴 수 있습니다.

아는 분이 저에게 해준 조언이 있습니다.

검증, 문서화 과정이 자동화가 되어 있지 않다면 누락이 발생하는 것은 필연적이며 인력으로 막기 어렵다

저는 바쁜 개발 과정에 문서를 항상 최신 상태로 유지하는 것은 아무리 신경을 써도 어려웠습니다. 그래서 저 조언에 크게 공감을 했고 많은 문서를 자동으로 생성할 수 있게 도와주는 JSONSchema는 단점보다 장점이 크다고 생각합니다.

여러분도 한 번에 네 마리 토끼를 잡을 수 있는 JSONSchema를 활용해보세요

--

--

야놀자클라우드 Tech Blog
야놀자클라우드 Tech Blog

Published in 야놀자클라우드 Tech Blog

야놀자 클라우드는 효율적인 호텔 운영을 돕고 편리한 호텔 고객 경험을 제공하는 클라우드 기반의 종합 호스피탈리티 솔루션을 만들어가고 있어요.

ByungJoon Lee
ByungJoon Lee

Written by ByungJoon Lee

Love TypeScript, Rust, JavaScript, Node.js, Fastify.js