인프런 커뮤니티 질문&답변
MemberRegisterRequest 의 검증 방식으로 @Valid 와 초기화 스크립트의 차이가 무엇인가요?
작성
·
306
·
수정됨
0
안녕하세요, 토비님! 훌륭한 강의 너무너무 잘 듣고 있습니다. 토비님의 고견 덕에 제 개발 세계관을 정리하는데 도움이 많이 되고 있습니다. 감사합니다.
강의의 데이터 검증 파트에서 질문이 있습니다.
MemberRegisterRequest 의 검증 방법으로 jakarta.validation 을 사용하도록 설명해주셨는데요.
(1) MemberRegisterRequest 의 생성자 또는 팩토리 매서드에서 값을 검증하는 방식과 validation 어노테이션을 이용하는 방식을 어떻게 구분해서 쓰는지? 와 (2) validation 어노테이션이 활용되는 영역의 범위가 궁금합니다.
저는 코틀린 스프링부트로 강의를 따라가고 있어서 코틀린 기준으로 예시를 들면, 보통 저렇게 도메인 레이어에서 비즈니스 로직에 직접 사용되는 데이터를 검증하는 경우에는 코틀린의 init 블록 내에 비즈니스 관련 데이터 검증 로직을 넣어서 해당 클래스 객체의 데이터 정합성이 항상 보장되게 해왔는데요. (게다가 도메인 레이어의 클래스다보니 더욱 타 기술 의존 없이 검증하는 게 좋다고 생각해서 init 블록을 애용해왔습니다.)
반대로, Valid 는 외부로부터 입력 받은 데이터들의 아주아주 기본적인 데이터 타입 검증 용도(Nullable, 숫자, 이메일, 공백 여부 등) 정도로만 사용해왔습니다. 애초에 제공해주는 어노테이션의 기능이 비즈니스 의미를 담기엔 턱없이 부족해서, "데이터가 비즈니스 의미상으로는 틀릴 수 있어도, 타입 자체는 맞아" 정도만 보장해주는 용도라고 느꼈습니다.
그래서 강의에서 어플리케이션 서비스의 파라미터나 도메인 객체의 상태를 검증하는 용도로 사용하시는 모습이 조금 낯설게 느껴졌습니다.
어플리케이션 서비스 파라미터에 들어있는 데이터는 이미 컨트롤러에서 기본 검증은 끝난 데이터들이라고 생각해서요.
게다가 Valid 를 어플리케이션 서비스에서도 쓰기 시작하면, 컨트롤러와 서비스에서 중복 검사를 하게 될 것 같습니다.
이런 점들에 대해서 어떻게 생각하시고 어떻게 구분하시는지 그 기준이 궁금합니다.
Q&A 의 다른 질문들에 대한 토비님의 답변들을 보면서도 많이 배우고 있습니다. 고견 감사합니다! 🙂
답변 1
3
안녕하세요.
오브젝트의 불변식 또는 오브젝트가 파라미터로 전달하는 값의 규칙을 검증하는 방식은 꽤 여러가지가 있습니다.
가장 기본은 생성자 또는 초기화 코드를 이용해서 스스로 규칙을 체크하는 방법입니다. 가장 직관적이고 단순합니다. 그럼에도 이렇게 코드를 이용한 검증 로직이 많아지고 반복되면 코드에서 차지하는 비중이 커지고 코드의 가독성이 떨어지고, 유사한 검증 로직이 중복되는 현상이 일어날 수 있습니다.
이걸 극복하기 위해서 검증 유틸리티 클래스를 만들어서 자바라면 정적 메소드를 이용하거나, 코틀린이라면 확장 함수 또는 탑 레벨 함수를 이런 용도로 많이 사용합니다. 이런 걸 잘 활용하면 검증 코드의 의도가 명확히 드러나면서도 코드는 간결합니다. 저도 이 방식을 기본이라고 생각하고 좋아합니다.
그런데, 자바 bean validation 표준에 정의된 애노테이션도 간단한 검증에 많이 사용됩니다. 애노테이션은 선언적인 방식으로 규칙을 정의하기 때문에 검증 로직을 코드로 담는 것보다 더 명료하고, 프로퍼티나 파라미터에 선언할 수 있으므로 검증 대상과 같이 등장하기 때문에 응집도가 높아 보인다는 장점이 있습니다.
게다가 Spring MVC 컨트롤러 파라미터나, 서비스 빈의 메소드 파라미터로 사용되면 선언적인 방법 만으로 애노테이션 검증이 가능하다는 편리함이 있습니다. 검증 로직을 부차적으로 본다면 메인 비즈니스 로직과 분리해서 코드를 읽을 수 있기 때문에 코드를 이해하기도 편리합니다.
하지만 말씀하신대로 표준 애노테이션 방식은 단순한 몇 가지 규칙을 제외하면 지원이 안 되거나, 커스톰 애노테이션과 검증 로직을 적용하려면 코드가 장황하고 어려워집니다.
이번 강의에서는 사실 애플리케이션 코드의 검증을 어느 계층의 어느 단계에서 책임질 것인가, 어떤 방식을 사용할 것인가를 확정하지 않은 채로 검증 방식의 다양한 접근 방법을 우선 간단한 예를 중심으로 탐험해보는 느낌으로 할용해봤습니다.
굳이 이렇게 한 이유는, 어떤 상황에서든 최선이라고 딱 결정할 수 있는 방법이 없다고 생각하기 때문입니다. 개발이 진행되면서 검증 로직에 대한 개발팀이 느끼는 나은 방법을 찾아가는 과정을 점진적으로 담으려고 합니다. 다음 강의나 이어지는 로드맵 강의에서 검증 방식과 규칙, 코드 스타일을 애플리케이션의 특성이 드러나는 과정을 통해서 적절한 이유를 가지고 결정할 겁니다. 현재 방식이 유지될 수도 있고, 바뀔 수도 있습니다. 이게 저는 클린 코드, 또 클린 스프링의 접근 방법이 가지는 장점이라고 생각합니다. 그런 발견과 코딩 방식의 진화가 이를 보호해주는 테스트 코드와 리팩터링을 통해서 유연하게 발현될 수 있기를 기대합니다.
그런데 근본적인 검증 로직에 대한 변하지 않을만한 기준은 어느 정도 초반부터 정립이 필요하다고 생각합니다.
말씀하신 것처럼 컨트롤러에서 검증한 값을 왜 또 서비스 계층에서 검증하고, 그걸 또 도메인에서 검증하는가라는 생각을 할 수 있겠죠. 사실 100% 모든 검증을 모든 단계에서 다 하지는 않았습니다.
계층이 나뉘었을 때 각 계층을 사용하는 상위 계층은 하나 이상이 될 가능성이 있습니다. 서비스 계층은 웹 컨트롤러에서만 사용하지 않습니다. 배치 프로그램이나, 다른 독립적인 애플리케이션의 구성에 포함되는 경우 지금의 컨트롤러를 거치지 않은 채로 애플리케이션 서비스의 인터페이스를 타고 들어오는 요청이 있다면, 웹 계층에서 이를 검증했으니 서비스 계층에서 생략한 코드는 위험해집니다. 도메인 계층도 마찬가지입니다. 지금의 애플리케이션 서비스가 아닌 곳에서도 사용될 수 있는데, 그때 이를 사용하는 코드에 검증이 충분하지 않았다면, 예를 들어 널이 들어온다면 자신을 보호하지 못하고 논리적인 버그, 혹은 정합성, 불변식이 깨지는 상황이 생길 수도 있습니다.
그럼에도 반복되는 검증 로직은 항상 부담스럽긴 합니다. 그런 면에서 애노테이션을 통한 선언적 검증 기능은 유용합니다. 또, 애노테이션으로 충분하지 않은 경우는 지금의 Request 레코드 클래스 내에 자신을 검증하는 validate() 같은 메소드를 둘 수 있습니다. 그러면 컨트롤러, 서비스 계층에서 매번 값을 꺼내서 검증하는 if 문의 중복을 제거할 수 있겠죠.
코드가 만들어지고, 지금 시점에 어떤 선택을 할 것인가를 결정하는 것은 매우 중요하고, 어려운 작업입니다. 제가 지키려고 하는 것은 어떤 것이 최소한의 지켜야할 중요한 기준인지를 찾고, 그리고 탐험적으로 여러가지 시도를 해보고, 시간이 지나서 코드를 다시 보면서 코드를 이해하고 기능을 변경할 때 그렇게 만든 코드가 어떻게 느껴지는지, 어떻게 평가할 수 있는지 생각해보는 것입니다. 처음 코드를 작성할 때와 시간이 지나서 다시 볼 때, 또 단순한 기능만 만들었을 때와 기능이 많이 추가되고 복잡한 로직이 들어갔을 때 다르게 느껴질 수 있습니다. 또 중요한 기준으로 어느 시점에 개발에 함께 참여하는 개발자의 경험, 실력, 스타일 등에 따라서 합의할 수 있는 기준이 바뀔 수도 있습니다. 그에 따라 최선의 방법을 함께 의논하고 결정하고 그리고 언제든 다시 평가해보고, 생각이 바뀌면 코드를 자유롭게 개선하는 작업을 꾸준히 해나가는 것이 클린 스프링이라는 이름의 제 강의에서 제가 보여드리고 싶은 내용입니다.
생각하시는 더 나은 방법이나, 또 탐험 해 볼만한 좋은 아이디어가 있다면 언제든 남겨주세요. 저도 생각해보고, 다음 강의에서 다뤼볼 수도 있습니다.




