API 설계 철학: REST에서 GraphQL까지, 현업에서 배운 7가지 원칙
예전에 스타트업 다닐 때 API가 진짜 개판이었습니다. "이 API 왜 응답이 이렇게 커?" "하위 호환성 깨졌잖아!" 이런 대화가 매일 오갔어요. 팀장이 "API 설계 원칙 한번 정리해봐" 하더라고요. 그래서 시작했는데 생각보다 deep한 세계였어요. 오늘은 그때 배운거랑 이후에 느낀 점들 좀 풀어볼게요.
1. 이름 짓기
제가 들어갔을때 API가 진짜 개판이었어요. 어떤건 /getUser고 어떤건 /users고, 심지어 /getUserList 이런것도 있었습니다. HTTP 메서드도 안지켜서 삭제하는데 GET 쓰고, 조회하는데 POST 쓰고 난리였어요. 다 명사 복수형으로 통일하고 GET/POST/PUT/PATCH/DELETE로 구분하는걸로 고쳤습니다. GET /users는 목록, POST /users는 생성, GET /users/{id}는 상세. 이렇게만 해도 반은 먹고 들어가요. 제 기준은 "처음보는 개발자가 3초안에 이해할수 있는가"입니다.
2. 버전 관리
이게 제가 가장 쓰라린 경험을 한 부분입니다. 초기에는 "일단 만들자" 마인드로 API를 막 수정했어요. 오늘은 필드 추가하고 내일은 필드 빼고... 근데 앱 사용자들은 업데이트를 안하잖아요? 구버전 앱에서 API 호출하면 크래시 나고 고객 불만 폭주했습니다. 몇번 반복되니까 사용자 신뢰가 바닥을 쳤어요. 그다음부터 URL에 버전 박기로 했습니다. /api/v1/users 이런식으로요. Major 버전은 하위호환성 깨질때만 올려야 하는데, 어떤 팀은 "필드 하나 추가했으니 v2로 올리자" 그러더라고요. 제 경험상 Major는 1년에 한두번이면 충분합니다. 그보다 자주 올라가면 아무도 안씁니다.
3. 에러 응답
API 쓰다보면 에러날때가 있잖아요? 근데 어떤 API는 {"message":"error"}만 떨궈주고, 어떤건 {"code":500,"message":"Internal Server Error"} 이렇게 구조화된거 주고... 심지어 같은 API인데 상황에 따라 응답 형식이 다를때도 있었습니다. 클라이언트 개발자들이 if-else 지옥에 빠지더라고요. RFC 7807 규격 도입해서 type, title, status, detail로 통일했더니 if(error.status===422) 한줄로 모든 에러처리가 가능해졌습니다. 작은 변화였는데 생산성 차이가 엄청났어요. 개발자 경험을 고려한 설계가 진짜 중요하다는걸 이때 느꼈습니다.
4. 페이지네이션
처음엔 "게시글 목록 그냥 다 내려주면 되지" 했다가 서버 터지는 사고 났습니다. 누군가 전체 조회 API를 호출했는데 메모리가 고갈되면서 서비스가 통으로 죽었어요. 그후로 모든 목록 조회 API는 커서 기반 페이지네이션으로 바꿨어요. offset 기반(?page=1)은 페이지 깊어질수록 이전 페이지를 다 스캔해야 해서 느려지는데, 커서 기반(?cursor=...)은 마지막 ID 기준으로만 스캔하니까 일정해요. 필터링도 화이트리스트로 관리하는게 안전합니다. 실수로 민감한 내부 필드가 필터링으로 노출된적이 있어서요. 다행히 QA에서 잡았지만 그뒤로는 파라미터 하나하나 다 정의해놓고 관리합니다.
5. 문서화
"코드가 곧 문서"라는 말 진짜 믿었던 시절이 있었습니다. 근데 외부 파트너사가 우리 API 연동하려는데 문서가 없어서 소스코드를 직접 까는 상황 발생했어요. 그때 파트너사 개발자 표정이 아직도 기억납니다. "이게 뭐하는 회사야?" 이런 표정이었어요. 그후로 OpenAPI Specification 적극 도입했습니다. 코드에 어노테이션만 추가하면 문서 자동생성되거든요. 근데 자동생성만 믿으면 안되더라고요. 자동 문서는 너무 장황하거나 반대로 너무 부실해서 실제 도움이 안될때가 많아요. 그래서 사람이 읽는 가이드 문서를 별도로 만듭니다. "이 API를 언제 쓰는게 좋은지"랑 "자주하는 실수" 위주로 적어요. API 문서는 레퍼런스가 아니라 커뮤니케이션 도구라는게 제 생각입니다.
6. GraphQL vs gRPC
둘다 써봤는데 장단점이 확실하더라고요. GraphQL은 클라이언트가 원하는 데이터만 요청할수 있다는게 진짜 좋았습니다. 모바일 앱에 도입했더니 네트워크 트래픽이 40%나 줄었어요. 근데 N+1 문제는 진짜 골때립니다. 복잡한 중첩 쿼리 하나 던졌는데 DB에 쿼리가 수백개 나가는거 보고 깜짝 놀랐어요. 그래서 GraphQL 도입할땐 쿼리 복잡도 제한이랑 깊이 제한을 꼭 걸어야 합니다. gRPC는 반대로 마이크로서비스간 통신에서 진가를 발휘했어요. REST보다 처리량이 3배 높게 나왔습니다. 근데 브라우저에서 네이티브 지원이 안되니까 웹프론트랑 연동하려면 추가작업이 필요하고, 바이너리 포맷이라 디버깅이 어렵다는 단점도 있고요. 제 결론은 외부 공개 API는 REST, 모바일은 GraphQL, 내부 MSA는 gRPC입니다. "요즘 GraphQL 대세라서" 같은 이유로 선택했다가 나중에 후회할수 있어요.
7. 보안
인증(Authentication)이랑 인가(Authorization)는 반드시 분리해야 합니다. "누군지 확인"하는거랑 "뭘 할수 있는지 결정"하는거는 완전히 다른 문제예요. JWT 쓰면 토큰에 권한정보 넣어서 편하게 쓰긴 하는데, JWT 탈취당하면 답이 없으니까 민감한 작업은 매번 백엔드에서 다시 확인해야 합니다. 레이트 리밋도 선택이 아니라 필수고요. 토큰 버킷 알고리즘 쓰면 단기 트래픽 버스트는 허용하면서 장기 평균은 제한할수 있어서 좋습니다. 근데 이거 설정할때도 너무 빡빡하면 정상 사용자 차단되고 너무 느슨하면 의미없어서 밸런스 맞추기가 젤 어렵습니다.
API 설계에 정답은 없습니다. REST가 최고라고 우기는 사람도 있고, GraphQL이 답이라고 말하는 사람도 있고, gRPC가 미래라고 주장하는 사람도 있어요. 근데 중요한건 유행을 쫓는게 아니라 내 도메인에 맞는걸 고르는거라고 생각합니다. 제가 가장 크게 배운 교훈은 "일관성"이었어요. 완벽하지 않아도 됩니다. 일관성만 있으면 클라이언트는 어떻게든 적응해요. 반대로 아무리 좋은 설계라도 일관성 없으면 혼란만 가중되고요. API는 기계가 기계랑 통신하는 프로토콜인 동시에 사람과 사람 사이의 소통 도구라는걸 잊지 않았으면 좋겠습니다. 자주 바뀌는 API는 아무도 안믿습니다. 첫인상이 평생가는게 API예요.