도메인 로직과 데이터 접근 로직을 분리하기 위해 자주 사용되는 소프트웨어 디자인 패턴.
주로 DDD(Domain-Driven Design)에서 강조되지만, MVC나 계층형 아키텍처에서도 널리 사용된다.
저장소 패턴은 GoF 디자인 패턴 23개에 포함되어 있지 않다. 하지만 실무 백엔드에서 정말 자주 쓰이는 실용적인 설계 패턴이라고 생각한다.
저장소(Repository)는 도메인 객체의 컬렉션처럼 작동하는 추상 계층으로, 데이터베이스, 외부 API, 캐시 등 다양한 저장소에서 데이터를 가져오는 복잡한 로직을 캡슐화하여 일관된 인터페이스로 제공하는 역할을 한다.
즉, Service -> Repository -> Data Source(DB, API) 구조로, 비즈니스 로직은 Repository에만 의존하며, 구체적인 구현은 숨겨진다.
일반적인 Service-Controller 구조에서는 비즈니스 로직과 DB 접근 로직이 강하게 결합되어 있다. 이는 장기적으로 보았을 때 코드의 복잡도를 높이고 가독성을 떨어뜨리며, 유지보수에 어려움을 겪을 수 있다.
저장소 패턴은 비즈니스 로직에서 데이터 접근 로직을 분리하여, DB나 외부 API 구조가 바뀌어도 Repository만 수정하면 되기 때문에 유지보수에 용이하며, 실제 DB 없이 Repository를 mocking하여 테스트가 가능하다. 또한 비즈니스 로직에서 복잡한 쿼리나 조건이 Repository로 숨겨지기 때문에 깔끔한 인터페이스를 제공할 수 있다.
라우터에서 요청 파라미터를 받아서, 비즈니스 로직으로 넘기게 된다.
# ❌ 비즈니스 로직과 데이터 접근 로직이 강하게 결합된 형태
class AuthService:
def login(self, request, db: Session) -> tuple[str, str]:
# DB 접근 로직 (User 조회)
user = db.query(User).filter(User.email == request.email).first()
# 비즈니스 로직 (비밀번호 검증, 토큰 생성)
...
AuthService
에서 처리 후 결과값을 router
에게 다시 넘겨주게 된다.
위 과정에서 비즈니스 로직에 DB 접근 로직이 포함되어 있다. 이를 Repository로 분리해주는 것이다.
class UserRepository:
def __init__(self, db: Session):
self.db = db
def get_by_email(self, email: str) -> Optional[User]:
# ✅ DB 쿼리 세부 구현은 여기서만 관리
return self.db.query(User).filter(User.email == email).first()
class AuthService:
def __init__(self, user_repo: UserRepository):
self.user_repo = user_repo
def login(self, request) -> tuple[str, str]:
user = self.user_repo.get_by_email(request.email)
if not user or not auth.verify_password(request.password, user.password):
raise HTTPException(...)
# ✅ 비즈니스 로직만 남음
...
이후 router
에서 Repository 클래스와 AuthService
클래스를 생성해 넣어준다.
위와 같은 방식으로 구현하게 되면 각 계층이 단일 책임 원칙(SRP)을 따르기 때문에 테스트, 유지보수, 확장이 쉬워진다.
규모가 작은 프로젝트의 경우 오버엔지니어링일 수 있으나, 규모가 커질 수 있는 프로젝트라면 나중을 대비해 저장소 패턴을 활용하여 API 로직을 설계하는 것도 좋은 방안이 될 수 있을 것 같다.
Reference
저장소 패턴(Repository Pattern) 도입기, by daco2020
[디자인패턴] Repository Pattern 레포지토리 패턴, by ttoogi