POOOLING FOREST
복잡한 API 응답 조립하기: 지옥의 글루 코드에서 탈출한 후기 - 복잡한 API 응답 조립 시 발생하는 '글루 코드' 문제를 pydantic-resolve를 통해 선언적으로
Engineering & Tech

복잡한 API 응답 조립하기: 지옥의 글루 코드에서 탈출한 후기

복잡한 API 응답 조립 시 발생하는 '글루 코드' 문제를 pydantic-resolve를 통해 선언적으로 해결하고 N+1 문제까지 방지한 실무 경험을 공유합니다.

김영태

테크리드

안녕하세요. 풀링포레스트 테크리드 김영태입니다.

백엔드 개발자로 일하다 보면 가장 피로도가 높은 순간이 언제일까요? 저는 단언컨대 '프론트엔드 요구사항에 맞춰 데이터를 덕지덕지 붙이는 순간'이라고 생각합니다. "팀 정보 내려줄 때, 그 팀에 속한 유저 리스트랑 현재 진행 중인 스프린트 정보도 같이 주세요. 아, 스프린트는 ID랑 이름만 있으면 돼요." 이런 요청, 다들 받아보셨을 겁니다.

과거의 저는 이런 요청이 오면 한숨부터 나왔습니다. 서비스 초기에는 그냥 for 문 돌면서 데이터를 가져와서 붙였습니다. 일명 '글루 코드(Glue Code)'죠. 하지만 서비스가 커지고 데이터 관계가 복잡해지니, 컨트롤러 코드는 순식간에 스파게티가 되더군요. 데이터를 가져오는 로직과 조립하는 로직이 뒤섞여 유지보수가 불가능한 지경에 이르렀습니다. 뼈저리게 후회했죠.

"차라리 GraphQL을 도입할까?" 하는 유혹도 있었습니다. 하지만 이미 REST API 기반으로 잘 돌아가는 시스템을 뒤집는 건 배보다 배꼽이 더 큰 작업이었습니다. 막막했습니다. 우리에게 필요한 건 거창한 쿼리 언어가 아니라, 파이썬 코드 안에서 우아하게 데이터를 조립할 방법이었으니까요.

그러다 발견한 것이 바로 pydantic-resolve입니다. 솔직히 처음엔 반신반의했습니다. Pydantic 위에서 돌아가는 작은 라이브러리가 얼마나 도움이 될까 싶었죠. 하지만 실무에 적용해보고 나서 무릎을 쳤습니다. 이 도구는 우리가 겪던 '명령형 코드의 지옥'을 '선언적 우아함'으로 바꿔주었습니다.

가장 큰 변화는 데이터를 '어떻게(How)' 가져올지 고민하는 대신, '무엇(What)'이 필요한지만 정의하면 된다는 점입니다.

예를 들어보겠습니다. 팀(Team) 정보에 스프린트(Sprint) 정보를 붙여야 하는 상황입니다. 예전 같으면 팀 리스트를 조회한 뒤, 루프를 돌며 각 팀의 스프린트를 조회하는 코드를 짰을 겁니다. 하지만 pydantic-resolve를 쓰면 모델 안에 로직을 숨길 수 있습니다.

class Team(DefineSubset):
    __subset__ = (team_schema.Team, ('id', 'name'))

    sprints: list[Sprint] = []

    def resolve_sprints(self, loader=Loader(sprint_loader.team_to_sprint_loader)):
        return loader.load(self.id)

보시다시피 resolve_sprints라는 메서드 하나만 정의하면 끝입니다. 이렇게 하면 컨트롤러에서는 Resolver().resolve(teams) 한 줄만 호출하면 됩니다. 복잡한 조립 로직이 모델 내부로 캡슐화되니 비즈니스 로직이 훨씬 깔끔해졌습니다.

여기서 많은 분들이 걱정하실 포인트가 하나 있을 겁니다. "저렇게 루프 돌면서 리졸버 호출하면 N+1 문제 터지는 거 아닌가요?" 저도 그게 제일 걱정이었습니다. 하지만 다행히도 이 라이브러리는 DataLoader 패턴을 지원합니다. 내부적으로 ID들을 모았다가 배치 쿼리로 한 번에 조회하기 때문에 성능 이슈도 잡을 수 있었습니다.

최근 릴리스된 v2 버전부터는 ERD(Entity Relationship Diagram)까지 지원하더군요. 애플리케이션 레벨에서 엔티티 관계를 코드로 선언하고 관리할 수 있어서, 마치 문서가 살아있는 코드처럼 동작합니다. FastAPI나 Django-ninja 같은 최신 프레임워크와도 찰떡같이 붙습니다. 특히 fastapi-voyager를 함께 사용하면 스키마 의존성을 시각화해서 볼 수도 있어, 복잡한 모델 관계를 파악해야 하는 신규 입사자들에게 설명하기가 한결 수월해졌습니다.

물론 모든 API에 이걸 적용할 필요는 없습니다. 단순한 CRUD라면 오버엔지니어링일 수 있습니다. 하지만 대시보드처럼 여러 도메인의 데이터를 한 번에 보여줘야 하는 UI 통합 시나리오에서는 이만한 해결책이 없다고 느꼈습니다. GraphQL의 유연함은 탐나지만 도입 비용이 부담스러운 팀이라면, pydantic-resolve는 훌륭한 대안이 될 것입니다.

오늘도 복잡한 JSON 응답을 만드느라 고통받고 계신다면, 잠시 멈추고 선언적인 데이터 구성을 고민해보시길 바랍니다. 코드가 깔끔해지면, 퇴근 시간도 빨라집니다.

지금 읽으신 내용, 귀사에 적용해보고 싶으신가요?

상황과 목표를 알려주시면 가능한 옵션과 현실적인 도입 경로를 제안해드립니다.