정의

스프링의 @Repository 의 주석을 확인해 보면 repository에 대한 정의를 에릭 에반스의 ddd에서 가져왔음을 알 수 있다. 따라서 이 정의를 이해하고 코드로 표현하면 될 것이다.

"a mechanism for encapsulating storage, retrieval, and search behavior which emulates a collection of objects".

객체의 컬렉션을 흉내내어 저장, 검색, 검색 동작을 캡슐화하는 매커니즘이다.

즉, 사용자 입장에서는 repository에 단순히 객체를 저장하고, 꺼낼 뿐 내부적으로 어떤 db에 저장을 하고, 꺼내는지, db가 쓰이는지 조차 신경 쓰지 않는다.

목적

repository는 결국 도메인 객체를 중심으로 사용하도록 설계되었다.
왜 이렇게 설계되어 있는지 생각해 볼 필요가 있다.
우리는 비즈니스 로직을 객체 지향의 핵심 원리인 객체들의 협력을 이용하여 구성하고자 한다.
repository를 도메인 모델에 추가함으로써 비즈니스 로직에 영속성과 관련된 코드가 등장하지 않고 POJO를 지켜서 객체들의 협력으로 구성한다.

구현

다음 코드는 웹 자동차 경주 미션에서 구현한 서비스의 play메서드이다. 다른 코드는 신경 쓰지 말고 주석과 함께 흐름을 살펴보자.

@Transactional  
public ResultResponseDto play(RequestDto requestDto) {  
    RacingGame racingGame = initGame(requestDto);  // RacingGame 초기화
    racingGame.moveCars();  // RacingGame 진행
    RacingGame save = racingGameRepository.save(racingGame);  // repository에 저장

    return new ResponseDto(save);  
}

도메인 객체인 RacingGame을 초기화하고, 진행하고, 저장하고 있다.
단순히 객체를 저장한다는 것만 나타내기 때문에 코드는 매우 간결하고 쉽다.
서비스가 의존하고 있는 RacingGameRepository 인터페이스는 다음과 같다.

public interface RacingGameRepository {    
    RacingGame save(RacingGame racingGame);
}

repository를 사용하는 service에서는 도메인 객체인 RacingGame을 저장할 뿐 내부 구현은 캡슐화되어있기 때문에 DB를 사용한다는 사실조차 알 수 없다.

이렇게 사용하면 DB가 바뀌거나 심지어 DB를 사용하지 않고 메모리에 객체를 저장한다 하더라도 서비스 로직은 변경할 필요가 없다. 구현체만 바꿔주면 된다.

다음은 DAO를 사용하여 repository를 구현한 예시이다.

@Override  
public RacingGame save(RacingGame racingGame) {  
    gameDao.updateGame(RacingGameMapper.mapToGame(racingGame));  
    playerDao.insertPlayer(RacingGameMapper.mapToPlayer(racingGame));

    return racingGame;  
}

repository의 구현체가 DAO를 사용한다는 코드를 확인하고 나서야 DB에 데이터를 저장한다는 사실을 알 수 있다. 위 코드에서는 RacingGame에 해당하는 데이터를 RacingGameMapper를 이용해 Game 엔티티와 Player 엔티티로 각각 변환해서 저장하고 있다.

다음은 DB에 저장하지 않고 메모리상에서만 저장되고 지워지는 repository를 구현한 예이다.

public class InMemoryRacingGameRepository implements RacingGameRepository {  

    private static final List<RacingGame> racingGames = new ArrayList<>();  

    @Override  
    public RacingGame save(RacingGame racingGame) {  
        racingGames.add(racingGame);  
        return racingGame;  
    }
}

이렇게 추상화된 repository를 사용하면 테스트가 쉽다는 장점도 있다. 실제 웹 애플리케이션에서는 위에서의 dao를 사용하는 repository를 사용하도록 하고, 테스트에서는 메모리에서만 동작하는 repository를 사용한다면 db에 의존하지 않고 쉽고 빠르게 테스트를 진행할 수 있다.

결론

repository는 도메인 객체를 저장하고 꺼내는 객체의 컬렉션처럼 사용한다.
이러한 메커니즘에 의해 도메인 중심으로 데이터를 저장하고 꺼낼 수 있으며 이 방법은 비즈니스 로직을 객체들의 협력으로 구성하도록 한다. 또한, 특정 DB나 구현체에 의존하지 않기 때문에 변경에 강하고 테스트를 쉽게 진행할 수 있다.

+ Recent posts