[10분 테코톡] 미미의 좋은 API 설계하기
ORIGINAL SOURCE ↗YouTube에서 보기 | 영상 길이: 9분 24초
요약
주제 선정 배경 — “한 번에 보내줘”
우아한테크코스 백엔드 7기 미미의 발표. 목차는 주제 선정 이유 → API 개념 → GitHub API 설계 원칙 → 성능 최적화와 API 분리의 관계 → 마무리 순서로 진행된다.
발표자는 팀 프로젝트를 하던 중 프론트엔드 개발자 친구로부터 이런 말을 들었다:
“여러 번 요청을 보내면 네트워크 비용이 많이 드니까 그냥 한 번에 보내줘.”
이 말을 들었을 때 “헉, 그래도 되나? 괜찮을 것 같기도 하고, 뭔가 안 괜찮을 것 같은데 이유를 모르겠네?”라는 생각이 들었고, 스스로도 어떻게 설계된 API를 “좋은 API”라 할 수 있는지 기준이 없었다. 그래서 이 의문을 직접 공부하고 결론을 내려 공유하기 위해 이 주제를 선택했다.
API와 API 설계의 개념
[01:10] API는 애플리케이션 프로그래밍 인터페이스(Application Programming Interface)의 약자다. 여기서 핵심 단어는 인터페이스다. 인터페이스란 시스템 간 정보를 교환할 수 있도록 공유되는 경계면이다. 그렇다면 API는 어디와 어디 사이의 경계면인가? 바로 API를 요청하는 클라이언트와 응답하는 서버 사이의 경계면이다.
우리는 코드 레벨에서 이미 다른 요소 간의 경계 설정을 해왔다. 단일 책임, 모듈화, 관심사의 분리 같은 방법으로. API도 마찬가지로 클라이언트와 서버라는 두 요소 사이의 정보 전달이기 때문에, 설계 단계에서 적절한 분리 지점을 고민하는 것이 중요하다. 잘못된 경계를 설정하면 사용하기 어려운 API가 된다.
GitHub PR API 사례로 보는 설계 원칙
[02:20] 이제 Pull Request API를 직접 개발한다고 가정하고 들어간다.
PR을 생성하려면 다음 정보가 필요하다:
- 제목(title)
- 본문(content)
- 작업 브랜치(head)
- 병합 대상 브랜치(base)
- Assignee 배정
- Label 지정
행위 중심 API — 처음 설계안
이 행위를 그대로 API로 옮기면 이런 형태가 나온다:
POST /pulls
Body: { title, content, head, base, assignees, labels }
이 API는 행위를 그대로 옮긴 형태다. 클라이언트 입장에서는 모든 요청을 한 번에 처리하므로 편리하다는 장점이 있다. 하지만 요구사항이 추가되거나 수정될 때 유연성이 급격히 저하된다.
문제 1 — Draft PR 기능 추가 [03:13]
GitHub에는 PR 생성 후 “Convert to draft” 버튼이 있다. 이를 클릭하면 PR이 Draft 상태로 전환되고, 머지 버튼이 막힌다. Draft PR은 실제로 머지할 의도는 없고 코드 검토만 받고 싶을 때 사용하는 PR이다. “Ready for review”를 눌러야 다시 원래 PR 상태로 돌아간다.
기존 API에서 Draft PR 기능을 추가하려면 API에 draft 필드를 추가해야 한다. 그런데 만약 Draft 로직 개발 중 문제가 생겨 API가 정상 동작하지 않으면 어떻게 될까? PR 생성만 실패하는 게 아니다. Assignee 배정도 실패하고, Label 설정도 실패한다. 모든 것이 하나로 묶여 있기 때문에 Draft 로직 하나의 오류가 전체를 무너뜨린다.
문제 2 — 요구사항 변경 [04:18]
이번엔 “PR 생성 이후에도 Assignee나 Label을 설정할 수 있도록” 요구사항이 변경된다면 어떻게 될까? 기존의 통합 API는 PR 생성과 Assignee, Label이 한 묶음이라 재활용이 불가능하다. PR 생성 API, Assignee 생성 API, Label 생성 API를 전부 새로 만들어야 한다.
해결책 — 리소스 중심 API [04:39]
처음부터 API를 조합 가능한 작은 단위로 쪼갰다면 다양한 워크플로우에 유연하게 대처할 수 있다. “ABC 값을 설정한다”는 행위가 아니라 “ABC라는 대상 자체”에 집중해서 API를 설계하면, 상황에 따라 API를 조합할 수 있다. 이 조합 가능한 작은 단위, 즉 대상을 리소스라고 부른다.
PR의 주속성과 부속성 분리 [05:11]:
- 주속성 (PR 생성 상태에 관여하는 것): title, content, head, base → PR이 생성되려면 반드시 있어야 함
- 부속성 (생성 시점이 유연하고, 실패해도 PR은 생성 가능한 것): Assignee, Label
이를 토대로 리소스를 분리한 API 스펙:
POST /pulls → { title, content, head, base }
POST /pulls/{id}/assignees → Assignee 별도 설정
POST /pulls/{id}/labels → Label 별도 설정
[06:00] 실제로 GitHub도 Pull Request 기능을 제공할 때 이와 유사한 방식으로 API를 분리하고 조합해서 다양한 워크플로우에 유연하게 대처하고 있다.
API를 잘 설계하는 방법 정리
[06:12] 사례를 통해 도출한 핵심 원칙:
- 적절한 분리 지점을 찾아라 — 행위에 집중하기보다 특정 대상, 즉 리소스에 집중하면 경계를 보다 쉽게 찾을 수 있다.
- API를 유연한 구성 요소로 만들어라 — API가 사용자가 정해진 순서대로만 써야 하는 도구가 아니라, 사용자가 원하는 시나리오에 맞게 조립할 수 있는 구성 요소가 되어야 한다.
성능 최적화와 API 분리의 양립
[06:47] 여기서 자연스럽게 드는 걱정이 있다. “API를 분리하면 요청이 많아지고, 그만큼 네트워크와 서버의 부하가 커지지 않을까?” 발표자도 이 부분이 걱정됐다고 한다.
그런데 GitHub의 결론은 명확하다. “여러 번 API를 호출하는 건 불가피하고, 대신 빠르게 요청을 처리하자.”
GitHub이 실제로 사용하는 성능 최적화 방법들:
- 리소스 분리 자체의 효과: 리소스를 분리하면 JSON Payload가 수백 바이트 이하로 떨어진다. 요청 자체가 가벼워지기 때문에 네트워크 부담이 줄고, DB I/O도 감소한다.
- 비동기 처리: 부담이 큰 쓰기 요청은 비동기로 처리해 서버와 DB의 부하를 감소시킨다.
- GraphQL 활용: 데이터를 조회할 때는 GraphQL을 적절히 활용해 필요한 경우 한 번에 데이터를 가져와 네트워크 통신을 줄인다.
[07:51] GitHub 엔지니어링 블로그 수치:
- REST API의 대부분은 1ms 이하의 CPU 시간 사용
- 초당 수십만 요청을 처리하면서도 DB 쓰기 지연의 95%가 15ms 이하
즉, API를 잘 분리하고 각 요청을 가볍게 만들면 성능과 유연성은 충분히 양립할 수 있다.
결론 — 좋은 API란?
[08:36] 발표자가 생각하는 좋은 API의 정의:
“조합 가능성을 고려한 API”
리소스를 중심으로 나눠서 필요에 따라 조립하고 조합할 수 있어야 한다. 단, “조합 가능성을 적용한” API가 아니라 “조합 가능성을 고려한” API라고 표현한 이유가 있다. 가장 중요한 건 API를 만들고 사용하는 이해관계자들의 편의성이기 때문이다. 상황에 따라 통합 API가 더 나을 수도 있다. 다만 설계 과정에서 “리소스 분리를 고려해봤는가?”를 한 번 떠올려 보는 것이 핵심이다.
[09:12] 마지막으로 발표자는 이런 말로 마무리한다:
“우리가 코드를 짤 때는 설계에 대한 고민을 많이 하는데, API는 훨씬 더 변경이 까다로운데도 불구하고 설계해야 될 대상이라는 사실을 놓치고 있는 건 아닌지 생각해보면 좋을 것 같습니다.”
키워드
API-설계, REST-API, 리소스-분리, GitHub, 백엔드, 성능-최적화, GraphQL, 조합-가능성, Draft-PR, 테코톡