엔티티를 가져오기위해 ID를 조회하는 로직이 많고 반복됨.
EntityFinder라는 클래스를 만들어서 공통 기능을 분리하여 중복을 제거하기로함.
사용하다보니 이 헬퍼클래스만으로 다양한 곳에서 사용될 경우 변경 전파의 위험성을 인지하게 됨. 또한 데이터 존재 여부와 조회의 역할을 서비스에서 해주는게 맞는가라는 생각이 듬.
고민을 하다 BaseRepository를 만들어서 모든 레포지토리가 BaseRepository의 인터페이스를 상속하여 변경 전파의 리스크도 줄이고 책임을 분리시키기로함.
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
default T findByIdOrThrow(ID id, ErrorCode errorCode) {
return findById(id)
.orElseThrow(() -> new CustomException(errorCode));
}
}
근데 이렇게 보니 결합도가 굉장히 높아졌음 모든 레포지토리가 BaseRepository에 의존하게 되는게 맞는건가?
상속을 통한 구현과 합성을 통한 구현에 대한 고민이 계속 됨. 상속을 통한 구현이나 합성을 통한 구현이나 변경 전파의 위험성이 있는건 똑같다는 생각이 들었음.
그렇다면 합성이냐 상속이냐에 초점을 두지 말고 결합도가 낮으면서도 변경전파를 최소화하기 위해 구성해보자 라는 생각이 듦.
EntityFinder라는 인터페이스를 두고 서비스가 구현체가 아닌 인터페이스에 의존하도록 설계하면 좋겠다라는 생각이 듦.
즉 헬퍼클래스의 구현을 변경하더라도 서비스에는 영향을 최소화 시키자!
import org.springframework.data.jpa.repository.JpaRepository;
public interface EntityFinder {
<T, ID> T findByIdOrThrow(JpaRepository<T, ID> repository, ID id, ErrorCode errorCode);
}
import org.springframework.stereotype.Component;
@Component
public class DefaultEntityFinder implements EntityFinder {
@Override
public <T, ID> T findByIdOrThrow(JpaRepository<T, ID> repository, ID id, ErrorCode errorCode) {
return repository.findById(id)
.orElseThrow(() -> new CustomException(errorCode));
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class LoggingEntityFinder implements EntityFinder {
private static final Logger logger = LoggerFactory.getLogger(LoggingEntityFinder.class);
private final EntityFinder delegate;
public LoggingEntityFinder(EntityFinder delegate) {
this.delegate = delegate;
}
@Override
public <T, ID> T findByIdOrThrow(JpaRepository<T, ID> repository, ID id, ErrorCode errorCode) {
logger.info("Attempting to find entity with ID: {}", id);
T entity = delegate.findByIdOrThrow(repository, id, errorCode);
logger.info("Entity found: {}", entity);
return entity;
}
}
import org.springframework.stereotype.Component;
@Component
public class MetricsEntityFinder implements EntityFinder {
private final EntityFinder delegate;
private final MetricsService metricsService;
public MetricsEntityFinder(EntityFinder delegate, MetricsService metricsService) {
this.delegate = delegate;
this.metricsService = metricsService;
}
@Override
public <T, ID> T findByIdOrThrow(JpaRepository<T, ID> repository, ID id, ErrorCode errorCode) {
long startTime = System.currentTimeMillis();
try {
return delegate.findByIdOrThrow(repository, id, errorCode);
} finally {
long duration = System.currentTimeMillis() - startTime;
metricsService.recordEntityFindTime(repository.getClass().getSimpleName(), duration);
}
}
}
'Programming > Spring' 카테고리의 다른 글
동시성 제어란 (0) | 2025.01.02 |
---|---|
이메일 인증 구현하기 (0) | 2024.12.31 |
ResponseDTO의 목적 (1) | 2024.12.07 |
빈(Bean)이 뭔데? (1) | 2024.11.22 |
어노테이션 정리 (0) | 2024.11.22 |