홈으로 돌아가기

RESTful API 설계 모범 사례 가이드

이 포괄적인 가이드는 장기적인 유지 관리성과 개발자 만족을 보장하는 모범 사례로 RESTful API를 설계하는 방법을 알려줍니다. 리소스 명명, HTTP 메서드 의미론, 버전 관리 전략, 보안, 성능 최적화 및 테스트를 다루며 프로덕션 등급 API 구축을 위한 실용적인 프레임워크를 제공합니다.

RESTful API 설계: 개발자를 위한 모범 사례
Advertisement 728x90

RESTful API 설계: 지속적인 품질을 위한 모범 사례

현대 디지털 생태계에서 API는 종종 제품의 주요 인터페이스 역할을 하지만, 많은 개발팀이 API 설계를 부차적인 것으로 간주하여 취약한 통합과 비용이 많이 드는 리팩토링으로 이어집니다. 잘 설계된 API와 그렇지 않은 API의 차이는 단순한 미적 차이가 아니라 개발자 생산성, 시스템 유지보수성, 서비스 자체의 장기적인 생존 가능성에 직접적인 영향을 미칩니다. 확립된 원칙과 미래 지향적인 접근 방식을 적용하면 오늘날의 요구를 충족할 뿐만 아니라 미래의 불확실성에도 우아하게 대응할 수 있는 인터페이스를 만들 수 있습니다.

학습 내용

시스템이 진화함에 따라 API가 생존하고 번성할 수 있도록 하는 실용적이고 결정 중심적인 프레임워크를 얻게 됩니다. 추상적인 규칙을 암기하는 대신 리소스 명명, HTTP 메서드 사용, 버전 관리, 오류 처리 등 각 설계 선택의 트레이드오프를 이해하게 됩니다. 결국 표준적이면서도 적응 가능한 모범 사례를 갖춘 RESTful API를 자신 있게 설계할 수 있게 되어, 서비스가 향후 개발을 위한 신뢰할 수 있는 기반이 될 수 있습니다.

핵심 원칙: RESTful 설계의 기초

REST(Representational State Transfer)는 프로토콜이 아니라 6가지 제약 조건으로 정의된 아키텍처 스타일입니다. 모범 사례를 갖춘 RESTful API를 설계하려면 먼저 이러한 원칙을 내재화해야 합니다.

Google AdInline article slot
  1. 클라이언트-서버 분리: 이 분리를 통해 클라이언트와 서버가 독립적으로 진화할 수 있습니다. 인터페이스(API)가 안정적으로 유지되는 한 기본 데이터베이스, 인증 메커니즘 또는 비즈니스 로직을 변경해도 클라이언트 애플리케이션이 손상되지 않습니다.
  2. 무상태성: 클라이언트에서 서버로의 각 요청에는 요청을 이해하고 처리하는 데 필요한 모든 정보가 포함되어야 합니다. 서버는 세션 상태를 저장하지 않습니다. 이 제약 조건은 모든 서버 인스턴스가 모든 요청을 처리할 수 있으므로 안정성과 확장성을 향상시킵니다.
  3. 캐시 가능성: 응답은 암시적 또는 명시적으로 캐시 가능 여부를 정의해야 합니다. 적절한 캐싱은 지연 시간과 서버 부하를 크게 줄일 수 있습니다. Cache-Control 헤더가 이를 위한 주요 도구입니다.
  4. 균일한 인터페이스: 이것이 REST의 핵심이자 단순성의 원천입니다. 여기에는 다음이 포함됩니다.
    • 리소스 식별: 리소스(사용자, 주문, 제품 등)는 일반적으로 URI를 통해 요청에서 식별됩니다.
    • 표현을 통한 리소스 조작: 클라이언트가 리소스의 표현(메타데이터 포함)을 보유하면 리소스를 수정하거나 삭제할 수 있는 충분한 정보를 갖게 됩니다.
    • 자기 설명적 메시지: 각 메시지에는 메시지 처리 방법을 설명하는 충분한 정보가 포함됩니다(예: Content-TypeAccept 헤더 사용).
    • 애플리케이션 상태 엔진으로서의 하이퍼미디어(HATEOAS): 이것은 종종 가장 간과되는 제약 조건입니다. HATEOAS는 클라이언트가 서버에서 동적으로 제공하는 하이퍼미디어 링크를 통해서만 API를 탐색해야 함을 의미합니다. "순수한" HATEOAS를 달성하는 것은 어려울 수 있지만, 응답에 관련 링크(예: 페이지가 매겨진 목록의 "next" 링크)를 포함하면 검색 가능성이 크게 향상됩니다.
  5. 계층화된 시스템: 아키텍처는 계층적 계층(예: 보안, 캐싱, 로드 밸런싱)으로 구성될 수 있습니다. 이는 각 계층이 다음 계층에 대해서만 알면 되므로 전체 시스템 복잡성과 보안이 향상됩니다.
  6. 주문형 코드(선택 사항): 서버는 실행 가능한 코드(예: JavaScript)를 전송하여 클라이언트 기능을 확장할 수 있습니다. 일반적인 RESTful API에서는 거의 사용되지 않습니다.

