데이터를 관련 계층에 대해서 고민을 하다보니 DAO와 Repository의 개념을 접하게 되었고 이 둘은 비슷하면서도 다른 점이 있어서 정리하는 시간을 가져보려고 합니다.
먼저 DAO에 대해 알아보도록 하겠습니다.
DAO는 Data Access Object의 약자로 jpa 공식문서에서는 다음과 같이 정의되어 있습니다.
Data Access Object - Pattern to separate persisting logic from the object to be persisted
위의 내용을 쉽게 정리하면 DAO는 데이터베이스를 접근하여 데이터를 저장, 검색, 업데이트 및 삭제하는 작업을 수행하는 객체
라고 할 수 있습니다. 데이터베이스에 대한 접근과 관련된 기능들을 캡슐화(상위 계층으로 하여금 퀴리문을 가려준다.)하여 비즈니스 로직과 분리하는 데 도움을 줍니다.또한 DAO는 데이터베이스 테이블과 1대1로 매치가 됩니다.
다음으로 Repository에 대해 알아보겠습니다.
Repository는 저장소의 계층이며 Eric Evans의 책인 Domain-Driven Design에서는 다음과 같이 정의되어 있습니다.
repository is a mechanism for encapsulating storage, retrieval, and search behavior, which emulates a collection of objects.
위의 내용을 간단하게 정리하면 Repository는 객체의 컬랙션을 대상으로 저장, 복구, 검색에 대한 기능을 캡슐화하는 매커니즘
이라고 할 수 있습니다. Repository 역시 데이터에 접근하고 검색하고 복구하는 계층이라고 볼 수 있는데 Repository는 데이터 매핑 계층과 도메인 사이에 위치합니다.
다시 생각해보면 Dao와 Repository는 모두 데이터 저장, 검색, 수정, 삭제와 같은 역할을 합니다. 그러면 Dao와 Repository는 같은 역할을 하는 것인가? 라고 생각했을 때에는 비슷한 역할을 하지만 엄연히 따지면 아니다
라고 말하고 싶습니다.
Dao는 Repository에 비해서 테이블의 데이터 접근과 관련된 기능(SQL문 활용)
을 한다면 좋을 것 같고 Repository에서는 비즈니스적인 측면에서 데이터과 관련된 기능
을 한다고 생각하면 좋을 것 같습니다. 예시를 들어서 간단하게 설명하겠습니다.
이번 자동차 게임 웹 미션에서는 다음과 같은 요구사항이 있었습니다.
위 요구사항에 맞게 결과를 저장하기 위해서는 game 테이블과 player 테이블이 필요했고 이 두 테이블은 1대N의 관계를 가지고 있습니다.
CREATE TABLE game (
game_id INT NOT NULL AUTO_INCREMENT,
play_count INT NOT NULL,
winners VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL default current_timestamp,
PRIMARY KEY (game_id)
);
CREATE TABLE player (
player_id INT NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
position INT NOT NULL,
game_id INT NOT NULL,
PRIMARY KEY (player_id),
CONSTRAINT fk_game_id FOREIGN KEY (game_id) REFERENCES game(game_id)
);
그러면 dao
의 경우 각 테이블에 1대1로 매치가 되도록 gameDao와 playerDao를 가지고 있어야 합니다.
public class RacingCarGameDao {
private final JdbcTemplate jdbcTemplate;
public RacingCarGameDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Long insertGameWithKeyHolder(Game game) {
// 게임 결과를 저장하는 기능
String sql = "INSERT INTO game(play_count, winners) VALUES(?, ?)";
...
}
}
public class PlayerDao {
private final JdbcTemplate jdbcTemplate;
public PlayerDao(final JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void insertPlayers(Player player) {
// 플레이어를 저장하는 기능
String sql = "INSERT INTO player(name, position, game_id) VALUES(?, ?, ?)";
...
}
}
위의 코드는 각 테이블(game, player)에 데이터를 저장하는 코드입니다. 우리는 이 DAO를 활용해서 미션의 요구사항인 게임결과(우승자, 시도횟수, 참가자)를 저장한다.
만족할 수 있습니다. 이 때 Repository 계층을 이용할 수 있습니다.
public class RacingCarRepository {
private final RacingCarGameDao racingCarGameDao;
private final PlayerDao playerDao;
public RacingCarRepository(final RacingCarGameDao racingCarGameDao, final PlayerDao playerDao) {
this.racingCarGameDao = racingCarGameDao;
this.playerDao = playerDao;
}
public void save(RacingGameDto racingGameDto, List<PlayerDto> playerDtos) {
int gameId = racingCarGameDao.insertGameWithKeyHolder(new Game(racingGameDto));
playerDao.insertPlayers(new Player(playerDto.getName(), playerDto.getPosition(), gameId);
}
위의 코드와 같이 Repository의 계층에서는 비즈니스 로직에 만족할 수 있게 DAO를 활용하는 것을 볼 수 있습니다. 이렇게 하는 이유는 상위계층에서는 저장순서(game 테이블에 데이터가 저장이 되고 id값을 받아서 player의 foriegnkey로 저장)를 고려하지 않고 데이터를 저장할 수 있다는 장점이 있습니다. 즉, 상위 계층에서는 repository 계층이 생김으로써 데이터 관리에 대한 관심사를 분리한 것으로 볼 수 있습니다.