💡 Key Takeaways
- Principle 1: Design Your API Like a Product, Not a Database Wrapper
- Principle 2: Embrace HTTP Status Codes Properly (But Don't Overthink Them)
- Principle 3: Version Your API From Day One (And Do It Right)
- Principle 4: Design Intuitive Resource Naming and URL Structures
나는 CEO에게 왜 우리의 모바일 앱이 사용자의 데이터 요금을 숲처럼 태우고 있는지 설명해야 했던 날이 아직도 기억난다. 2016년이었고, 나는 핀테크 스타트업에서 수석 API 아키텍트로 3년째 일하고 있었다. 우리의 REST API는 앱이 계좌 잔액을 확인할 때마다 프로필 사진이 base64로 인코딩된 전체 사용자 객체를 반환하고 있었다. 우리는 고객을 잃고 있었고, 내가 디자인한 API가 우리를 죽이고 있었다.
💡 주요 요점
- 원칙 1: 제품처럼 API 설계하기, 데이터베이스 래퍼처럼 설계하지 않기
- 원칙 2: HTTP 상태 코드를 적절히 활용하기 (하지만 너무 깊게 생각하지 않기)
- 원칙 3: 첫 날부터 API 버전 관리하기 (그리고 제대로 하기)
- 원칙 4: 직관적인 리소스 명명 및 URL 구조 설계하기
그 고통스러운 교훈은 나에게 중요한 것을 가르쳤다: REST API 설계는 단순히 동작하게 만드는 것이 아니다. 그것은 현실적인 조건에서 잘 작동하도록 만드는 것이며, 교과서에서는 절대 언급하지 않는 스케일에 대해 강조해야 한다. 지난 12년 동안 10명 규모의 스타트업에서부터 포춘 500대 기업에 이르기까지 API를 구축하면서, 나는 같은 실수를 무수히 반복하는 것을 보았고, 대부분은 나 자신이 저질렀다.
오늘은 내가 API 설계를 어떻게 변화시켰는지 10가지 원칙을 공유할 것이다. 이것들은 학술 논문의 이론적인 개념이 아니다. 그것들은 하루에 수백만 건의 요청을 처리하는 프로덕션 환경에서 검증된 지침들이다. 당신이 첫 번째 API를 구축하거나 열 번째 API를 리팩토링하든, 이 원칙들은 개발자들이 실제로 사용하고 싶어하는 인터페이스를 만드는 데 도움을 줄 것이다.
원칙 1: 제품처럼 API 설계하기, 데이터베이스 래퍼처럼 설계하지 않기
내가 REST API 설계에서 보는 가장 큰 실수는 API를 데이터베이스 테이블 위에 얇은 레이어로 취급하는 것이다. 나는 엔드포인트가 데이터베이스 스키마와 일대일로 매핑되는 수백 개의 API를 검토했으며, 내부 구현 세부사항이 드러나지 말아야 할 때도 있었다. 이러한 접근 방식은 데이터 모델이 진화할 때마다 깨지는 취약한 인터페이스를 만든다.
내가 현재 회사에 플랫폼 엔지니어링 부사장으로 합류했을 때, 우리의 API에는 PostgreSQL 스키마를 직접 반영한 47개의 엔드포인트가 있었다. 한 개의 열 이름을 변경하려면 23개 서로 다른 클라이언트 애플리케이션의 업데이트를 조율해야 했다. 기술적 부채는 우리의 혁신 능력을 질식시키고 있었다.
대신, API를 자체 생명 주기, 버전 관리 전략 및 사용자 경험 고려 사항을 가진 제품으로 생각하라. API 소비자는 데이터베이스 정규화 전략이나 내부 마이크로서비스 아키텍처에 신경 쓰지 않는다. 그들은 특정 작업을 효율적으로 수행하는 것에 대해 관심이 있다.
예를 들어, /users, /user_profiles, /user_preferences 및 /user_settings에 대해 별도의 엔드포인트를 노출하는 대신 API 소비자가 실제로 필요한 것이 무엇인지 고려하라. 대부분의 경우 그들은 세심하게 구성된 리소스를 반환하는 응집력 있는 /users/{id} 엔드포인트를 원한다. ?fields=profile,preferences와 같은 쿼리 매개변수를 사용하여 소비자가 필요한 것을 정확히 요청하도록 하라.
나는 의료 SaaS 회사에서 이 접근 방식으로 89개의 엔드포인트에서 34개로 API 표면적을 줄이면서 실제로 기능성을 증가시켰다. 여러 요청을 통해 기본 정보를 조합해야 하는 대화를 없애면서 응답 시간이 43% 빠르게 줄어들었다. 더 중요한 것은 API 문서가 이해할 수 있는 형태가 되었고, 개발자 온보딩 시간이 2주에서 3일로 단축되었다.
핵심은 API의 도메인 모델을 이해하는 것이다. 이는 영속성 모델과는 크게 다를 수 있다. 내부 프론트엔드 팀이든 외부 파트너이든 API 소비자와 시간을 보내고 그들의 작업 흐름을 이해하라. 리소스를 데이터베이스 테이블이 아니라 그들의 정신 모델 주변에 설계하라.
원칙 2: HTTP 상태 코드를 적절히 활용하기 (하지만 너무 깊게 생각하지 않기)
HTTP 상태 코드는 API가 클라이언트와 소통하는 첫 번째 방법이며, 나는 매번 잘못 사용되거나 완전히 무시되는 것을 본다. 나는 한 번 모든 응답에 대해 200 OK를 반환하고, 오류 포함하여 실제 상태를 JSON 필드에 숨기는 API를 감사한 적이 있다. 개발자들은 "항상 성공한다"는 생각에 도움이 되고 있다고 생각했지만, 사실 모든 HTTP 클라이언트 라이브러리의 오류 처리를 깨뜨리고 있었다.
모든 63개의 HTTP 상태 코드를 암기할 필요는 없지만, 핵심 코드를 올바르게 사용해야 한다. 12년의 프로덕션 경험을 바탕으로 한 나의 실용적인 분류는 다음과 같다:
- 200 OK: 내용을 반환하는 성공적인 GET, PUT 또는 PATCH
- 201 Created: 리소스를 생성하는 성공적인 POST (Location 헤더 포함)
- 204 No Content: 아무것도 반환하지 않는 성공적인 DELETE 또는 업데이트
- 400 Bad Request: 클라이언트가 유효하지 않은 데이터를 보냈음 (구체적인 유효성 검사 오류 포함)
- 401 Unauthorized: 인증 필요 또는 실패
- 403 Forbidden: 인증됨 그러나 권한이 없음
- 404 Not Found: 리소스가 존재하지 않음
- 409 Conflict: 요청이 현재 상태와 충돌함 (중복 생성, 버전 불일치)
- 422 Unprocessable Entity: 문법적으로 올바르지만 의미적으로 잘못됨
- 429 Too Many Requests: 속도 제한 초과 (Retry-After 헤더 포함)
- 500 Internal Server Error: 귀하의 쪽에서 무언가가 고장남
- 503 Service Unavailable: 임시 중단 또는 유지보수
401과 403의 구분은 많은 개발자를 혼란스럽게 한다. 401은 "당신이 누구인지 모르겠다"는 것이고 403은 "당신이 누구인지는 알지만 그건 할 수 없다"는 의미로 생각하라. 이는 클라이언트에게 재인증이 도움이 될 수 있는지를 알려준다.
유사하게, 400과 422의 차별점은 미세하지만 유용하다. 형식이 잘못된 JSON이나 필수 필드 누락과 같은 기본 파싱을 실패하는 경우에는 400을 사용하고, 계좌에 존재하는 돈 이상을 이체하려고 하면 422를 사용하라. 이 구분은 클라이언트가 오류를 적절히 분류하는 데 도움을 준다.
내 이전 회사에서는 적절한 상태 코드 사용을 구현하고, 첫 분기에 API 오류 관련 지원 티켓이 67% 감소한 것을 보았다. 개발자들은 마침내 성공이나 실패를 결정하기 위해 응답 본문을 파싱하는 대신 표준 HTTP 의미에 의존할 수 있었다.
원칙 3: 첫 날부터 API 버전 관리하기 (그리고 제대로 하기)
나는 이 교훈을 어렵게 배웠다. 2014년에 "우리는 영원히 하위 호환성을 유지할 것이다"라는 이유로 버전 관리 없이 API를 시작했다. 6개월 후, 중요한 비즈니스 요구를 지원하기 위해 파괴적인 변경이 필요했다. 우리는 1,200명의 활성 API 소비자가 있었지만, 이를 점진적으로 마이그레이션할 방법이 없었다. 결과적으로 강제 업그레이드가 발생했고, 이는 모두를 고장내는 결과를 초래했다.