내가 하고있는 이커머스 프로젝트에서는 최근 3일간의 판매 순위 Top5를 뽑아오는 Rank API가 존재한다.

RankingRepository.java

RankingService.java
JPQL로 3일전에 판매된 데이터를 모두 가져와서 같은 productId로 그룹핑 한 후
판매 개수를 SUM해서 List<Object[]> 형태로 반환해 주는데 이 코드에서
이런 피드백을 받았다.
VO? VO라는 말을 처음 들어봐서 어떻게 변경해야하는지 감이오지 않아서 이 글을 찌게되었다 :D
VO란 도메인에서 한 개 또는 그 이상의 속성들을 묶어서 특정값을 나타내는 객체를 의미한다.
생성자를 통해 한 번 생성되면 이후에는 내부의 값을 변경할 수 없어야 한다.
VO객체는 이를 보장하기 위해서 다음과 같은 작업이 필요하다.
Setter를 허용하지 않는다.
GC에 의해 폐기 될 때 까지 불변해야 한다.
Hassle-free Sharing(번거롭지 않은 공유)
: 사용도중에 값이 변경되지 않으므로 참조로 공유가 가능하며 side effect를 피하고, 동시에 코드의 복잡성과 부하를 감소시킨다. > 멀티쓰레드 환경에서 유용함!
Improved Semantics(향상된 의미)
: 명확한 이름과 동작을 가질 수 있게 된다.
이를 위해서 VO의 초기 클래스에는 생성자와 private 인스턴스 변수만 있어야 한다.
무의미한 인터페이스 생성을 피하고 의미 있는 이름과 동작을 가지게 된다.
* 새 인스턴스를 만들 때는 생성자 또는 static메서드 만을 사용한다.
* 현재 VO를 통해 새로운 VO를 생성한다.
* 내부의 데이터를 추출해 다른 타입으로 변환한다.
VO는 동등성 검사를 해야하고 각각의 값이 같다면 두 VO객체는 동일하다고 판단한다.
public class Car {
private final String name;
private final int position;
...
}
public class Main {
public static void main(String[] args) {
Car carOne = new Car("sun", 1);
Car carTwo = new Car("sun", 1);
}
}
두 자동차 객체 carOne과 carTwo를 생성했을 때, carOne과 carTwo는 다른 객체이지만 결국 동일한 내부 값을 가지고 있으므로 동등하다고 판단하는 것이 VO의 값 동등성이다.
✨ 이때 같은 값을 가지고 있는지 판단하는 메서드를 만들거나 equals와 hashCode()함수를 재정의 해야한다.
VO는 유효하지 않은 값으로 값 객체를 만들 수 없도록 유효성 검사를 시행해야한다.
필드 중 하나라도 유효하지 않다면 적절한 예외를 던져야 하는데 이러한 강제 검증은 도메인 제약조건을 의미 있고 명시적인 방식으로 표현하는데 유용하다.
VO가 필요한 이유는 primitive 타입이 도메인 객체를 모델링 하기 위해 충분하지 않기 때문.
//RankingRepository.java
List<Ranking> findByNowdateForRanking(@Param("threeDaysAgo") LocalDateTime threeDaysAgo, Pageable pageable);
//Ranking.class
import lombok.Getter;
@Getter
public final class Ranking {
private final Long productId;
private final Long orderCount;
public Ranking(Long productId, Long orderCount) {
this.productId = productId;
this.orderCount = orderCount;
}
}
//RankingService.java
// VO로 변경 시작
List<Ranking> orderedList = rankingRepository.findByNowdateForRanking(threeDaysAgo, pageable);
return orderedList.stream()
.map(r -> {
Long productId = r.getProductId();
Long orderCount = r.getOrderCount();
//VO로 변경 완료
Product product = productRepository.findByProductId(productId);
return new RankingResponse(
product.getProductId(),
product.getProductName(),
orderCount,
product.getPrice(),
product.getCategory()
);
}).collect(Collectors.toList());
참고할 만한 글을 찾아보다가 DTO와 DAO, VO 를 비교해 놓은 글을 많이 보게되었는데 이후에 DTO공부를 하면서 비교해 봐야겠다.
단순히 값만 받아와서 반환해 주는 역할을 하는거라면 VO를 자주 사용해야겠다.
참고