1. 리소스 중심 설계: 명명 및 구조

모범 사례를 갖춘 RESTful API를 설계할 때 가장 중요하고 첫 번째 단계는 시스템의 명사인 리소스를 식별하는 것입니다. 리소스의 이름과 구조를 지정하는 방법은 전체 API의 분위기를 결정합니다.

명사 사용, 동사 사용 금지

URI는 리소스를 나타냅니다. 동작이 아닌 명사여야 합니다.

  • 나쁨: /getUser, /createOrder, /updateProduct
  • 좋음: /users, /orders, /products

컬렉션에는 복수 명사 사용

컬렉션은 리소스 집합입니다. 일관성을 유지하려면 복수형을 사용하세요.

Google AdInline article slot
  • 컬렉션: /users
  • 인스턴스: /users/{userId}
  • 하위 컬렉션: /users/{userId}/orders

계층 구조 유지

리소스는 자연스럽게 계층 구조를 형성합니다. URI 구조는 이를 반영해야 합니다. 예를 들어, 특정 사용자에게 속한 주문은 중첩될 수 있습니다.

GET /users/123/orders — 사용자 123의 모든 주문 검색. GET /users/123/orders/456 — 특정 주문 검색.

⚠️ 경고: 2~3단계 이상의 깊은 중첩은 피하세요. 깊게 중첩된 URI(예: /users/123/orders/456/items/789)는 다루기 어려워질 수 있으며, 쿼리 매개변수를 사용하여 리소스 구조를 평평하게 만들어야 한다는 신호인 경우가 많습니다. 대신 /items?orderId=456을 고려하세요.

Google AdInline article slot

2. HTTP 메서드 및 상태 코드 올바르게 활용

API의 정확성은 HTTP 동사와 상태 코드의 올바른 사용에 크게 의존합니다. 이것이 리소스를 조작하는 메커니즘입니다.

HTTP 메서드

  • GET: 리소스 검색. 안전하고 멱등적이어야 합니다.
  • POST: 새 리소스 생성. 안전하지도 않고 멱등적이지도 않은 작업에 사용됩니다. 응답에는 일반적으로 새로 생성된 리소스를 가리키는 Location 헤더가 포함됩니다.
  • PUT: 특정 URI에서 리소스 생성 또는 교체. 멱등적이어야 합니다. 클라이언트는 완전한 리소스 표현을 보냅니다.
  • PATCH: 리소스 부분 업데이트. 기본적으로 엄격하게 멱등적이지는 않지만 멱등적으로 설계되어야 합니다. 경합 조건을 피하려면 application/merge-patch+json과 같은 특정 미디어 유형과 함께 PATCH를 사용하세요.
  • DELETE: 리소스 제거. 멱등적: 동일한 URI에 대한 두 번째 DELETE는 404 또는 204를 반환해야 합니다.

