백엔드 코드를 보다 보면 repository라는 이름을 정말 자주 보게 됩니다. 그런데 입문 단계에서는 이게 단순히 “DB 관련 코드 모아둔 파일”처럼 느껴질 수 있습니다. 실제로는 데이터 접근과 도메인 로직 사이의 경계를 어떻게 잡을지에 대한 설계 감각과 연결됩니다.
이 글에서는 아래 내용을 정리합니다.
- repository pattern이 무엇인지
- 왜 데이터 접근을 한 겹 감싸는지
- 어떤 상황에서 유용한지
- 언제는 오히려 과할 수 있는지
핵심은 레포지토리 패턴은 데이터 저장소 세부사항이 비즈니스 로직 곳곳에 새어나가지 않도록 경계를 만들려는 구조라는 점입니다.
레포지토리 패턴이란 무엇인가
레포지토리 패턴은 도메인 로직이 직접 SQL이나 ORM 세부사항을 다루지 않고, 데이터 조회/저장을 역할 단위 인터페이스 뒤로 감추는 방식입니다.
즉:
- 도메인 쪽은 “무엇을 얻고 저장할지”를 말하고
- 레포지토리는 “어떻게 DB에서 가져올지”를 맡습니다
왜 유용할까
데이터 접근 코드가 서비스 로직 곳곳에 흩어지기 시작하면:
- DB 세부사항이 퍼지고
- 테스트가 불편해지고
- 저장소 변경이 어려워집니다
레포지토리 계층은 이런 세부사항을 한곳으로 모으는 데 도움을 줍니다.
어떤 상황에서 잘 맞을까
- 도메인 로직과 데이터 접근을 분리하고 싶을 때
- 테스트 대체 구현이 필요할 때
- 저장소 종류나 구현이 바뀔 가능성이 있을 때
즉, 데이터 접근이 하나의 의미 있는 역할로 보이기 시작할 때 잘 맞습니다.
언제 과할 수 있을까
아주 단순한 CRUD 프로젝트에서 레포지토리 계층을 너무 무겁게 만들면:
- 인터페이스와 클래스만 늘어나고
- 실제 가치보다 복잡도만 커질 수 있습니다
즉, 레포지토리 패턴도 문제와 규모에 맞게 적용하는 것이 중요합니다.
자주 하는 오해
1. DB 코드가 있으면 무조건 레포지토리를 만들어야 한다
작고 단순한 구조에서는 직접성이 더 낫기도 합니다.
2. 레포지토리를 쓰면 DB를 쉽게 바꿀 수 있다
가능성은 높아지지만, 실제 쿼리와 도메인 모델 설계에 따라 쉽지 않을 수도 있습니다.
3. 레포지토리는 단순 SQL 래퍼다
단순 위임만 하면 패턴의 장점이 약해질 수 있습니다. 중요한 것은 경계와 역할입니다.
Simple Example
interface UserRepository {
findById(id: number): { id: number; name: string } | null;
}
class InMemoryUserRepository implements UserRepository {
private users = [{ id: 1, name: 'Ari' }];
findById(id: number): { id: number; name: string } | null {
return this.users.find((user) => user.id === id) ?? null;
}
}
class UserService {
constructor(private userRepository: UserRepository) {}
getUserName(id: number): string {
return this.userRepository.findById(id)?.name ?? 'unknown';
}
}
const service = new UserService(new InMemoryUserRepository());
console.log(service.getUserName(1));
The service depends on the repository contract, not on SQL or ORM details, which makes the data access boundary clearer.
FAQ
Q. 입문자는 어디서 레포지토리를 떠올리면 좋을까
서비스 로직 안에 ORM 호출이 계속 쌓일 때 떠올리기 좋습니다.
Q. 레포지토리와 DAO는 같은가
비슷하게 쓰이는 경우도 있지만, 레포지토리는 도메인 중심 설계 맥락에서 더 많이 언급됩니다.
Q. 테스트에 왜 도움이 되나
데이터 접근을 대체 구현으로 바꾸기 쉬워질 수 있기 때문입니다.
Read Next
- 의존성 경계는 DIP 가이드와 의존성 주입 가이드를 같이 보면 좋습니다.
- 생성 책임은 팩토리 패턴 가이드와도 이어집니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.
먼저 읽어볼 가이드
검색 유입이 많은 핵심 글부터 이어서 보세요.
- 미들웨어 트러블슈팅 가이드: Redis vs RabbitMQ vs Kafka 개발자를 위한 미들웨어 트러블슈팅 허브 글입니다. Redis, RabbitMQ, Kafka 중 어떤 증상부터 먼저 봐야 하는지와 어떤 문제 패턴이 각 시스템에 가까운지 정리합니다.
- Kubernetes CrashLoopBackOff: 먼저 볼 것들 startup failure, probe, config, resource limit 관점에서 CrashLoopBackOff를 어떻게 나눠서 봐야 하는지 정리한 가이드입니다.
- Kafka consumer lag가 계속 늘 때: 트러블슈팅 가이드 Kafka consumer lag가 계속 늘어날 때 무엇부터 봐야 하는지 정리합니다. poll 주기, 처리 속도, rebalance, consumer 설정까지 실전 기준으로 다룹니다.
- Kafka Rebalancing Too Often 가이드 Kafka consumer group에서 rebalance가 너무 자주 일어날 때 membership flapping, poll timing, protocol, assignment churn을 어떤 순서로 봐야 하는지 설명하는 실전 가이드입니다.
- Docker container가 계속 재시작될 때: 먼저 확인할 것들 exit code, command failure, environment mistake, health check 관점에서 Docker restart loop를 푸는 실전 가이드입니다.
심사 대기 중에는 광고 대신 관련 가이드를 먼저 보여줍니다.