데이터 검증 (Data Validation)은 언제, 얼마나 해야 할까?

개요

필자는 처음 웹 프로젝트를 받았을 때 고생했습니다. 왜냐하면 아는 게 하나도 없었기 때문이지요 (물론 그렇다고 지금도 아는 게 많은 것 같지는 않아요..) 그래도 고생하고 경험하면서 직접적으로 느끼며 배우는 것들은 많은 것 같습니다. 그 많고 많은 주제 중에, 데이터 검증 (Data Validation) 을 언제(when) 얼마나(how much) 해야 하는가에 대한 질문을 조금이나마 더 잘 대답할 수 있게 된 것 같습니다.

데이터 검증이 중요하다는 것은 이견이 없습니다. 왜냐하면 서버든 클라이언트든 의도되지 않은 행동을 했을 때에는 그것이 불가능하다고 단정지어야 하기 때문이지요. 의도되지 않았으니까요! 제가 그렇게 사용하라고 만든 게 아닌데, 그렇게 사용한다면, 당연히 금지시켜야겠죠? 휴대폰 번호를 입력하라고 만든 입력창에 사용자가 이메일을 입력했는데, 아무런 문제 없이 그대로 통과되었다고 가정합시다. 자, 이제 사용자에게 전화를 걸고 싶습니다. 그래서 해당 사용자의 휴대폰 번호를 확인하는데 이메일이 담겨있어요! 이메일로 전화는 못걸잖아요. 이렇듯 사용자가 의도하였든 의도하지 않았든 프로그램에 심각한 문제를 일으키리라는 것은 명백하니까요. 만약 데이터를 검증했을 때 아무런 이상이 없다면? 그냥 그대로 진행하면 되겠습니다. 하지만 문제가 있다면? 그 결과가 서버가 통째로 멈추는 것이든, 단순히 입력이 잘못되었다고 에러 메시지를 출력하든, 혹은 입력을 단순히 무시할 수도 있습니다. 이처럼 검증이 통과하지 못했을 때 적절한 조치까지 취해져야 합니다.

본 글은 필자가 Node.js 앱을 만들면서 느꼈던 점을 서술하기도 합니다. 그래서 Node.js, GraphQL, Mongoose 에 대한 기본 지식이 없다면 문맥을 이해하는 데에 힘들 수 있습니다. 간단하게 설명하자면, Node.js 는 자바스크립트 기반 V8 엔진 런타임 환경이고, Node.js 위에 express 웹 앱 등 다양한 모듈이 서로 유기적으로 결합할 수 있습니다. GraphQL 은 페이스북에서 만든 API 에 대한 인터페이스로, (인터페이스에 대한 인터페이스) REST API 와 비슷한 역할이라고 보시면 됩니다. Mongoose 는 MongoDB를 Node.js 환경에서 손쉽게 사용할 수 있게 해주는 모듈입니다.

사람들의 토론

사람들이 Validation 에 대해서 어떤 의견들을 가지고 있는지 궁금하여 한 번 검색해봤습니다. 그 중 가장 눈에 띄는 건 StackExchange 에서 Data input validation – Where? How much? 라는 글이었습니다. 대충 요약하자면, 우선 발제자의 의견은 다음과 같습니다. 영어가 된다면 찬찬히 읽어보시기를 권장합니다. (아래 간단히 번역한 내용은 다소 부정확합니다.)

  • 클라이언트 단계 (브라우저) : 간단한 검증만 한다. (6자 이상 20자 이하 등)
  • 웹 커뮤니케이션 제어 단계 (컨트롤러 혹은 라우팅) : 검증에 관해서는 별다른 규칙을 두고 있지 않다. 그렇지만 이 단계에서 데이터를 상황에 맞게 변형, 구조화 혹은 파싱할 수 있도록 한다.
  • 비즈니스 레이어 단계: 견고한 검증을 하도록 한다. 데이터 포맷, 범위, 값, 내부상태 체크 (메소드를 항상 호출할 수는 없을 때), 사용자 역할 및 권한, 기타 등등을 검증해볼 수 있다. 값을 사용할 때에는 사용자가 입력한 건 최대한 사용하지 않도록 하고 가능한한 실제 데이터베이스에 있는 값들을 사용한다.
  • 데이터 접근 단계 (DAL, DAO) : 이 단계는 데이터 접근(Access)이 가장 중요한 단계이므로 검증이 많이 필요하지는 않다고 본다.
  • 데이터베이스 단계 : 데이터 자체의 일관성이 잘 유지되기 위해서 가능한 한 강력한 검증을 넣도록 한다. 견고한 기본키와 외래키, 제약, 데이터 타입, 길이, 크기, 정밀도, 기타 등등을 검증해볼 수 있다.