주요 상태 코드

  • 2xx 성공:
    • 200 OK – 표준 성공.
    • 201 Created – 리소스 생성됨. Location 헤더 포함.
    • 204 No Content – 성공했지만 반환할 콘텐츠 없음(DELETE에 일반적).
  • 3xx 리디렉션:
    • 301 Moved Permanently – URI가 영구적으로 변경된 경우 사용.
    • 304 Not Modified – 캐싱과 함께 사용; 리소스가 변경되지 않았음을 나타냄.
  • 4xx 클라이언트 오류:
    • 400 Bad Request – 일반 클라이언트 측 오류(예: 잘못된 형식의 페이로드).
    • 401 Unauthorized – 인증 누락 또는 잘못됨.
    • 403 Forbidden – 인증되었지만 권한 없음.
    • 404 Not Found – 리소스를 찾을 수 없음.
    • 422 Unprocessable Entity – 요청은 형식이 올바르지만 의미상 유효하지 않음(예: 유효성 검사 오류).
  • 5xx 서버 오류:
    • 500 Internal Server Error – 예기치 않은 서버 오류.
    • 503 Service Unavailable – 유지보수 또는 과부하로 서비스를 사용할 수 없음.

3. 버전 관리 및 진화

소프트웨어 개발에서 유일한 불변은 변화입니다. 잘 설계된 API는 진화를 위한 전략을 가지고 있어야 합니다. 모범 사례를 갖춘 RESTful API를 설계할 때 버전 관리 방식은 클라이언트의 수명과 유지보수성을 결정합니다.

하위 호환성 수용

가장 강력한 전략은 하위 호환 가능한 변경만 수행하는 것입니다. 즉:

  • 요청 또는 응답에 새 필드를 추가할 수 있습니다.
  • 새 엔드포인트를 추가할 수 있습니다.
  • 필드를 제거하거나 이름을 바꾸면 안 됩니다.

그러나 때로는 호환성이 깨지는 변경이 필요합니다. 이때 버전 관리가 필수적입니다.

버전 관리 전략

REST API를 버전 관리하는 방법에는 여러 가지가 있습니다. 선택은 조직의 요구 사항과 기존 인프라에 따라 달라지는 경우가 많습니다.

전략 예시 장점 단점
URI 경로 /v1/users, /v2/users 가장 눈에 띄고 구현하기 쉬우며 개발자 친화적입니다. 시간이 지남에 따라 URI가 비대해질 수 있습니다.
쿼리 매개변수 /users?version=1 URI 경로와 유사하지만 덜 눈에 띕니다. 쉽게 잊혀질 수 있고 관용적이지 않습니다.
사용자 정의 요청 헤더 Api-Version: 1 URI를 깔끔하게 유지합니다. 사용자 정의 도구가 필요하고 검색 가능성이 떨어집니다.
콘텐츠 협상 Accept: application/vnd.myapp.v1+json RESTful하며 표준 HTTP를 활용합니다. 구현하고 이해하기 복잡합니다.

업계 동향(Stripe, Google, GitHub의 주요 공개 API에서 관찰됨)을 종합해 보면 URI 경로 전략이 대부분의 팀에게 가장 인기 있고 간단합니다. 명시적이며 API 버전을 개발자에게 즉시 전달합니다.

지원 중단 전략

새 버전을 도입하고 이전 버전을 지원 중단으로 표시하는 경우, 클라이언트에게 제거에 대한 명확한 일정을 제공하세요. 응답에 Deprecation 또는 Sunset HTTP 헤더를 포함하세요. 합리적인 지원 중단 기간은 개발자에게 마이그레이션할 충분한 시간을 주기 위해 12-24개월일 수 있습니다.

4. 문서화의 중요한 역할

API는 문서화만큼이나 좋습니다. API가 완벽하더라도 문서화가 혼란스럽거나 없으면 사용자는 어려움을 겪고 API는 실패합니다. OpenAPI 사양(이전 Swagger)은 API 문서화의 사실상 표준입니다.

