Spring Boot로 개발을 하다 보면 다양한 곳에서 검증을 고려하게 된다. 사용자의 요청을 직접적으로 처리하는 REST 컨트롤러부터 DB와 상호 작용하는 구성 요소까지 그 어디든 우리는 필요에 따라 검증을 강제할 수 있다. 다만 모든 곳에서 검증을 수행할 수는 없다. 우리는 다음과 같은 기준을 삼고 최적의 검증 장소를 결정해야 한다.
1. 무엇을 위한 검증인가?
ㄴ 우리는 이 검증이 왜 필요한지를 분명히 말할 수 있어야 한다.
2. 검증이 너무 중복되어 수행되지는 않는가?
ㄴ 검증이 필요 이상으로 빈번하게 이뤄진다면 자칫 오버헤드가 될 여지가 있다.
3. 검증은 성능을 고려하여 이뤄지고 있는가?
ㄴ 그렇잖아도 수행하는 데 오래 걸리는 구성 요소에 검증을 도입하는 건 좋지 못한 생각일 수 있을 것이다.
4. 검증은 필요한 모든 곳에서 영향을 발휘하고 있는가?
ㄴ 검증이 누락되는 로직 흐름이 발생하는 일은 없어야 할 것이다.
5. 검증이 간편하게 이뤄질 수 있는가?
ㄴ 명시적으로 if문을 사용하는 게 아니라 더욱 깔끔하고 관리하기 쉬운 검증 방식을 채용할 수 있다면, 그렇게 하는 게 좋을 것이다.
필자는 위의 기준 중에서도 특히 1번이 중요하게 다뤄져야 한다고 생각한다. 1번은 검증을 어디서 해야 하는지를 말해줄 뿐만 아니라, 어떤 검증이 이뤄져야 하는지에 대해서도 말해준다. 즉, 비즈니스적으로 검증할 필요가 있는 모든 것이 적절히 검증되기 위해서 우리는 어떤 가능한 예외 상황을 염두에 두어야 하는지 빠짐없이 살필 필요가 있는데, 이것이 검증이 이루어지는 구성 요소의 역할과 꼭 맞기를 바랐다. 이 과정이 합리적으로 이루어져야 우리가 받아들인 클린 아키텍처 또는 DDD도 그 이점을 유지할 수 있고, 개별적인 검증이 아닌 검증을 하는 목적을 기반으로 검증 로직을 조직화할 수 있다.
다만 의미만 따지다 보면 모든 구성 요소에서 검증하는 게 가장 좋은 선택지라는 결론에 다다를 수도 있다. 그 의미가 과연 얼마나 타당한지 분석하고 그 검증이 정말로 필요한지에 관한 논의를 보완하기 위한 방법으로 기준 2, 3번을 제시한다. 검증은 무료가 아니다. 검증도 어쨌든 한 종류의 로직으로써 유지보수성을 고려해야 하고, 오버헤드 등 비용에 비해 그 장점이 딱히 의미가 있지 않다고 생각되면 과감히 삭제할 수도 있는 것이다. 3번과 관련하여 지표를 숫자로 확인할 수 있다면 더욱 좋겠지만 아직 기반이 마련되지 않아, 지금 당장은 정성적인 판단이 주를 차지하는 채로 남겨둘 수밖에는 없는 점이 아쉽기는 하다.
4번 기준은 절대적이다. 반드시 달성되어야 하는데, 생각처럼 쉬운 내용은 아니라고 생각한다. 맨 마지막에 이어 작성한다. 5번 기준은 어쩌면 선택적인 고려 사항일 수도 있겠지만, 어떤 구성 요소에서 어떤 식으로 검증을 해야 좀 더 간결하게 같은 목적을 달성할 수 있을지보다는 우리가 이미 알고 있는 검증 관련 요소를 어떤 식으로 활용할 수 있을지에 중점을 뒀다. 예를 들어 Java Bean Validation이나 엔터티 클래스의 @Column을 통한 검증이 있다.
우리 사이드 프로젝트에서 검증이 필요한지 고려한 모든 구성 요소를 이야기하라고 하면 아래와 같을 것이다. 하나씩 살펴 보면서 우리 사이드 프로젝트에서 어떻게 검증을 설계했는지 같이 알아보자.
1. REST 컨트롤러(검증한다!)
REST 컨트롤러는 사용자로부터 요청을 받고 적절한 응답을 생성하여 내보내는 구성 요소이다. 우리는 이 구성 요소에서 검증의 의의를 다음과 같이 두었다: 클라이언트로부터 전달되는 데이터가 올바른 형식 및 조건을 갖추고 있는지 보장하기 위해 검증한다. 이 부분은 @Validated와 @Valid 어노테이션을 결합하면 검증하기도 쉽고, 서버에서 이러한 요건에 부합하지 않는 요청은 더 이상 무언가를 할 필요가 없다. 컨트롤러에서 이 검증을 처리함으로써 우리는 응답 시간을 단축하고, 정말 처리가 필요한 요청에 리소스를 할당할 수 있게 될 것이다.
2. 서비스(검증한다!)
사실 우리 사이드 프로젝트는 클린 아키텍처를 도입하고 있는 관계로 해당 구성 요소의 이름 또한 컨트롤러지만, 그런 건 현 맥락에서는 크게 중요한 것 같지는 않다. 이 구성 요소의 역할은 레이어 아키텍처든 클린 아키텍처든 상관없이, 비즈니스 로직을 수행하기 위해 필요한 구성 요소들을 오케스트레이션하는 것이다. 즉, 특정한 구성 요소를 사용해서 검증을 할 수 있는 중앙집중화된 장소로 볼 수 있다. 여기서 검증은 진정으로 비즈니스 로직의 일부로서 기능한다. 예를 들어 회원 프로필을 저장하는데 회원이 없다면, 무언가 문제가 발생해야 알맞지 않겠는가? 따라서 검증은 비즈니스 로직을 구성하기 위한 필수적인 일부가 되며, 이 구성 요소에서의 검증을 통해 우리는 더 이상 구성 요소 간 상호작용에서 발생할 수 있는 문제를 신경 쓰지 않아도 된다.
3. 도메인 모델(검증한다!)
DDD를 적용하든 그렇지 않든, 도메인 모델(여기서도 용어는 일반적인 것을 차용한다)을 올바르게 사용한다고 한다면 우리는 백엔드 코드베이스 안에서 쓰이는 도메인 모델이 무결하다는 것을 보장해야 한다. 도메인 모델은 우리가 비즈니스적으로 어떤 대상을 다루고자 하는지 그 자체이다. 단순히 데이터 홀더로써의 도메인 객체가 아니라, 비즈니스적으로 의미가 있는 도메인 객체를 다루는 건 ADM(Anemic Domain Model)과 RDM(Rich Domain Model)을 가르는 주요한 요소이다. 캡슐화가 잘 이루어진 도메인 객체를 비즈니스 로직에서 다루는 핵심 구성 요소로 삼음으로써 우리는 비즈니스 로직을 비즈니스를 대표하는 객체 간 상호 작용으로 구성할 수 있게 되며, 단순히 변수의 이름을 통해 여러 값들을 구분하는 것을 넘어 모듈러화되고 조직화된 코드를 작성할 수 있게 된다.
4. 영속성 관련 구성 요소(검증하지 않는다!)
영속성 관련 구성 요소는 크게 리포지토리와 엔터티 클래스로 구분할 수 있다. 리포지토리는 DB와 상호 작용하기 위한 구성 요소이며, 엔터티 클래스는 DB의 레코드에 포함된 특정 레코드를 대표하는 구성 요소이다. 우리 팀에서는 영속성 관련 구성 요소를 사용하기 전에 이미 모든 검증 요건이 충족되어야 한다고 믿는다. 영속성 관련 구성 요소에서 세부적인 검증은 모두 덜어내고 반드시 필요한 제약 조건(길이 제약 조건, NOT NULL 및 UNIQUE 등)만 유지하여, 영속성 관련 구성 요소가 본연의 의미에 충실할 수 있도록 하였다. 이렇듯 우리 프로젝트에서 DB는 따로 데이터에 대한 세부적인 제약을 가하는 존재라기보다는 비즈니스 로직의 결과를 저장하는 저장소로서의 역할 정도만을 수행하고 있다. 다만 이렇게 하면 실제 저장되는 데이터가 비즈니스에 따른 제약 조건을 100% 반영한다고 장담할 수는 없기에, 후속 논의가 있을 수도 있겠다.
5. 매퍼 관련 구성 요소(검증하지 않는다!)
매퍼는 서로 다른 두 구성 요소(계층)에서 달리 사용되는 데이터 형식을 서로 변환해주기 위해 사용된다. 팀 내의 합의에 따라 매퍼에서도 검증을 하기로 결정할 수도 있다. 예를 들어 회원 프로필 도메인 모델을 엔터티 클래스로 매핑할 때 회원 엔터티를 리포지토리로부터 가져올 일이 있다면, 이 데이터가 존재하는지 검증하는 식이다. 다만 우리 팀은 여기서도 검증을 도입하지 않기로 했는데, 그 이유도 위와 비슷하게 매퍼 관련 구성 요소가 본연의 역할에 충실할 수 있도록 하기 위해서이다. DB에 있는 데이터는 이미 검증을 마친 데이터로 간주할 수 있기 때문에 매핑을 한다고 해서 이러한 데이터의 제약 조건을 재검증할 필요는 없다고 생각했고, 매핑이 검증이 필요한 만큼 중심적인 로직이라고 생각하지도 않았다. 더구나 우리 프로젝트에서 매핑은 주로 도메인 모델과 엔터티 클래스 간의 매핑인데, 도메인 모델은 생성 과정에서 검증이 빠질 수가 없으므로 더더욱 필요성이 그리 크게 인지되지 않았다.
이렇게 우리 프로젝트에서 검증이 어떻게 이루어지는지 알아봤는데, 위에서 기준 4번과 관련하여 이야기를 끝맺지 않았다. 필자에게는 이전부터 혼란스러웠던 부분이 있었는데, 이 포스팅을 작성함으로써 좀 더 생각이 명확해진 듯하다. 기준 4번을 다루는 건 방어적 프로그래밍에 대한 고민을 할 수 있는 좋은 시작점이라고 본다. 현재 프로젝트에서는 논리적으로 빠뜨림 없는 검증을 진행하고 있다. 그렇다고 해서, 위에서도 언급했듯, 달리 검증 로직이 없는 엔터티 클래스에 비즈니스 조건을 모두 만족하는 값만 들어온다고 자신 있게 말할 수 있는가? 대부분의 경우 그렇겠지만 전적으로 확신할 수는 없을 것이며, 경우에 따라 단 하나의 문제가 서버의 가동을 중단시킬 수도 있을 것이다.
꼭 방어적 프로그래밍이 아니더라도 페일 패스트 원리나 유저 중심 디자인처럼, 필자가 알고 있는 개념 중에서도 예외를 어떻게 다뤄야 하는지를 다루는 방법론이나 디자인 철학 등이 존재한다. 이들을 공부하여 적용한다면 단순히 검증을 잘 수행하는 것을 넘어, 어떤 상황에서도 유저를 만족시킬 수 있는 웹 사이트를 개발하는 데 한걸음 더 나아갈 수 있을 것이다.
어떤 코드가 더욱 좋은 코드인가를 늘 고민하면서 현재 식별되는 문제점을 계속해서 개선해나가야겠다.
'웹 개발 (Spring Boot)' 카테고리의 다른 글
| 데이터 스토리지 솔루션 고르기 (2) (0) | 2026.01.10 |
|---|---|
| 데이터 스토리지 솔루션 고르기 (1) (0) | 2026.01.01 |
| 레이어 아키텍처에서 클린 아키텍처 + DDD로 리팩토링하기 (2) (1) | 2025.12.10 |
| 레이어 아키텍처에서 클린 아키텍처 + DDD로 리팩토링하기 (1) (0) | 2025.12.04 |
| 사이드 프로젝트 팀장으로서의 깨달음 (2) | 2025.06.28 |