또 다른 의견

위 방법대로라면 모든 검증 단계가 일관성을 유지하고 있어야 한다. 예를 들어 클라이언트 사이드에서 검증이 완료된 것이 비즈니스 단계에서도 문제 없이 통과해야 한다. 만약 적어야 할 칸이 굉장히 많은데, 제출해보고 나서야 에러 메시지를 받게 된다면 사용자들은 굉장히 짜증이 날 것이다. 그래서 Validation 관련된 것들을 따로 모아두고 언제 어디서든지 필요할 때 호출하여 사용할 수 있는 단계를 만들어야 한다.

또또 다른 의견

  • Presentation/UI
    • 입력 검증은 간단하게.
    • 입력이 잘못된 포맷이라면 계속 진행하지 않음.
    • “gate” 클라이언트가 요청을 서버로 보냄으로써 Round-Trip 을 줄여서 사용성 개선과 대역폭 및 시간 감소 효과를 기대함.
  • Logic
    • 비즈니스 로직과 인증
    • 사용자가 허용되지 않은 행동을 할 수 있도록 내버려두지 말자.
    • 파생된 속성과 상태를 이 단계에서 다룸. (데이터베이스에서 비정규화된 것들이 될 수 있음)
  • Data
    • 핵심 데이터 레이어
    • 필요없는 값을 저장하는 건 무조건 거부
    • DB 자체적으로 포맷 강제
    • 관계를 적절하게 보장하기 위해 데이터베이스 제약을 사용함.

데이터는 어디에서든지 검증될 수 있다.

데이터는 어디에서든지 검증될 수 있습니다. 말 그대로입니다. 코드를 입력할 수 있는 곳에는 데이터 검증이 가능하죠. 그냥 단순히 if 문으로도 검증 절차를 넣을 수 있습니다. if (isValid(value)) doSomething(); 이렇게요. isValid 함수의 내용은 상상에 맡기겠습니다.

데이터 검증이 하도 중요하다보니 거의 모든 라이브러리, 모듈, 개발 환경에서는 검증(Validation) 관련 기능을 제공하거나 써드-파티 Validation 라이브러리가 활발하게 개발됩니다. vue 를 확인해볼까요? VeeValidate 등이 건재합니다. 필자가 별로 써보지 않은 React 를 보아도, 단순히 React Validation Library 만 구글에 검색해도 바로 상위에 React Hook Form 이 등장하는 것을 확인할 수 있습니다. (star가 19,000개 이상입니다.) API 인터페이스 중 하나인 GraphQL 의 경우 그냥 Learn 의 한 꼭지로 커다랗게 Validation 이 차지하고 있네요. Node.js 의 Express 에서도 router 단에서 사용할 수 있는 express-validator 가 있습니다. Node.js 에서 가장 대표적인 MongoDB 의 ODM 을 구현하고 있는 mongoose 의 경우에도 대놓고 validation 기능을 첫 문장에서 소개하고 있으며 Docs 에서 Validation 이 한 꼭지를 당당하게 차지하고 있습니다.

이렇듯 데이터는 언제 어디서든지 검증될 수 있습니다. 하지만 우리의 시간과 노력은 제한되어 있는게 문제이지요.

목적과 대상 사용자에 따라서 구분하여 검증하자

