R&D
개발현황
쿼리 로직의 일관성 있는 관리와 재사용성 강화를 위한 쿼리 빌더 리팩토링 : 조현진 연구원
- 관리자
- 2025.07.14
쿼리 로직의 일관성 있는 관리와 재사용성 강화를 위한 쿼리 빌더 리팩토링
배경
우리 시스템에서는 다양한 조회 API 핸들러에서 복잡하고 중복된 필터 조건 로직이 2가지 이상 존재했습니다. 이로 인해 다음과 같은 문제가 반복되었습니다:
- 핸들러마다 필터 조건 로직이 다르게 작성됨 -> 유지보수 어려움
- 공통 조건 필터링 로직이 반복됨 → 코드 중복
- 파라미터 조합이 많아질수록 조건 처리 로직이 장황해짐
리팩토링의 목표
- 공통 필터링 로직의 재사용
- 가독성 높은 쿼리 조건 구성 방식 도입
- 쿼리 조립을 함수 중심에서 객체 지향 방식(QueryBuilder) 으로 전환
접근 방법
방법 1 - 단순 조건 추가 함수
- 설명: 개별 파라미터에 대해 조건을 추가하는 단순 유틸 함수
- 장점: 코드가 매우 간단하며 빠르게 적용 가능
- 단점: 파라미터가 많아지면 중복/분산되기 쉬우며, 재사용성과 확장성이 낮음
하지만, 이 방법은 변수 이름이 명확하지 않았고 한 번에 다양한 파라미터를 필터링 할 수 없다는 단점이 존재했습니다. 그래서 고안해낸 방법은
방법 2 - 데코레이터 기반 조건 함수
- 설명: 조건 로직을 공통 유틸 함수 또는 데코레이터로 추출한 방식
- 장점: 조건별 로직이 모듈화되어 재사용성과 유지보수가 좋음
- 단점: 각 조건마다 함수를 분리해야 하므로, 함수가 지나치게 난립할 수 있음
이 방법은 리더님의 코드를 보고 약간의 리팩토링을 거친 코드입니다. 재사용성면에서는 좋을 것 같으나, 과한 느낌이 없지 않아들었습니다. 그래서 방법 3를 고안해냈습니다.
방법 3- 조건 함수 모듈화
- 설명: 자주 쓰이는 필터 조건을 공통 조건 함수로 묶어서 사용하는 방식
- 장점: 공통 로직 분리와 최소한의 추상화로 실용적
- 단점: 다양한 케이스에 유연하게 대응하기에는 한계가 존재
위와 같이 다양한 방법을 실험하며, 각 방식의 재사용성, 확장성, 실용성 측면을 비교해보았습니다:
방법 | 설명 | 장점 | 단점 |
---|---|---|---|
방법 1 | 단순 조건 추가 함수 | 매우 간단, 빠르게 구현 가능 | 재사용성 낮음, 코드 분산 |
방법 2 | 조건 전용 데코레이터 또는 함수 | 높은 모듈화와 재사용성 | 함수 난립, 과한 구조 가능 |
방법 3 | 공통 조건 함수로 분리 | 적절한 추상화, 실용적 구조 | 유연성이 다소 부족할 수 있음 |
최종 선택 - Query Builder 도입
선택 이유
- 필터 조건, 정렬, 조인, 페이징 등 복잡한 SQL 구성을 메서드 체이닝으로 간결하게 표현
filter_if_not_none
,active
,of_factory
등 조건적 필터 처리 메서드를 통해 가독성 향상- 예시:
query = QueryBuilder(session, Model).filter_if_not_none(Model.name, name).active().get_all()
- 예시:
- 공통 로직 추상화를 통해 유지보수성 향상
- 단일 쿼리 빌더 인스턴스로 다양한 상황 대응 가능
핸들러별 커스텀 로직을 제거하면서도 공통 기능을 유연하게 조립할 수 있는 구조로 리팩토링 목표에 부합했습니다.
객체 지향 방식(QueryBuilder)
파이썬에서 객체 지향 방식의 QueryBuilder
란 SQL 쿼리를 객체 지향적으로 구성하여 동적 쿼리 작성 및 유지보수를 용이하게 해주는 설계 패턴입니다.
QueryBuilder
는 SQLAlchemy ORM 위에 구축된 도메인 특화 언어(DSL)이자, 데이터 접근 계층의 재사용성과 표현력을 높이기 위해 반복되는 조건 처리 로직을 하나의 클래스, 즉 객체로 추상화하여 다음과 같은 객제 지향의 원칙을 실현합니다.
설계 철학 및 아키텍처 원칙
1. SRP (단일 책임 원칙)
QueryBuilder
는 오직 쿼리 조립만을 책임지고, 실행이나 결과 해석은 호출자에게 위임합니다.- 이를 통해 비즈니스 로직과 데이터 접근 로직을 분리하여 유지보수가 쉬워집니다.
2. OCP (개방-폐쇄 원칙)
.filter_if_not_none()
와 같은 메서드를 통해 조건을 필요에 따라 유연하게 추가할 수 있습니다.- 특정 필터 조건은 공통 유틸 메서드로 캡슐화되어 있어, 중복 없이 재사용 가능합니다.
3. LSP (리스코프 치환 원칙)
- 기본 모델 외에도 alias나 join된 모델 등으로 교체가 가능합니다.(모델 대체성 보장)
4. DSL (Domain-Specific Language) 패턴
- 쿼리 구성 문법을 직관적이고 선언적인 체이닝 방식으로 구현할 수 있습니다.
- SQL이라는 저수준 언어를 도메인 친화적 API로 추상화할 수 있습니다.
5. ORM 계층에서의 위치
QueryBuilder
는 SQLAlchemy ORM의 상위 추상화 계층으로, Repository나 Service Layer와 ORM 간의 인터페이스 계층 역할을 수행합니다.- 이는 CQRS 패턴에서 Query Layer의 역할에 대응되며, 비즈니스 로직과 데이터 접근의 명확한 분리를 가능하게 합니다.
CQRS 패턴이란?
- 명령(Command)과 조회(Query)를 분리하여 각자의 책임에 집중하게 하는 설계 원칙입니다.
- Command는 상태를 변경하는 작업(예: 등록, 수정, 삭제)
- Query는 데이터를 조회만 수행하는 작업(예: 리스트, 상세 보기 등)
리팩토링 결과
- 중복된 조건 필터링 코드 제거 → 핸들러 및 서비스 코드 30~40% 간결화
- 조건 조립 코드의 가독성/일관성 향상
- 쿼리 조건별 단위 테스트가 가능해지면서 테스트 커버리지 증가
- 팀원 간 코드 리뷰 효율성 상승 (조건 흐름이 명확)
향후 계획 및 확장 방향
- 조회 쿼리 외에
get_column_if_not_value
,update_execute()
등의 동적 갱신 기능 추가할 예정입니다. - 도메인 별 QueryBuilder 분리:
- 예: Factory Service, Inspect Schedule Service 등 각 도메인 서비스에 특화된 쿼리 조합을 모듈화하여, DDD 관점의 Repository Layer와 유사한 구조로 확장 가능
QueryBuilder는 도메인별 조회 책임을 유틸리티로 분리하여, 향후 CQRS의 Query Layer나 Read Model 구성에도 기초로 활용될 수 있습니다.
마무리
이번 리팩토링은 단순한 유틸성 도구 도입을 넘어서, 코드의 일관성, 유지보수성, 아키텍처적 방향성을 함께 정비하는 계기가 되었습니다. 향후에도 QueryBuilder의 설계를 지속적으로 개선하고, 다양한 쿼리 시나리오에 적용해 나갈 계획입니다.