우아한테크코스/모락 프로젝트

JPA entity에 validation annotation을 붙인 이유

자바지기 2022. 8. 28. 00:14
반응형

mo-rak.com

예시로 사용된 코드는 깃허브를 통해 확인할 수 있습니다. 

validation annotation을 붙인 이유와 생성자 검증의 맹점에 대해 작성한 글입니다.

DB에 있는 데이터들을 100% 신뢰할 수 있다!라고 생각한다면 이 글은 읽지 않으셔도 좋습니다.

 

 

모락 프로젝트를 진행하면서, Entity 필드의 검증 로직이 존재해야 하는 곳에 대한 토론을 많이 진행했습니다. 우리는 토론의 결과로 검증을 위해 Entity 필드에 validation annotation을 붙이는 것을 택했습니다.

 

모락에서 사용되는 Entity에 대한 예시로는 다음과 같습니다.

 

Entity 필드에 대해 올바른 값인지 확인하는 방법은 2가지가 있습니다.

1. 위와 같이 각 필드에 validation annotation을 붙이기

2. Entity 객체 생성 시 생성자에 검증 로직을 작성하기

 

이 글에서는 우리가 1번 방법을 택한 이유에 대해서 중점적으로 다룰 예정입니다.

 

Entity에 validation annotation을 사용하면 어떻게 될까?

첫 번째로 Entity에 validation annotation을 사용하면 어떤 방법으로 적용되는지 알아보겠습니다.

이를 위해 간단하게 테스트 코드를 작성해보았습니다. 테스트에 사용되는 Entity는 다음과 같습니다.

Member Entity 필드에 붙인 valid annotation에 해당하는 조건은 다음과 같습니다.

 

1. name의 길이는 10자 이하여야 한다.

2. age는 15살 이상이어야 한다. 

 

이 두 조건을 가지고 테스트를 진행해보겠습니다.

 

Entity 객체가 생성될 때 validation annotation이 적용될까?

 

정답은 X입니다. 다음의 테스트를 확인해보겠습니다.

 

위의 테스트는 통과합니다. 

단순히 객체를 생성하는 과정에서는 validation annotation이 적용되지 않습니다.

이유는 간단합니다.

생성자에서 validation annotation이 적용되지 않기 때문입니다.

@Builder
public Member(Long id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
}

 

Entity를 생성하는 것이 가능하다면 validation은 어디에서 적용되는 것일까?

 

다음 테스트를 살펴보겠습니다.

위의 테스트는 통과합니다.

Member Entity를 생성하는 것은 가능하지만 해당 Entity가 영속성을 가지게 될 때 validation annotation이 적용됩니다.

 

이 과정을 천천히 살펴보면 다음과 같습니다.

 

1. 적절하지 않은 필드 값을 가지고 있는 멤버를 save 한다.

memberRepository.save(멤버)

 

2. JpaRepository의 구현체인 SimpleJpaRepository의 save 메서드에서 em.persist(entity)를 실행한다.

즉, entity가 영속성을 가질 수 있게 합니다.

 

3. em.persist(entity) 실행 중에 validation annotation 검증을 하여 검증에 실패하면 예외를 던진다.

즉 entity가 영속성을 갖기 전에 validation annotation이 적용됩니다.

 

 

정리하자면 validation annotation은 Entity가 영속성을 가지려 할 때 적용된다!!!

 

이 annotation 방식과 생성자에서 검증을 진행하는 방식의 차이는 무엇일까?

이 또한 테스트를 진행하면서 확인해보겠습니다.

다음과 같이 생성자에서 검증을 진행하는 Member2 클래스를 생성하였습니다.

 

Member2 Entity는 생성자에서 검증을 진행하므로, 잘못된 값으로는 Entity를 생성조차 하지 못합니다.

 

하지만 다음과 같은 문제가 발생할 수 있습니다.

위의 테스트는 성공합니다.

올바른 값으로 멤버2를 save 한 뒤, 영속성을 가지는 멤버2의 age 필드를 잘못된 값으로 변경해도 검증 로직이 동작하지 않습니다. 멤버2에서는 생성자에서만 검증 로직이 실행되기 때문에 생성된 이후에는 검증 로직이 실행되지 않아서 생기는 문제입니다.

 

이 문제는 validation annotation을 붙인 Entity에서 해결할 수 있습니다.

위의 테스트는 성공합니다. 

memberRepository.flush() 동작 중에도 validation annotation이 적용되기 때문에 테스트가 성공했습니다.

 

하지만 이 문제는 validation annotation이 아닌 다른 방법으로도 해결할 수 있습니다. 

 