지금까지 모든 단계에서 검증이 가능하고, 또 사람마다 검증을 어느 단계에서 힘주어서 해야 하는지에 대한 공통된 의견과 다른 의견들이 있음을 보았습니다. 어떤 상황에서든지 100% 통하는 방법론은 없을 것이라 확신하지만, 좀 더 검증의 목적과 대상 사용자를 명확히 하여 효율적으로 코드를 짜는 게 중요하다고 생각합니다. 앞서 말했듯이 우리의 체력과 시간은 무한하지 않습니다.

브라우저

브라우저는 명확히 클라이언트의 영역입니다. 사용자의 경험이 중요한 부분이라서, 검증도 그에 따라서 부드럽게, 매끄럽게 잘 되어야 합니다. 그러니까 검증은 오로지 사용자의 경험을 더 낫게 하는 데에 의의를 두어야 합니다. 어떤 느낌이냐면, 어차피 추후 서버에서 Error 를 일으킬 것을, 적절한 메시지와 적절한 타이밍에 미리 사용자에게 알려주어 더 나은 경험을 제공한다는 것입니다. 그래서 서버쪽의 검증에 완전히 의존적입니다.

또 한 가지 생각해야 할 점은, 브라우저 단계에서 어떤 값이 실제로 유효할지에 대한 의의는 전혀 필요가 없다는 점입니다. 왜냐하면 데이터를 받는 서버 입장에서 그 데이터가 적절한 클라이언트로부터 왔는지를 검증할 수 있는 방법이 없기 때문입니다. 패킷이야 서버로 오기 전까지는 언제든지 조작이 가능합니다. 서버 입장에서는 요청으로부터의 데이터를 단 하나도 신뢰할 수 없기 때문에, 아무리 브라우저에서 신뢰할 수 있는 값을 사용자들로 하여금 입력할 수 있게 하더라도, 의미가 없습니다. 의미가 있을 때는, 사용자가 더 나은 경험을 할 때 입니다.

브라우저 단계에서는 검증 대상도 명확히 일반 사용자입니다. 아니, 검증 대상 사용자가 일반 사용자 말고 뭐가 있냐구요? 바로 개발자 우리 자신이요!

API 인터페이스, 컨트롤러, 라우팅 단계

실제 비즈니스 로직을 호출하기 전입니다. GraphQL 같은 경우는 스키마에 맞지 않는 데이터가 입력된다면 자동으로 에러를 일으킵니다. 어느 정도 자체적으로 검증이 되는 셈입니다. 이 단계에서는 자체적인 검증 뿐만 아니라 사용자의 역할이나 권한에 문제가 없는지도 체크할 수 있습니다. 보통 요청에 사용자의 Session Id 등이 함께 딸려오는 경우가 많으므로, 권한을 검사한다면 이 단계가 가장 빠르다고 볼 수 있습니다.

마찬가지로 GrpahQL 이 어느정도 해주는 부분이지만, 요청으로부터 날라온 데이터가 서버사이드의 언어에 맞도록 타입이 잘 지정되어야 합니다. 우선 요청으로 들어온 값들은 모두 문자열이므로, 그것을 실제로 사용할 변수로 만들어줘야 합니다. 그래야 나중에 비즈니스 로직에 집중할 수 있습니다. 위 사람들의 토론에서 발제자가 이야기한, 데이터를 상황에 맞게 변형, 구조화 혹은 파싱해야 한다는 이야기와 일맥상통합니다.

이 단계에서 검증 대상은 사실 더 확대됩니다. 일반 사용자 뿐만 아니라 악성 사용자도 고려해야 합니다. 이 사용자는 패킷을 얼마든지 조작할 수 있고, 얼마든지 요청을 무수히 많이 보내어 서버 과부하를 일으키고 싶어할 수 있습니다.

비즈니스 로직 단계

