본문 바로가기

개발/루퍼스(Loopers)

JPA CountBy 조회 vs 엔티티 필드 저장, DDD에서는 무엇을 선택해야 할까?

반응형

쇼핑몰 서비스를 만들다 보니 제품에 눌린 좋아요의 총 개수를 계산할 때 Product 엔티티에 likeCount 같은 상태 필드를 추가할 것인가,
아니면 Like 테이블에서 countByProductId()로 매번 조회해서 계산할 것인가라는 선택의 기로에 섰다.

 

일부분만 코드로 적어보자면

// Product Entity
@Column(name = "like_count", nullable = false)
private Long likeCount = 0L;

public Long getLikeCount() {
    return likeCount;
}

public void increaseLikeCount() {
    this.likeCount = (this.likeCount == null ? 1L : this.likeCount + 1L);
}

public void decreaseLikeCount() {
    long current = this.likeCount == null ? 0L : this.likeCount;
    this.likeCount = Math.max(0L, current - 1L);
}

이렇게 Product에 필드를 두고 “좋아요 수”를 상태(state)로 관리하는 방식이 있는가 하면,

 

long totalLikes = likeRepository.countByProductId(productId);

처럼 Like 테이블을 매번 조회하는 방식도 있다.

 

 

나는 개발하는 입장에서 단순히 JPA가 제공하는 메서드인 CountBy를 사용하는 것이 간단하지 않나? 생각하였으나 멘토님과 많은 이야기를 나누면서 두 방식 각각이 가진 장단점을 더 깊게 이해하게 되었다.

 

우리는 서비스를 개발하는 입장에서 유지보수 관점을 고민하고 결정을 내려야 한다. 물론 지금은 CountBy 한 줄로 쉽게 조회가 가능하지만, 비즈니스 요구가 늘어나고 쿼리가 복잡해지기 시작하면 Native Query를 작성해야 하는 순간이 오고, 그때부터 유지보수 난이도는 급격하게 증가한다.

 

또한 현재는 유저가 적어 응답이 빠르지만, 데이터 행이 수십만·수백만 단위로 늘고 정렬 + 페이징 + JOIN 조건까지 붙게 된다면 응답에 수 초가 걸리고, DB Connection Pool이 소진되어 병목이 생길 가능성도 높다.

 

한 마디로 서비스 규모가 커질 시유지보수 측면이나, 성능적인 측면에서 불리하다는 것이다. 

 

그렇다면 likeCount 같은 상태 필드를 추가하면 어떠할까? 

 

Product.totalLikes를 상태로 관리하면 좋아요 요청이 들어올 때마다 필요한 작업은 단 두 가지뿐이다.

  1. Product.likeCount 증가 (DB UPDATE 한 번)
  2. Like 엔티티는 기록용으로 append (필요 시 비동기 처리 가능)

즉, 좋아요 증가 연산이 O(1)로 고정된다.

 

더 나아가 Like INSERT를 메시지 큐나 이벤트 기반으로 비동기 처리하여 유저에게는 즉시 반영되는 것처럼 보이고, 서버에서는 추후에 일관성을 확인하는 방식도 적용할 수 있다. 이것은 필드 방식에서만 가능한 강력한 장점이다.


 

여기까진 성능과 유지보수 관점에서는 필드 방식이 유리하다는 점을 살펴보았다. 그렇다면 이제, 우리는 한 단계 더 들어가서 DDD 관점에서는 어떤 방식이 더 적절한가를 고민해볼 필요가 있다.

 

1. 우선 전에 배웠던 도메인 언어(Ubiquitous Language)적 관점으로 본다면 좋아요는 결국 상품이 가지는 도메인 상태다. 그렇기에 상품의 속성으로 속해있는 것이 옳다고 생각한다. 

 

2. 추가적으로, CountBy 방식은 Aggregate 경계를 흐린다.

대표적인 예시로 실제 구현 단계에서 정렬 조건으로 이 값을 사용해야 한다는 점도 중요한 근거가 된다.
예를 들어 다음과 같은 Repository 메서드를 생각해보자.

List<Product> findAllByOrderByLikeCountDesc();

 

이 메소드를 구현하기 위해선 좋아요 수(LikeCount)는 Product가 직접 가지고 있어야 정렬이 가능하다. 

그런데 좋아요 수가 Like 테이블의 CountBy로 계산되는 방식이라면, Product는 자신의 핵심 상태(좋아요 수)를 직접 관리하지 않고
외부 테이블의 계산 결과에 의존하게 된다. 이는 도메인의 경계를 망가뜨리고 응집도를 약화시킨다. 


결론적으로 필드 기반 구현이 DDD 원칙과 실무 모두에 부합하는 올바른 선택이다.

 

반응형