Member2 Entity에서 생성자뿐만 아니라 필드를 변경할 때도 검증 로직을 추가하는 것입니다.  

즉, setter 메서드에 검증 로직을 추가하거나 필드 자체를 VO의 형태로 만들어서 값을 변경할 때 무조건 검증 로직을 거칠 수밖에 없도록 만들면 이 문제를 해결할 수 있습니다.

public void setName(String name) {
    validateName(name);
    this.name = name;
}

public void setAge(int age) {
    validateAge(age);
    this.age = age;
}

 

하지만, 위와같이 검증 로직을 추가해도 문제가 발생할 수 있습니다.

테스트 코드를 통해 알아보겠습니다.

 

테스트 작성 전 다음과 같이 잘못된 값으로 DB에 데이터를 삽입하였습니다.

 

이 데이터를 이용하여 문제가 되는 테스트를 작성해보았습니다.

위의 테스트는 통과합니다.

DB 데이터는 도메인 로직에 어긋나는 값을 가지고 있었지만, Member2를 조회할 때 생성자를 거치지 않기 때문에 Entity가 생성될 수 있었습니다. 즉, member2.findbyId()를 실행할 때 Member2의 생성자를 거치지 않기 때문에 Entity가 생성되었고, 이 Entity안의 필드까지 수정할 수 있었습니다.

 

현재 Member2의 필드 값으로 name에는 "10글자가 넘어가는 이름으로 저장" 이 존재하고, age에는 25가 존재합니다.

 

 

모락 팀에서는 이 상황을 해결하고자 하였습니다.

한 팀원이 실수로 DB에 잘못된 데이터를 넣었을 때 혹은 어떤 상황이건 DB에 잘못된 데이터가 들어왔을 때, 이 데이터를 이용해 위 테스트와 같은 작업을 수행할 수 있게 할 것인가? 에 대한 답으로 

안된다. 

라는 것이 우리 팀의 의견이었습니다.

결론적으로 DB에 있는 데이터들을 100% 신뢰할 수 없다!라는 것입니다.

 

생성자, setter에서의 검증만으로는 우리 팀이 고민하던 문제를 해결할 수 없었습니다. 

 

위와 같이 validation annotation을 붙인 Entity에서는 우리 팀이 고민하던 문제를 해결할 수 있었습니다.

 

 

그렇다면 validation annotation을 통한 검증은 무조건 좋은 것일까?

답은 X라고 생각합니다. 

 

문제 1. Entity 객체 생성 자체는 가능합니다.

valid annotation은 Entity가 영속성을 얻게 될 때 적용되는 것이기 때문에, 영속성을 갖지 않는 Entity 생성 시점에는 Entity 내부에 검증되지 않은 값이 존재할 수 있습니다.

 

문제 2. 이 검증 방법은 JPA에게 검증 역할을 부여한 것입니다. 즉, JPA에게 심하게 의존하고 있는 방법입니다.

문제 1처럼 검증되지 않은 값으로 Entity를 생성할 수 있는데, 영속성이 존재하는 환경에서만 Entity에 대해서 검증이 가능하므로,영속성이 존재하지 않는 환경에서는 제대로 동작하지 않는 코드가 되어버립니다.

 

 

문제 3.  각 필드마다 annotation이 붙어있기 때문에 코드의 가독성이 심하게 저하됩니다. 

필드가 많아질수록 코드를 읽기 어려워집니다.

 

생성자 검증 방식, validation annotation을 모두 작성하면 해결할 수 있을까?

다양한 에러 상황을 해결하기 위해 검증할 수 있는 모든 방법을 적용한다면 보안적인 측면을 강화할 수 있을 것 같습니다.

DB에 있는 데이터들을 100% 신뢰할 수없다!라고 생각이 든다면, 두 가지 방법을 모두 적용하는 것이 좋을 것 같습니다.

 

하지만 개발 과정에서 모든 방법을 적용하는 것은 쉽지 않은 일입니다. 모락 팀은 개발하는 시간이 한정되어있었기 때문에 모두 적용하지 못했고, JPA에 의존적인 검증 방식으로 진행했습니다. 

 

이에 대해 우리 팀은 DB에 있는 데이터들을 100% 신뢰할 수 있다! 라고 생각을 바꾸어야 할지, 

DB에 있는 데이터들을 100% 신뢰할 수 없으므로 두 가지 방법을 모두 적용해야 할 지에 대해서 결정할 예정입니다.

 

 

모든 것은 trade off...

 

모락 깃허브 : https://github.com/woowacourse-teams/2022-mo-rak

반응형