실제 우리의 의도가 가장 많이 담기는 단계입니다. 여기서는 검증과 비즈니스 로직이 섞일 수 밖에 없습니다. 어떤 값이 유효한 이메일인지 아닌지 구분하는 방법은 너무나도 명확하고 전 세계적으로 보편적인 이야기입니다. 반면 돈벌기 게임에서 노동 시간을 초과하여 일을 했다고 가정했을 때에는 영향을 주는 것들도 많고 결과에 따른 선택지도 다양할 수 있습니다. 현재까지 일했던 시간, 회사의 권장 노동시간, 법적근로시간 등등이 모두 맞물리겠지요. 그러한 핵심 동작들이 모두 비즈니스 로직에 녹아있어야 할 것입니다.

이 단계에서 검증 대상을 따지는 것은 크게 무의미합니다. 검증 대상은 모든 것들이 될 수 있습니다. 사용자가 입력한 값, 개발자가 계산하거나 제시하는 값, 이미 데이터베이스에 저장되어 있는 값들을 검증해야 합니다.

Database

mongoose 는 너무나도 유명하고 보편적이라, 어떻게 보면 MongoDB 에 정말 딱 달라붙어 있다고도 말할 수 있습니다. 이러한 데이터베이스 모델링을 객체화하여 읽고 편집하고 삭제할 수 있게 해주는 모듈들은 보통 Validation 도 구현이 되어있는 경우가 많습니다. 왜냐하면 그 모듈이 신뢰성 있게 동작을 할 수 있어야 하니까요! mongoose 같은 경우는 Model 을 정의할 때, Schema 에 validation 관련 내용들을 마구마구 집어넣을 수 있습니다.

이 단계에서 검증 대상은 거의 대부분 개발자가 입력한 값입니다. 그래서 타입 시스템이 빛을 충분히 볼 수 있습니다. Typescript 로 Node.js 앱을 만들었을 때의 이점을 톡톡히 볼 수 있지요. Typescript 는 실제로 코드가 동작하기 전에 검증을 수행할 수 있는 강력한 도구입니다. (그렇다고 검증이 완벽하지는 않습니다. 개발자의 실수를 크게 줄여줄 뿐입니다.)

이 단계에서의 검증 목적은 데이터베이스의 신뢰성 및 일관성 유지입니다. 결국 남는 건 데이터베이스고, 이 데이터베이스가 가장 잘 유지되어야 한다는 건 불변의 진리입니다. 그러므로 검증이 많으면 많을 수록 좋겠지만, 문제는 비즈니스 로직 단계 혹은 API 단계와 겹칠 수 있다는 것입니다. 예를 들어 사람의 나이를 입력한다고 했을 때, 우선 제일 먼저 클라이언트에서 검증을 해볼 수 있습니다. 하지만 서버 입장에서는 데이터가 어떻게 들어올지 모르니까 결국 API 단계 혹은 비즈니스 로직 단계에서 한번 더 검사를 해야 합니다. 문제는 mongoose 입장에서도 개발자가 이상한 음수 값을 넣어버릴 수 있잖아요! 에러는 언제 어떻게 터질지 모릅니다. 그러므로 mongoose 도 나이 필드가 음수가 아닌지를 일일히 체크해야 합니다.

이 단계에서 여러가지 문제가 터집니다. 우선 필드를 중복해서 찾아야 한다는 점이지요. 위 토론 내용 중간에서 나오듯 저도 하나의 validation 관련 파일이나 폴더를 만들어두고 필요할 때마다 써먹어야 하나 싶습니다.

아이디어

하나의 파일에 어떤 객체의 (검증을 포함한) 모든 것을 정의내릴 수는 없을까? 이는 제가 언젠가 한번 도전해보도록 하겠습니다.

  • Form Validation (브라우저)
  • GraphQL Schema
  • GraphQL Resolver
  • 몽구스 함수를 사용하는 비즈니스 로직 (Mongoose Wrapper Functions)
  • 몽구스 스키마 정의 (Mongoose Schema Definition)

마치며

간단하게 Validation 에 관한 사견을 풀어보았습니다. 여러분의 코딩에 도움이 되길 바랍니다.

2 thoughts on “데이터 검증 (Data Validation)은 언제, 얼마나 해야 할까?

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 항목은 *(으)로 표시합니다

Scroll to top