유효성 검사는 안정적이고 견고한 서버를 개발하기 위해 필수적입니다. 예를 들어 어떤 함수가 1에서 100자리까지만을 허용하는 문자열이며, 또한 옵셔널한 값이라고 해봅시다. 여기서 옵셔널한 값이라 함은 null이 아님을 의미합니다.
function someFunction(option?: string) {
// 만약 1-100 자리의 문자열 범위를 넘어선 값 혹은 문자열이 아닌 값이 올 경우 에러
}
함수에서는 이를 옵셔널한 문자열로만 표현이 가능할 겁니다. 물론 타입을 엄격하게 짜면 방법이 있을지도 모르지만, 일반적으로는 그렇습니다. 따라서, 런타임은 물론이고 컴파일 시점에서도 함수가 허락하지 않는 값이 들어올 수 있습니다. 이는 버그의 원인이 됩니다. 따라서, 안전한 함수 호출, 그리고 서버 개발을 위해서라도 런타임 유효성 검사기
가 필요합니다. Node.js 생태계에서는 가장 대표적인 것이 class-validator 이고, 그 외에도 zod와 같이 런타임에 타입을 체크해주는 도구들이 있지만 저는 몇 가지 이유로 typia를 사용 중에 있습니다. ( 이 오픈소스를 개발한 사람이 테크리더인 것도 몇 가지 이유 중 하나겠지만, 함께 일하기 전부터 사용해왔으니깐요. ) 가장 대표적인 런타임 유효성 검사 라이브러리들과 비교하여, Typia의 장점에 대해 소개하겠습니다.
interface SomeType {
prop?: string;
}
class DTO implements SomeType {
@MinLength(1)
@MaxLength(100)
@IsString()
@IsOptional()
prop?: string;
}
C.V. ( class-validator를 앞으로 이렇게 부르겠습니다. ) 는 위와 같이 타입에 유효성 검사를 명시합니다. 데코레이터를 활용한 방식이죠. 안타까운 것은 C.V.는 node.js가 아닌, 자바 계열에서 타입을 검사하는 것과 동일한 방식으로 만들어진 듯 합니다. 쉽게 말하면 타입 정보를 사용하지 않고 값을 이용해서 검사하는 방식인데요, 여기에는 몇 가지 문제점이 있습니다. 첫째는 null을 체크하기가 굉장히 어렵다는 것이고, 둘째는 유니온 타입을 지원하지 않기 때문에 enum을 이용해야 합니다. 또한 이 방식 역시 단순한 원시 타입에서만 가능할 뿐, 조금만 복잡해져도,
export function IsStringOrNumber(validationOptions?: ValidationOptions) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: 'isStringOrNumber',
target: object.constructor,
propertyName,
constraints: [],
options: validationOptions,
validator: {
validate(value: any, _: ValidationArguments) {
return typeof value === 'string' || typeof value === 'number' || value === null;
},
defaultMessage(): string {
return '값은 문자열, 숫자 또는 null이어야 합니다.';
},
},
});
};
}
이런 데코레이터를 만들기 일쑤입니다. 문자열 또는 숫자여야 한다는 것을 하나의 함수로 정의해야 하는 것을 보면, 유니온이 하나 늘어날 때마다 복잡해지기 마련입니다. 또 유니언 타입은 타입 간의 조합으로 표현되기 때문에 어마무시한 경우의 수가 나올 텐데, 이를 C.V.로 대응하는 것은 사실 상 불가능해보입니다. 반면 Typia에서는 이를 표현하는 것이 압도적으로 편리합니다. C.V.에서는 객체와 배열 타입 등 타입이 복잡해지면 표현 불가능한 반면, Typia에서는 순수한 타입스크립트 타입을 이용하여 검사를 하기 때문에 타입 시스템이 표현 가능한 모든 타입을 검사 가능한 거죠.
interface SomeType {
prop?: string;
}
import typia from 'typia';
typia.is<SomeType>(target); // boolean