OpenAPI 사용

OpenAPI를 사용하여 API를 정의하면 대화형 문서(Swagger UI 등), 여러 언어의 클라이언트 SDK, 서버 스텁까지 자동으로 생성할 수 있습니다. OpenAPI 문서는 단일 진실 공급원 역할을 합니다.

실용적인 문서화 요령

  • 예제 제공: 모든 엔드포인트에 대해 실제 요청/응답 예제를 제공하세요. 이는 장황한 설명보다 더 가치 있습니다.
  • 오류 코드 설명: 각 엔드포인트에 대해 가능한 모든 오류 코드와 그 의미를 나열하세요. 일관된 오류 처리를 위해 problem+json 구조(RFC 7807에 정의됨)를 포함하세요.
  • 시작 가이드 포함: 빠른 시작 가이드는 개발자가 몇 분 안에 첫 번째 성공적인 API 호출을 할 수 있도록 도와줍니다.

5. 보안 및 데이터 검증

보안은 부차적인 고려 사항이 아닙니다. 처음부터 설계에 통합되어야 합니다. 모범 사례를 갖춘 RESTful API를 설계할 때 보안은 논쟁의 여지가 없는 핵심 요소입니다.

인증 및 권한 부여

  • OAuth 2.0은 권한 부여를 위한 업계 표준입니다. 세분화된 액세스 제어를 허용합니다.
  • API 키는 더 간단하지만 세분화 정도가 낮습니다. 서버 간 통신에 사용하세요.
  • **JWT(JSON Web Tokens)**는 무상태 인증에 널리 사용됩니다.

전송 계층 보안(TLS)

모든 API 엔드포인트에 HTTPS를 항상 적용하세요. TLS 1.2 이상을 사용하세요. 이는 전송 중인 데이터를 도청 및 중간자 공격으로부터 보호합니다.

입력 검증

클라이언트 입력을 절대 신뢰하지 마세요. 비즈니스 로직에 도달하기 전에 경계에서 모든 들어오는 데이터를 검증하세요.

  • 블랙리스트가 아닌 화이트리스트: 금지된 모든 것을 정의하려고 하지 말고 허용되는 것을 정확히 정의하세요.
  • 데이터 유형, 길이 및 형식 검증: string이 숫자가 아닌지, email이 유효한지 확인하세요.
  • 검증 라이브러리 사용: Java에서는 Hibernate Validator, Python에서는 Marshmallow 또는 Pydantic, Node.js에서는 Joi 또는 Zod를 사용하세요.

6. 성능: 캐싱, 페이지 매김 및 필터링

느리거나 쿼리하기 어려운 API는 사용자를 좌절시킵니다. 성능은 핵심 설계 고려 사항입니다.

캐싱

  • 클라이언트 측 캐싱: Cache-Control 헤더를 사용하여 클라이언트가 응답을 캐시할 수 있는 기간을 알려주세요. 예를 들어 Cache-Control: max-age=3600은 1시간 동안 캐시합니다.
  • 서버 측 캐싱: 리버스 프록시 캐시(Varnish 등) 또는 분산 캐시(Redis 등)를 사용하여 빈번한 응답을 저장하세요.
  • E-Tag: 엔터티 태그를 사용하여 조건부 요청을 구현하세요. 서버는 ETag 헤더를 제공하고, 클라이언트는 이후 요청에서 If-None-Match 헤더에 이를 사용합니다. 리소스가 변경되지 않은 경우 서버는 304 Not Modified를 반환하여 대역폭을 절약합니다.

페이지 매김 및 필터링

