Serivce 레이어를 통한 DAO와 Repository에 대한 고찰
0. 들어가기전에
스프링을 사용한 자동차 경주 미션을 하고 나서 주변 크루와 이야기를 해보았더니, Repository라는 계층을 둔 크루들이 많았습니다. Repository와 DAO의 차이를 몰라서 쓰질 않았는데 이번 기회에 공부하고 다음 미션에 어떻게 적용할 수 있을지 고민을 해보았습니다.
1. 예제 코드
기존 코드와 구조와 같은 다른 코드를 작성했습니다. 코드를 설명하자면 ScoreService
의 save
메서드는 학생의 데이터가 저장되었는지에 따라 다르게 동작하는 메서드입니다. 학생이 존재하면 점수만 저장하고, 학생이 존재하지 않으면 학생을 먼저 저장한 후 점수를 저장합니다.
[As - Is]
@Service
@Transactional
public class ScoreService {
private final StudentDao studentDao;
private final ScoreDao scoreDao;
public ScoreService(final StudentDao studentDao, final ScoreDao scoreDao) {
this.studentDao = studentDao;
this.scoreDao = scoreDao;
}
public void save(final Student student, final Score score) {
// 학생이 없으면 학생을 저장하고 점수를 저장
if (studentDao.doesNotExist(student)) {
final Long studentId = studentDao.save(student);
scoreDao.save(studentId, score);
}
// 학생이 존재하면 점수만 저장
scoreDao.save(student.getId(), score);
}
}
이번 미션에서 처음으로 서비스 레이어를 두었습니다. 기존에는 데이터베이스가 없었기 때문에, 컨트롤러와 도메인에서 애플리케이션 로직을 처리할 수 있었습니다. 하지만 데이터베이스가 추가 되니, 컨트롤러가 데이터베이스의 연결도 관리하게 되어 역할이 많아졌습니다. 그래서 서비스 레이어를 만들어 컨트롤러가 프레젠테이션 영역만 관리하도록 관심사를 분리했습니다. 추가로 마틴 파울러가 말하길 서비스 레이어는 비즈니스 로직을 캡슐화하고 트랜잭션을 제어한다고 하니 잘 분리가 된 것 같습니다.
하지만 여기에서 문제가 있는데 Dao의 메서드 2개를 서비스에서 조합하고 있는 상황입니다. 이게 왜 문제인가 하면 비즈니스 로직 상 학생이 존재해야지만 점수를 저장할 수 있습니다. 하지만 나중에 개발자의 실수로 두 메서드중 하나가 없어지거나 바뀌면, 버그가 생길 수 있습니다. 물론 테스트를 통해서 이 로직을 보호할 수는 있지만, 설계의 변경을 통해서 관리할 수도 있습니다. 또한 데이터베이스와 관련된 로직을 관리하는 것은 서비스 레이어의 목적이 아닙니다.
[To - Be]
@Service
@Transactional
public class ScoreService {
private final ScoreRepository scoreRepository;
public ScoreService(final ScoreRepository scoreRepository) {
this.scoreRepository = scoreRepository;
}
public void save(final Student student, final Score score) {
scoreRepository.save(student, score);
}
}
@Repository
public class ScoreRepository {
private final StudentDao studentDao;
private final ScoreDao scoreDao;
public ScoreRepository(final StudentDao studentDao, final ScoreDao scoreDao) {
this.studentDao = studentDao;
this.scoreDao = scoreDao;
}
public void save(final Student student, final Score score) {
if (doesNotExist(student)) {
saveNewStudentScore(student, score);
}
scoreDao.save(student.getId(), score);
}
public boolean doesNotExist(final Student student) {
return studentDao.doesNotExist(student);
}
public void saveNewStudentScore(final Student student, final Score score) {
final Long studentId = studentDao.save(student);
save(student, score);
}
}
Repository를 도입해서 save
의 로직(학생과 점수의 참조 관계)을 옮겨줬습니다. 이제 ScoreService는 데이터베이스의 저장 순서를 관리할 필요가 없어졌습니다. 이로써 Service레이어는 학생들의 CRUD만 관리하게 되고, Repository가 도메인간의 참조 관계를 관리하게 되었습니다.
2. DAO? Repository?
그렇다면 위에서 사용한 DAO와 Repository의 차이점은 무엇일까요?
DAO는 Data Access Object로 데이터베이스와 상호 작용을 담당하는 객체입니다. 좀 더 자세하게 알아보기 위해서 오라클 문서를 살펴보았습니다.
Use a Data Access Object (DAO) to abstract and encapsulate all access to the data source. The DAO manages the connection with the data source to obtain and store data.
DAO는 데이터 소스를 추상화하고 캡슐화하기 위한 객체로써 커넥션을 관리하는 객체라고 나왔습니다.
그러면 Repository는 어떨까요?
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
(중략)
Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.
Repository는 도메인과 데이터 매핑 레이어 사이를 중재하는 역할이면서, 객체지향을 위해 영속되는 객체들의 집합과 그 객체 간의 수행되는 작업들을 캡슐화한다고 나와있습니다.
위의 글을 통해, Repository와 DAO는 둘 다 데이터 매핑과 관련된 레이어 임을 알 수 있습니다. 둘의 차이점은 커넥션을 관리하는 데 중점을 둘 것이냐(DAO)
아니면 객체들의 작업에 집중할 것이냐(Repository)
가 되겠습니다.
3. 결론
Repository와 DAO는 둘 다 데이터베이스와 관련된 작업을 한다는 점에서 동일하지만,Repository가 DAO보다 더 높은 수준의 추상화를 한다는 점에서 다릅니다.
더 자세히 설명하자면, Repository는 객체들의 작업(혹은 관계)에 집중하는 레이어이고, DAO는 커넥션을 관리하는 데 집중을 합니다.