데이터베이스의 제약 조건은 어디서 확인해야 하는가?
🔍 데이터 베이스의 제약 조건은 어디서 확인해야 하는가?
이펙티브 데이터베이스를 공부하던 중 재미있는 내용이 있어 생각을 정리해보았다. 다음은 책 내용의 일부이다.
이메일은 중복되면 안 된다와 같은 비즈니스 규치과 데이터 간 관계를 정리 하고 확인하는 것은 데이터 모델의 일부분이어야 하며, 그 역할은 애플리케이션 프로그램이 아닌 데이터베이스에서 수행해야 한다. 모든 사람이 같은 데이터로 작업하고, 이 데이터를 한 가지 방식으로 갱신되게 하려면 데이터 규칙이 애플리케이션에 독립적이어야 한다.
이 문장을 읽고 나서, 서비스 로직에서 유효성 검증을 하는 코드가 스쳐지나갔다. 다음과 코드이다.
1
2
3
4
5
6
7
8
9
private User googleSignup(UserDTO.GoogleUser googleUser) {
userRepository.findByUsername(googleUser.getName()).ifPresent(user -> {
throw new DuplicatedEntityException(ErrorCode.DUPLICATED_USERNAME);
});
...
User user = User.ofSocial(googleUser.getName(), googleUser.getEmail());
return userRepository.save(user);
}
- 코드에서는 같은 이름을 가진 사용자가 있는지 확인하고, 있으면 예외를 던진다.
이 책의 저자는 위 코드에서 구현된 findByUsername().ifPresent()
코드가 모범 사례가 아니라고 말한다. 데이터 처리 및 업데이트의 일관성과 통일성을 보장하기 위해 데이터 규칙은 애플리케이션과 독립적이어야 하며 데이터베이스가 데이터 규칙에 대한 무결성을 관리해야 되기 때문이다.
저자의 주장을 바탕으로 코드를 수정하면 다음과 같다. 데이터의 무결성 규칙을 데이터베이스에 위임하고, 애플리케이션에서는 데이터베이스가 던져주는 예외를 처리하도록 하자. (데이터베이스의 제약 조건은 설정되어 있다고 가정하자!)
1
2
3
4
5
6
7
8
9
10
private User googleSignup(UserDTO.GoogleUser googleUser) {
User user = User.ofSocial(googleUser.getName(), googleUser.getEmail());
try {
TAccountRepository.save(entity);
} catch (DataIntegrityViolationException e) {
log.debug("이미 존재하는 아이디입니다.");
throw new IllegalArgumentException("이미 존재하는 이메일입니다.");
}
return user;
}
데이터베이스 무결성 원칙을 지키지 않으면 SQLIntegrityConstraintViolationException
에러가 발생한다.
1
java.sql.SQLIntegrityConstraintViolationException: (conn=4849) Duplicate entry 'teester@gmail.com' for key 'UNIQUE'
이 방법의 문제는 데이터베이스의 제약 조건이 여러개 위반 되었을 때 세밀한 예외 처리가 불가능하다는 것이다. 예를 들어, 이메일과 이름이 모두 중복되었을 때, 어떤 필드가 중복되었는지 알 수 없다.
🔍 두 가지 방법의 비교
개인적으로 두 가지 방법을 비교해봤다.
- 쿼리 호출 횟수 비교
- 애플리케이션에서 유효성 검사를 하는 경우, 쿼리가 2번 호출된다. (findByUsername() 에서 한 번, save()에서 한 번)
- 데이터베이스에서 유효성 검사를 하는 경우, 쿼리가 1번 호출된다.
- 코드의 간결성
- 애플리케이션에서 유효성 검사를 하는 경우, 코드가 길어진다.
- 예외 처리의 세밀함
- 첫 번째 방식이 어떤 문제를 겪고 있는지 표현하기 쉽다.
- 두 번째 방식은 SQLIntegrityConstraintViolationException 에러를 던지기 때문에 폭 넓은 예외 메시지를 던지기 유리하다.
- 경쟁 상태에서의 문제
- 애플리케이션에서 유효성 검사를 하는 경우, 경쟁 상태에서 문제가 발생할 수 있다. (TOCTOU(Time of Check to Time of Use) 문제)
- 데이터베이스에서 유효성 검사를 하는 경우, 경쟁 상태에서 문제가 발생하지 않는다.
🔍 정리
- UX를 위해 에러를 정확하게 설명하고, 원하는 방향으로 사용자가 사용하게 만들기 위해서는 애플리케이션에서 유효성 검사를 하여 예외를 던지는 것이 좋다.
- 애플리케이션에서 유효성 검사릏 하더라도, 데이터베이스의 제약 조건을 설정되어야 한다.
- TOCTOU(Time of Check to Time of Use) 문제를 해결하기 위해서는 데이터베이스에서 유효성 검사를 하는 것이 좋다.