방탈출 예약 관리 미션을 제출하고 받은 코드 리뷰 내용 중 아래 내용이 있었다.
위 내용에 답변을 하기 위해서 공부해야 할 내용은 총 3가지였다.
위의 내용을 뜯어보며 차근차근 공부해보자!
스프링 공식 문서를 따라가보면,
This annotation also serves as a specialization of @Component, allowing for implementation classes to be autodetected through classpath scanning.
@Repository 어노테이션은 @Component 의 specific 한 케이스이다.
아래 Repository 구조를 살펴보면 @Component 를 포함하고 있다는 걸 알 수 있다.
@Target(TYPE)
@Retention(RUNTIME)
@Documented
@Component
public @interface Repository
@Repository annotates classes at the persistence layer, which will act as a database repository.
그리고 persistence layer 즉, Data access layer 에 사용된다.
그래서 Data access layer 가 뭐냐면,
Layered Architecture 에서 데이터베이스나 다른 저장 매체와의 상호 작용, CRUD(Create, Read, Update, Delete) 연산을 수행하는 계층이다.
여기까지 보면 우리는 Data access layer 에 @Repository 어노테이션이 붙는다는 걸 알 수 있고, 이제 결정해야 할 건 그래서 해당 클래스가 DAO 인지, Repository 인지 결정해야 한다는 것이다.
DAO 와 Repository 를 분리하는 것이 그렇게 중요한가? 라고 느낄 수 있지만 중요점은 스프링 공식문서에도 명시되어있다.
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. This annotation is a general-purpose stereotype and individual teams may narrow their semantics and use as appropriate.
DAO 가 제공하는 오퍼레이션이 Repository 가 제공하는 오퍼레이션보다 더 세밀하다.
여기서 세밀하다는 것의 의미는 데이터베이스의 CRUD 쿼리와 1:1 매칭되는 세밀한 단위의 오퍼레이션을 제공한다는 의미이다.
The API hides from the application all the complexity of performing CRUD operations in the underlying storage mechanism.
즉, DAO 는 하나의 테이블에 대한 저수준 CRUD 의 책임을 가진다.
@Repository
public class ReservationTimeDAO { // DAO 의 예시코드
private final JdbcTemplate jdbcTemplate;
@Autowired
public ReservationTimeDAO(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
public List<ReservationTime> findAll() {
return jdbcTemplate.query("SELECT * FROM reservation_time", rowMapper);
}
}
반면에 Repository 는 앱의 비즈니스 로직에 더 가까운 상위 레벨에 위치한다.
Repository 에서 제공하는 하나의 오퍼레이션이 DAO 의 여러 오퍼레이션에 매핑되는 것이 일반적이며 객체 컬렉션의 집합 처리에 관한 책임을 가지고 있다.
즉, 하나의 Repository 내부에서 다수의 DAO 를 호출하는 방식으로 Repository를 구현할 수 있다.
In other words, a repository also deals with data and hides queries similar to DAO. However, it sits at a higher level, closer to the business logic of an app.
Consequently, a repository can use a DAO to fetch data from the database and populate a domain object. Or, it can prepare the data from a domain object and send it to a storage system using a DAO for persistence.
아래 클래스에서도 ReservationTimeDAO 와 같이 CRUD 를 하고 있지만 Repository 인 이유를 살펴보자.
jdbcTemplate 쿼리를 통해 Read 를 하고 있는데 RowMapper에서 보면 2가지 추가 작업이 이뤄진다.
DAO 클래스 안에서 도메인 객체로 변환하는 작업과 여러 테이블을 조회해서 필요한 데이터를 불러오는 작업을 하고 있다.
@Repository
public class ReservationRepository { // Repository 의 예시코드
public List<Reservation> findAll() {
return jdbcTemplate.query(
"SELECT "
+ "r.id as reservation_id, "
+ "r.name, "
+ "r.date, "
+ "t.id as time_id, "
+ "t.start_at as time_value "
+ "FROM reservation as r "
+ "inner join reservation_time as t "
+ "on r.time_id = t.id",
rowMapper
);
}
private final RowMapper<Reservation> rowMapper = (resultSet, rowNum) -> new Reservation(
resultSet.getLong("reservation_id"),
resultSet.getString("name"),
resultSet.getString("date"),
new ReservationTime(resultSet.getLong("time_id"),
resultSet.getString("time_value")));
}
이제 ReservationTimeDAO 와 ReservationRepository 의 역할이 명확해졌다.
: ReservationTimeDAO ➡️ ReservationTimeRepository 로 변경
왜냐하면, 현재 ReservationTimeDAO 와 Repository 의 내부 구조는 같다. (ReservationRepository 가 Repository 의 책임을 더 가지고 있을 뿐)
이런 상태에서 DAO와 Repository 를 엄격하게 분리하는 것은 유지보수에 비용이 많이 들 것으로 예상된다. (추후에 혼동이 될 수 있다)
그런 의미에서 좀 더 포괄적으로 관리할 수 있는 Repository 를 선택하기로 했다.
DAO vs Repository Patterns
The DAO Pattern in Java
dao-vs-repository
@Component vs @Repository and @Service in Spring
Annotation Interface Repository