일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- RequestBody
- Test code
- enumSet
- OS
- backend
- db
- ObjectMapper
- 인프콘2023
- modelmapper
- 소프티어
- 공룡책
- softeer
- Test Doulbe
- FCM
- Spring
- Java
- Server
- 일상
- ExceptionResolver
- Service 계층 테스트
- JPQL
- Coputer Science
- MySQL
- 테크쇼
- 자바
- JPA
- Junit 5
- proxyFactory
- Test
- mapstruct
- Today
- Total
공부내용공유
spring validation (service, presentation 어디서 검증할까?) 본문
서론
프로젝트를 진행하다 보면 항상 검증에 대해 많이 고민을 하게된다.
Request로 들어올 때 부터 철저히 검사해야할지, entity를 만들고 검증을 해야할지 등등...
이처럼 검증에 관해서는 굉장히 다양한 상황들이 있고 은총알은 없다고 생각한다.
해당 글에서는 내가 고민하면서 찾은 내용들, 받았던 조언들을 정리할 것이다.
본론
먼저 해당 글에서는 흔하게 사용되는 Presentation, Service, Repository 계층으로 나누어진다는 가정하에 글을 작성할 것이다. (entity 패키지, business 계층 같은 부가적인 부분은 생략한다.)
간단한 객체 생성에 대한 API를 예시로 어떤 계층에서 검증을 했을 때 어떤 장,단점이 있는지 알아보자.
Presentation 계층
여기서 presentation 계층에서 검증을 한다는 것은 API에 대한 Request를 검증함을 의미한다.
해당 계층에서 검증 방식은 주로 spring에서 제공해주는 @NotNull
, @NotBlank
, @Validated
등의 기능을 사용한다.
물론 조금 더 복잡한 검증이 필요할 경우 어노테이션을 직접 만들어서 request의 형식에 대해서도 검증이 가능하다.
하지만 해당 방식에는 분명한 한계점이 보인다.
- 장점
entity
를 만들기전에 검증을 하므로entity
가 항상 완전함을 보장해준다.- 새로운 객체를 만들기 전에 검증을 해서 불필요한 객체 생성, 코드 수행을 안해도 된다.
- 단점
- 만약 다른 여러 API에 대한
request
들이 많아지면 일일히 검증 로직을 추가해줘야 한다. reqeust
가 아닌 다른domain
에서 값을 생성, 수정하게 되면 검증 로직을 따로 추가해줘야 한다.
- 만약 다른 여러 API에 대한
service 계층
service 계층에서 검증은 또 다양하게 나눌 수 있지만 일단 지금은 entity
를 만들고 나서 검증하는
방식을 의미한다.
검증을 할 때 private 메서드로 검증할 수도 있고 validator
와 같은 클래스 생성,
객체한테 각 검증 책임을 맡김 등등이 있다. 이러한 방식에서의 차이점들은 뒤에서 다룰 것이다.
- 장점
- 각
request
별로 검증 로직을 추가할 필요 없이 일단entity
를 생성하고 검증을 하여 검증로직의 재사용성이 좋다. - 구현 방식의 차이가 있겠지만 별도의 validator 클래스를 만들었을 경우 다른 domain에서도 검증 로직 재사용이 가능하다.
- 각
- 단점
entity
를 만들고 검증하기 때문에 불완전한 entity 가 생성되는 경우가 있다.entity
를 만들기 때문에 잘못된 요청이라도 메모리, 시간을 소요해야 한다.
그래서 어떤 계층에서 검증을 하는게 좋을까?
계층별 차이점은 이러한 것들이 있고 이 중 어떤 방식이 무조건 좋다고 얘기할 수 없다,
사실 각 계층별, 클래스별의 책임을 생각했을 때
- presentation :
NotNull
,length
와 같은 API 구현 스펙 검증 - service :
domain
이 가지는 규칙, 다른 값을 혹은 복합적인 값을 알아햐 하는 검증 로직
(사실 복합적인 값에 대한 검증이나 다른 값을 참조해야하는 경우는presentation
에서 검증은 한계가 있다.)
이정도의 기준을 가지고 적절히 섞어서 사용하는 것이 좋다고 생각이 들었다.
service 계층에서 검증은 어떤 방식으로 검증하는게 좋을까?
위에서 언급했던 검증 구현 방식을 분류해보면
- 클래스내
private method
로 선언 validator
와 같은 새로운 클래스 생성- 각 객체한테
검증 책임
을 위임
각 항목들에 대해 간단히 설명하고 장,단점을 정리해보자.
클래스내 private method
로 선언
public class OrderService{
public void save(SaveReq req){
Order order = req.toEntity();
validate(order);
//...
}
public void update(UpdateReq req){
//req 로 찾고 entity 업데이트
validate(order);
}
private void validate(){
//검증로직
}
}
이러한 방식은 규모가 작고 추가적인 변경, 확장이 아닌 프로젝트에서는 가장 간단하고 빠른 방식이라고 생각한다.
하지만 프로젝트 규모가 커지고 확장이 된다는 가정하에 분명한 단점들이 존재한다.
- private 메서드여서
test code
작성이 어렵다. - 만약 다른 도메인에서 entity를 생성, 변경할 경우 해당 도메인에 검증 로직을 또 작성해야 한다.
이처럼 실무에서는 적용하기 애매한 단점들이 있다.
validator
와 같은 새로운 클래스 생성
@RequriedArgsConstructor
public class OrderService{
private final OrderValidator orderValidator;
public void save(SaveReq req){
Order order = req.toEntity();
orderValidator.validate(order);
//...
}
public void update(UpdateReq req){
//req 로 찾고 entity 업데이트
orderValidator.validate(order);
}
private void validate(){
//검증로직
}
}
validator 클래스를 만들고 주입받아서 사용하는 방법이다.
이렇게 하면 private method
방식의 단점을 모두 해결할 수 있다. 하지만 결국 다른 도메인에서
해당 검증 로직이 필요하다면 해당 클래스를 주입받아야 한다.
이는 검증이 필요한 각 도메인마다 검증을 하는 클래스에 추가적으로 의존해야한다는 아쉬움이 있다.
각 객체한테 검증 책임
을 위임
해당 방식은 흔히들 말하는 VO
를 사용하는 방식이다. 예를 들어서 Order 라는Entity
에 Price
, Quantity
같은 필드들이 있다면
@Entity
class Order{
@Embedded
private Price price;
@Embedded
private Quantity quantity;
}
class Price{
private long value;
public Price(long value){
validate(value);
this.value = value;
}
private void validate(long value){
if(value < 0) throw new Exception();
}
}
class Quantity{
private long value;
public void update(long value){
validate(value);
this.value = value;
}
private void validate(long value){
if(value < 0) throw new Exception();
}
}
이러한 방식으로 VO
를 만들어서 각 객체에 검증의 책임을 맡기는 것이다.
이렇게 하면 다른 도메인에서도 객체를 건드려도 검증로직을 강제할 수 있고 별도의 의존을 추가로 할
필요가 없어진다. 보다 더 객체지향적인 코드라 생각한다.
물론 모든 검증 로직을 VO
에 넣을 수는 없다, 복잡하고 다양한 값들을 필요로 하는 검증 로직이라면validator
와 같은 클래스를 만들어서 검증을 해야할 것이다.
결론
결론은 사실 뻔한 얘기이다. 은총알은 없다.
각 도메인의 상황, 프로젝트의 상황(규모, 기획상 변경 가능성, 마감 기한...)등을 고려하여 적절하게
검증을 해주면 된다.
나도 아직 명확한 기준이 잡히진 않았지만 프로젝트를 하고 공부를 하면서 기준이 전보다는 명확히 잡혔고
앞으로도 나만의 기준을 만들기 위해 노력할 것이다.
'Spring > Spring' 카테고리의 다른 글
Mongo @CreateDate, @LastModifiedDate 오류 (feat: Spring Data Auditing) (1) | 2024.11.08 |
---|---|
getFile 사용시 주의점 (feat: FileNotFoundException) (0) | 2023.10.06 |
FCM 기능 구현 및 리팩토링을 하면서의 고민들 (0) | 2023.09.08 |
Spring Proxy Factory 뜯어보기 (0) | 2023.09.05 |
Test Code H2 DB 사용시 주의사항 (0) | 2023.07.28 |