대부분의 애플리케이션은 Persistence, Service, Presentation 등과 같이 계층이 나뉜다.
@Component
와 @Repository
모두 스프링 어노테이션이다.
그 중 Persistence 계층의 구현체가 DAO를 사용할 때 둘 중 어느 걸 사용해야하는지 의문이 생겼다.
그 전에 DAO 패턴과 Repository 패턴 용어에 대해 알아보자
DAO(Data Access Object) 패턴이란 데이터 영속성의 추상화이며, 테이블 중심적인 패턴이다.
이를 사용하여 도메인 모델을 지속성 계층에서 완전히 분리된 상태로 유지할 수 있다.
public interface Dao<T> {
Optional<T> get(long id);
List<T> getAll();
void save(T t);
void update(T t, String[] params);
void delete(T t);
}
“repository is a mechanism for encapsulating storage, retrieval, and search behavior, which emulates a collection of objects.”
- Eric Evans
“mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.”
- Patterns of Enterprise Application Architecture
DDD(Domain Driven Development) 책의 저자인 Eric Evans는 Repository를 객체의 컬렉션을 관리하는 저장 및 검색 동작을 캡슐화하는 메커니즘이라 표현한다.
즉, Repository 패턴도 DAO 패턴과 유사하게 데이터를 처리하고 쿼리를 숨기는 것을 의미한다.
Repository 패턴 또한 DAO 패턴을 사용하여 데이터베이스에서 데이터를 가져오고 도메인 개체를 채우거나 도메인 개체의 데이터를 DAO를 사용하여 데이터베이스로 보낼 수 있다.
public interface UserRepository {
User get(Long id);
void add(User user);
void update(User user);
void remove(User user);
}
Indicates that the annotated class is a component.
Such classes are considered as candidates for auto-detection when using annotation-based configuration and classpath scanning.
- 스프링 공식문서
Spring 공식 문서에 따르면 @Component
어노테이션이 적용된 클래스는 어노테이션 기반 설정과 클래스 경로 스캔을 사용할 때 자동으로 후보로 등록된다고 나타낸다.
@Repository
어노테이션은 어떨까?
신기하게도 @Repository
의 구현을 살펴보면@Component
어노테이션이 명시되어 있음을 알 수 있다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Repository {
@AliasFor(annotation = Component.class)
String value() default "";
}
Spring 공식 문서를 참고하자면 @Repository
는 Repository 패턴을 사용하기 위해 쓰이며, DAO 클래스에도 이 타입을 명시할 수 있다고 표현한다.
Indicates that an annotated class is a "Repository", originally defined by Domain-Driven Design (Evans, 2003) as "a mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects".
Teams implementing traditional Jakarta EE patterns such as "Data Access Object" may also apply this stereotype to DAO classes, though care should be taken to understand the distinction between Data Access Object and DDD-style repositories before doing so.
- Spring docs
"명시할 수 있다"란 표현을 통해 순수하게 DAO 패턴을 사용할 때는 명확한 개발 의도를 나타내기 위해 @Repository
가 아닌 @Component
를 사용하는 것이 더 설득적이게 보인다.
그러나 @Repository
어노테이션은 다른 기능이 존재한다.
@Repository’s job is to catch persistence-specific exceptions and re-throw them as one of Spring’s unified unchecked exceptions.
- Baeldung
@Repository
어노테이션은 영속성 특정 예외(checked)를 잡아 Spring이 통합한 Unchecked 예외로 다시 던져준다.
우리가 계층 분리를 한 이유는 무엇일까?
많은 이유들이 있지만 그 중 구현체에 독립적으로 수행할 수 있게 하기 위함에 집중해보자
만약 JdbcTemplate을 사용하다 예외가 발생한 경우 checked 예외인 DataAccessException
가 발생한다.
여기에는 예외 메시지 등 영속성 계층과 관련된 정보가 포함되어 있다.
만약 이 예외를 try/catch로 처리하지 않는 경우 해당 예외는 상위 계층(Service Layer, Presentation Layer)으로 던져지게 된다.
이 과정에서 캡슐화를 위반했다고 볼 수 있다.
따라서 Spring이 의도적으로 Unchecked 예외로 던져 처리의 강제성을 제거해주었다.
또한 위의 기능을 사용하기 위해 @Repository
을 써야한다.
though care should be taken to understand the distinction between Data Access Object and DDD-style repositories before doing so.
- Spring docs
다만 공식문서에도 나와 있듯, 두 패턴에 대한 차이를 명확히 알고 사용해야 할 것이다.