클라이언트가 전체 데이터 세트를 다운로드하도록 강요해서는 안 됩니다.

  • 페이지 매김: pagesize 또는 offsetlimit 매개변수를 사용하세요. 더 강력한 접근 방식은 "커서 기반" 페이지 매김(next_cursor와 같은 토큰 사용)으로, 대규모 데이터 세트에 더 효율적이고 데이터 변경 시 중복을 방지합니다.
  • 필터링: 필터링에는 쿼리 매개변수를 사용하세요. 예: GET /products?category=books&price_min=10.
    • 필터링을 위한 강력한 표준은 OData 사양이지만, 더 간단한 요구 사항에는 사용자 정의 쿼리 매개변수로 충분합니다.
  • 희소 필드: 클라이언트가 원하는 필드를 지정할 수 있도록 하여 페이로드 크기를 줄이세요. 예: GET /users/123?fields=id,name,email.

7. 테스트 및 품질 보증

API를 제품처럼 취급하세요. 즉, 엄격한 테스트가 필요합니다.

단위 테스트

비즈니스 로직을 격리하여 테스트하세요.

통합 테스트

API가 데이터베이스, 캐시 및 기타 서비스와 상호 작용하는 방식을 테스트하세요.

계약 테스트

마이크로서비스의 부상으로 계약 테스트(예: Pact 사용)는 공급자(API)와 소비자(프론트엔드 또는 다른 서비스)가 올바르게 통신할 수 있도록 보장합니다. 공급자가 소비자의 기대를 충족하는지 확인합니다.

종단 간(E2E) 테스트

클라이언트 요청에서 데이터베이스까지 전체 흐름을 테스트하세요.

자주 묻는 질문

1. 새 API에 REST와 GraphQL 중 어떤 것을 사용해야 하나요?

REST는 잘 정의된 엔터티가 있는 간단한 리소스 중심 API에 탁월한 선택입니다. GraphQL은 데이터가 매우 상호 연결되어 있거나 다양한 데이터 요구 사항을 가진 다양한 클라이언트를 지원해야 할 때 유용합니다. 단순성과 캐싱을 위해 REST를 선택하고, 유연성과 오버/언더 페칭 감소를 위해 GraphQL을 선택하세요.

2. REST API에서 부분 업데이트를 어떻게 처리하나요?

부분 업데이트를 수행하는 올바른 방법은 PATCH 메서드를 사용하는 것입니다. 서버는 application/merge-patch+json(RFC 7396)과 같은 패치 미디어 유형을 지원해야 하며, 이는 서버에 제공된 필드만 적용하도록 지시합니다. 이는 PUT을 통해 전체 리소스를 보내는 것보다 효율적이며 의도하지 않은 덮어쓰기를 방지합니다.

3. 오류를 처리하고 클라이언트에 의미 있는 피드백을 제공하는 가장 좋은 방법은 무엇인가요?

적절한 HTTP 상태 코드(클라이언트 오류는 4xx, 서버 오류는 5xx)를 사용하세요. 응답 본문에서 type URI, title, detail 메시지 및 status를 포함하는 일관된 오류 객체(RFC 7807에 따름)를 반환하세요. 이 구조는 클라이언트에게 문제를 해결하기 위한 프로그래밍 방식 및 사람이 읽을 수 있는 정보를 제공합니다.

4. API 버전 번호는 얼마나 자주 변경해야 하나요?

호환성이 깨지는 변경(예: 필드 제거, 데이터 유형 변경, 요청/응답 구조 변경)을 수행할 때만 새 버전을 도입하세요. 새 필드나 엔드포인트 추가와 같은 호환성이 깨지지 않는 변경은 버전 확산과 개발자 피로를 피하기 위해 기존 버전 내에서 수행해야 합니다.

5. REST API가 HATEOAS를 준수해야 하나요?

HATEOAS는 REST의 핵심 제약 조건 중 하나이지만, 실제로 "순수한" HATEOAS를 달성하는 경우는 드뭅니다. 고품질 API의 경우 각 리소스에 대한 self 링크와 페이지 매김 링크(next, prev)와 같은 일부 하이퍼미디어 컨트롤을 포함하는 것이 가치 있습니다. 이는 전체 하이퍼미디어 엔진의 복잡성 없이 검색 가능성을 향상시킵니다.

— Editorial Team

Advertisement 728x90

다음 읽기