[Java] RedisTemplate를 활용하는(Spring Data JPA Repository를 사용하지 않는) Persistence Class에도 Repository 어노테이션을 명시하는 것이 유지관리에 유리한 이유(Layered Architecture의 정확한 활용을 위해 알아두면 좋은 원리들)

Hyo Kyun Lee·2025년 10월 2일
0

Java

목록 보기
103/106

1. 개요

조회수 기능을 구현하기 위해 View 도메인에서 Redis에 대한 영속성 계층을 구성하는 도중 흥미로운 궁금증이 생겼다.

Redis와 소통하기 위해 RedisTemplate을 활용해야 하는데, Spring DATA JPA Repository에서 제공하는 메소드를 사용하는 상황이 아니기에 Interface가 아닌 Class를 사용하였다.

/*
* Redis를 통한 조회수 처리 위한 Repository "Class"
* 실제 JPA Repository를 사용하는 것이 아닌 RedisTemplate 활용
* */
@Repository
@RequiredArgsConstructor
public class ArticleViewCountRepository {
    /*
    * Redis 의존성 주입
    * = Redis와의 통신
    * */
    private final StringRedisTemplate redisTemplate;
    
    ....,

이전같았으면 보이지도 않았을텐데, 확실히 깊게 공부하고 이를 실무에서 많이 접하게 되어서 그런지, 이때 사용하는 Repository 어노테이션이 단순히 "이 클래스는 영속성 계층에서 사용하는 클래스이므로 유의해서 사용하세요" 라는 의도를 표기하기 위한 용도가 아닐 것이라는 생각이 들었다.

이에 대해 공부하면서 Layered Architecture에서 영속성 계층으로 활용하기 위한 Spring boot의 내부적인 동작이 이루어짐을 알게 되었고, 더 나아가 확장성 등 프로젝트 유지관리에 유리한 점이 명확히 있었기에 반드시 필요한 부분임을 알게되었다.

이에 대해 공부한 내용을 기록한다.

2. 왜 Repository 어노테이션을 Class에도 적용해야 하는가?(권장이 아닌 필수)

결론적으로 Layered Architecture의 정확한 활용을 위해선, 영속성 계층에 선언한 Repository Interface든 Class든 무엇이든 반드시 Repository 어노테이션을 붙여야 한다.

일전에 살펴보았듯이 어노테이션은 Spring Boot의 생명주기와 강하게 연관되어 특정 기능 혹은 특정 체계에 연결될 수 있도록 정의/매핑해주는 역할을 한다.

이를 기반으로 이해해보도록 하자.

왜 @Repository를 붙였을까?

Repository 클래스는 Spring Data JPA의 Repository 인터페이스를 상속받은 건 아니고, 그냥 RedisTemplate을 활용하였으며, 엄연한 영속성 계층 클래스이다(다만 Customized Repository Layer에 가깝다).

사실 동작 자체는 해당 어노테이션을 붙이지 않아도 정상적으로 이루어지지만, @Repository를 붙이는 아래 중요한 두가지 이유가 존재한다.

#1. Spring이 해당 영속성 클래스를 Bean으로 등록한다.

기본적으로 Spring 컴포넌트 스캔 대상으로 등록하여, 이를 다른 layer에서 의존성을 주입받든 생성자 주입을 받든하여 사용하게 되는데 @Repository는 사실상 @Component의 특수화(또 다른 형태의) 어노테이션이다.

즉, 이 어노테이션을 붙이면 Spring이 자동으로 Bean 등록을 한다. 만약 붙이지 않았다면, Bean 등록을 위해 @Service나 @Component로 등록했어야 한다.

#2. Persistence Exception Translation (예외 변환)의 기술적 독립성을 보장한다.

@Repository의 가장 큰 특징 중 하나는, 데이터 접근 계층에서(즉 JPA 혹은 이에 준하는 영속성 계층에서) 발생하는 예외를 Spring의 DataAccessException 계열로 변환해줄 수 있다는 점이다.

여기서 중요한 점은 어떠한 형태의 데이터 접근 계층에서의 예외 상황이라도, Spring 차원에서 이를 추상화하여 동일한 형태의 DataAccessException을 제공해준다는점이다.

예를 들어 JPA, JDBC, Redis 등 각각 다른 데이터 접근 기술이 SQLException, RedisConnectionFailureException 같은 다양한 예외를 던지는데,
@Repository가 붙은 Bean에서는 AOP를 통해 이걸 스프링 공통 예외 계층(DataAccessException)으로 감싸서 던져준다.

개발자 입장에서는 이에 대한 예외처리를 굳이 번거롭게 고려하지 않아도, Spring 측에서 알아서 예외처리를 해주니 프로젝트 유지관리 입장에서 훨씬 효율적이고 간결하다.

즉, 단순히 "이건 영속 계층이다"라는 의미를 넘어 Bean 등록과 Spring 예외 변환 메커니즘을 적용하여 기술적 독립성을 보장하기 위한 목적도 존재한다.

3. 동일한 Persistence Layer이지만 Class와 Interface는 @Repository에 대한 동작이 조금 다르다.

객체를 만드는 차이가 있다.

위의 경우와 같이, ArticleViewCountRepository처럼 그냥 클래스에 @Repository를 붙이는 경우 컴포넌트 스캔 + 예외 변환 AOP만 적용한다.

JpaRepository 같은 인터페이스에 붙는 @Repository의 경우 Spring Data JPA가 프록시 객체를 만든다. Class가 아니므로, 객체를 만드는 과정이 별도로 필요하며 구현체를 등록할 때 @Repository 메타데이터를 활용해 동일하게 Bean 등록한다. 예외 변환을 적용하는 과정은 동일하다.

결론적으로 보자면, Class의 경우 객체가 바로 Bean으로 등록되어 Proxy 객체가 만들어지지 않지만, JPA Repository 인터페이스는 추가적으로 "동적 프록시 구현체 생성" 과정이 붙는다는 차이가 있다.

구분클래스(@Repository)인터페이스(@Repository)
Bean 등록 방식원래 클래스 그대로 등록Spring Data가 동적 프록시 구현체 생성 후 등록
구현체 존재 여부이미 존재없음 (프록시로 생성)
AOP 적용 여부필요 시 AOP 프록시가 붙을 수 있음기본적으로 프록시 기반이므로 AOP도 쉽게 결합 가능

4. Data Access Exception의 추상화를 통한 기술적 독립성의 필요성

공부하면서 느낀점 중 하나인데, 처음에는 바라보고 있는 DB가 다르다면 이에 따라 예외를 다르게 처리해줌으로써 예외원인 및 기점을 명확하게 아는 것이 중요하지 않을까라는 생각을 하였다.

하지만 실무에서는 완전히 이와 반대로 접근해야 한다. 오히려, 바라보는 DB가 달라질 수 있기 때문에 이에 대비하여 어떠한 DB가 오든 공통적으로 처리하고 수용이 가능한 추상적 접근이 필요하다.

기술적 독립성은 결국 프로젝트의 유지관리에 효율성을 더해주고, 개발자 관점에서 결합도가 낮은 Architecturing이 가능한 강력한 요인이다.

더 자세하게 살펴보자.

#1. 기술 독립성 확보 상세 설명

만약 지금은 JPA + RDBMS를 쓰다가, 나중에 MongoDB, Redis로 스위칭한다고 해보자.

예외 처리를 JPA/Hibernate에서 던지는 예외(EntityNotFoundException, ConstraintViolationException) 기준으로 해놨다면, Mongo/Redis로 바꾸면 전부 수정한 DB에 맞게 수정해야 한다.

하지만 DataAccessException은 공통 부모(추상화) 타입이니까, 어떠한 DB에든 적용가능하므로 데이터 접근 계층에서 발생한 예외를 다 한꺼번에 다룰 수 있으며 개발자 입장에서는 수정할 필요가 없다.

즉, 데이터 저장소 기술을 바꿔도 예외 처리 코드를 재사용 가능하게 해주기에, 유지 관리에 유리하다.

#2. 일관된 핸들링 가능

또한, 비즈니스 로직에서 “데이터 무결성 위반” 같은 상황은 DB 종류와 상관없이 동일하게 처리해야 하는데..DB마다 예외 클래스 이름, 패키지, 라이브러리가 전부 다르기에, 위와 같은 변경점이 발생하거나 확장해야 하는 상황이 발생한다면 해당 로직의 중복/복잡도 증가할 수 밖에 없다.

이에 대한 내용은 로직을 살펴보면 바로 이해할 수 있다.

// 추상화 없을 때
try {
    repo.save(entity);
} catch (ConstraintViolationException | SQLIntegrityConstraintViolationException | MongoWriteException e) {
    throw new BusinessException("데이터 무결성 위반");
}

위와 같이 DB마다 다른 예외상황을 관리하다가,

// DataAccessException 추상화 있을 때
try {
    repo.save(entity);
} catch (DataIntegrityViolationException e) { // DataAccessException 하위
    throw new BusinessException("데이터 무결성 위반");
}

@Repository 어노테이션을 붙이는 행위 하나만으로 에러핸들링을 추상화하여, 기술적 독립성 및 일관적인 프로젝트 유지 관리가 가능해졌다.

5. 결론

Layered Architecture에 대한 정확한 이해가 필요한 이유, 유지관리 필요성을 정확히 인지해야 하는 이유가 여기서 나타난다.

이를 유지관리하는 입장에서도, 비즈니스 로직/사용자 응답 처리에는 “데이터 계층 예외”라는 추상화 정보만 필요하기에 어노테이션 붙이는 과정을 안할 이유가 없다.

지금 공부한 내용을 바탕으로 어노테이션 동작 과정이나 layer에 대해 정확히 이해하면서, 프로젝트 유지관리를 하도록 하자.

0개의 댓글