InfoGrab DocsInfoGrab Docs

API 스타일 가이드

요약

이 스타일 가이드는 API 개발에 대한 모범 사례를 권장합니다. 고객에게 두 가지 유형의 API를 제공합니다: 두 API를 병렬로 지원하는 기술적 부담을 줄이기 위해, 가능한 한 많은 구현을 공유해야 합니다. 프론트엔드 개발 시 어떤 API를 사용할지에 대한 자세한 내용은 프론트엔드 가이드를 참조하세요.

이 스타일 가이드는 API 개발에 대한 모범 사례를 권장합니다.

GraphQL 및 REST API#

고객에게 두 가지 유형의 API를 제공합니다:

두 API를 병렬로 지원하는 기술적 부담을 줄이기 위해, 가능한 한 많은 구현을 공유해야 합니다. 예를 들어, 동일한 서비스를 공유할 수 있습니다.

Frontend#

프론트엔드 개발 시 어떤 API를 사용할지에 대한 자세한 내용은 프론트엔드 가이드를 참조하세요.

인스턴스 변수#

인스턴스 변수를 사용하지 마세요. 인스턴스 변수는 필요하지 않습니다(Rails 뷰에서처럼 접근할 필요가 없습니다). 지역 변수를 사용하면 됩니다.

Entities#

엔드포인트의 페이로드를 표시할 때는 항상 Entity를 사용하세요.

Entity 필드 정의#

Entity에 노출된 모든 필드는 유효한 타입을 포함하거나 참조해야 합니다.

다른 Entity 참조#

다른 Entity를 참조하는 필드를 노출할 때는 using 옵션을 사용하세요. using 옵션은 API::Entities 클래스를 가리키는 상수만 허용합니다. 좋은 예시는 다음과 같습니다:

  expose :project, using: ::API::Entities::BasicProjectDetails

유효한 필드 타입#

필드 타입은 문자열로 지정해야 합니다. 다음 타입이 허용됩니다:

카테고리 타입
스칼라 Integer, Float, BigDecimal, Numeric, Date, DateTime, Time, String, Symbol, Boolean
구조체 Hash, Array, Set
특수 JSON, File
Entity 참조 임의의 API::Entities::* 클래스 (문자열로)

필드 타입 정의#

필드 타입은 documentation 해시에 정의해야 합니다:

  expose :id, documentation: { type: 'Integer', example: 1 }
  expose :name, documentation: { type: 'String', example: 'John Doe' }
  expose :active, documentation: { type: 'Boolean', example: true }
  expose :project, documentation: { type: 'API::Entities::BasicProject'}

고영향 Entity와 기능 범위 Entity#

UserBasic, ProjectIdentity, Commit과 같은 일부 기반 Entity는 여러 API 엔드포인트에 임베드되거나 중첩됩니다. 이러한 Entity 중 하나에 단 하나의 expose 호출을 추가하면 해당 Entity를 직접 또는 간접적으로 사용하는 모든 엔드포인트의 JSON 응답이 커집니다. 예를 들어, UserBasic에 단 하나의 expose 호출을 추가하면 212개의 엔드포인트에 영향을 미치고, CustomAttribute에 추가하면 238개에 영향을 미칩니다.

API 응답 페이로드의 통제되지 않는 증가를 방지하기 위해 일련의 고영향 EntityAPI/EntityExposureGrowth RuboCop cop으로 보호됩니다. 이 cop은 api_entity_exposure_baseline.yml에서 Entity별로 허용된 필드의 허용 목록을 관리합니다. 보호된 Entity에 추가된 새로운 expose 호출이 허용 목록에 없으면 위반이 트리거됩니다.

왜 중요한가#

  • 성능: 추가 필드는 해당 Entity를 포함하는 모든 응답에 대해 직렬화되어 페이로드 크기와 직렬화 시간이 증가합니다.

  • 브레이킹 체인지 위험: 기존에 노출된 필드를 제거하는 것은 브레이킹 체인지로 간주됩니다. 기반 Entity에 추가된 필드는 많은 소비자에게 영향을 미치기 때문에 제거하기가 특히 어렵습니다.

  • 연쇄 영향: Entity는 상속(class User < UserBasic)과 임베딩(expose :author, using: UserBasic)을 통해 구성됩니다. UserBasic에 추가된 단 하나의 필드가 User, UserPublic, 그리고 이를 임베드하는 모든 Entity에 연쇄 영향을 미칩니다.

권장 패턴#

고영향 Entity에 필드를 추가하는 대신, 기능 범위 Entity를 생성하세요: 새 필드가 필요한 엔드포인트에서만 사용되는 새로운 목적 중심 Entity 클래스입니다.

가장 간단한 접근 방법은 기반 Entity를 상속받아 필요한 필드를 추가하는 새 Entity를 생성하는 것입니다:

# bad - adds :notification_email to every endpoint using UserBasic (212 endpoints)
module API
  module Entities
    class UserBasic < UserSafe
      expose :state
      expose :avatar_url
      expose :web_url
      expose :notification_email  # <-- new field inflates 212 endpoint responses
    end
  end
end

# good - create a domain-scoped entity used only by the endpoints that need it
module API
  module Entities
    module Ci
      class JobOwner < UserBasic
        expose :notification_email, documentation: { type: 'String', example: 'user@example.com' }
      end
    end
  end
end

Entity 이름은 필드 내용(예: UserWithNotificationEmail)이 아닌 도메인 컨텍스트에서 무엇을 나타내는지(예: Ci::JobOwner)에 따라 지정하세요. UserWithNotificationEmail과 같은 이름은 관련 없는 도메인에서 재사용을 유도하여 연쇄 문제를 다시 만들게 됩니다. 도메인 범위 이름은 Entity를 단일 사용 사례에 집중시킵니다.

그런 다음 해당 Entity가 필요한 엔드포인트에서만 새 Entity를 사용하세요:

# In your API endpoint file
desc 'List CI job owners' do
  detail 'Returns the owners of CI jobs with notification details.'
  success Entities::Ci::JobOwner
  tags ['ci']
end
get ':id/ci/job_owners' do
  owners = find_job_owners(params[:id])
  present owners, with: Entities::Ci::JobOwner
end

허용 목록 업데이트#

api_entity_exposure_baseline.yml의 허용 목록은 각 보호된 Entity에 대해 허용된 필드 이름을 기록합니다. 새 필드를 추가하기 위해 허용 목록을 수동으로 편집하지 마세요; 대신 위에서 설명한 대로 기능 범위 Entity를 생성하세요.

필드가 실제로 고영향 Entity에 속한다고 생각되는 경우(예를 들어, 대다수의 소비자에게 필요한 경우), 진행하기 전에 트레이드오프를 평가하기 위해 API Platform 팀과 토의를 여세요.

문서#

새로운 또는 업데이트된 각 API 엔드포인트에는 문서가 포함되어야 합니다. 문서는 동일한 머지 리퀘스트에 있어야 하며, 꼭 필요한 경우에만 원본 머지 리퀘스트와 동일한 마일스톤의 후속 머지 리퀘스트에 포함될 수 있습니다.

Markdown 및 OpenAPI 정의 파일에서 API 리소스를 문서화하는 방법에 대한 자세한 내용은 문서 스타일 가이드 RESTful API 페이지를 참조하세요.

메서드 및 파라미터 설명#

모든 메서드는 Grape DSL을 사용하여 설명해야 합니다 (좋은 예시는 environments.rb를 참조하세요):

  • 메서드 요약을 위한 desc. 120자를 초과하지 않는 요약 문자열을 포함해야 합니다.

  • desc 블록을 위한 detail. 문자열이어야 합니다.

  • desc 블록을 위한 success. 성공 응답을 정의합니다.

  • desc 블록을 위한 tags. 문자열 또는 문자열 배열이어야 합니다.

  • 메서드 파라미터를 위한 params. 파라미터의 설명, 유효성 검사 및 강제 변환 역할을 합니다.

좋은 예시는 다음과 같습니다:

desc 'Get all broadcast messages' do
  detail 'This feature was introduced in GitLab 8.12.'
  success Entities::System::BroadcastMessage
  tags ['broadcast_messages']
end
params do
  optional :page,     type: Integer, desc: 'Current page number'
  optional :per_page, type: Integer, desc: 'Number of messages per page'
end
get do
  messages = System::BroadcastMessage.all

  present paginate(messages), with: Entities::System::BroadcastMessage
end

엔드포인트 desc 정의#

모든 엔드포인트는 desc 블록에 요약 문자열을 포함해야 합니다. 요약은 REST 리소스에 대한 작업을 설명하며 생성된 OpenAPI 문서에 사용됩니다.

요약은 다음을 권장합니다:

  • HTTP 메서드에 맞는 동사로 시작 (Get, List, Create, Update, Delete)

  • 작업 대상 리소스 식별

  • 유사한 엔드포인트를 구분하기 위한 한정어 포함(필요한 경우)

요약은 다음을 반드시 해야 합니다:

  • 문자열 리터럴 또는 보간된 문자열이어야 함(변수나 메서드 호출 불가)

  • 120자를 초과하지 않아야 함

좋은 예시는 다음과 같습니다:

  desc 'Get a specific environment' do
    detail 'Returns environment details. This feature was introduced in GitLab 18.12.'
    success Entities::Environment
  end

나쁜 예시는 다음과 같습니다:

  desc 'Get a specific environment. Returns environment details. This feature was introduced in GitLab 18.12.' do
    detail 'Only available to authenticated project owner.'
    success Entities::Environment
  end

엔드포인트 details 정의#

모든 엔드포인트는 각 desc 블록에 detail 값을 가져야 합니다. 값은 문자열이어야 합니다. detaildesc에서 다루지 않은 추가 세부 사항을 설명해야 합니다:

  • 엔드포인트가 추가된 GitLab 버전.

  • 기능 플래그 뒤에 있는 경우, 대신 다음과 같이 언급하세요: This feature is gated by the :feature\_flag\_symbol feature flag.

  • 엔드포인트가 더 이상 사용되지 않는 경우 및 계획된 제거 날짜.

detail 또는 desc 요약 문자열에 "experiment", "experimental", "general availability", "GA", "beta"와 같은 라이프사이클 용어를 포함하지 마세요. 대신 route_setting :lifecycle을 사용하세요. 자세한 내용은 엔드포인트 라이프사이클 표시를 참조하세요.

엔드포인트 success 정의#

모든 엔드포인트는 각 desc 블록에 success 값을 가져야 합니다. 값은 엔드포인트의 성공 응답을 정확하게 설명해야 합니다.

성공 응답을 문서화하기 위해 http_codes 옵션을 사용하지 마세요.

success 옵션은 다음 중 하나를 허용합니다:

  • Grape::Entity 클래스를 직접

  • 옵션 해시

해시 형식을 사용할 때 다음 옵션을 사용할 수 있습니다:

옵션 타입 필수 여부 설명
code Integer 아니요 HTTP 상태 코드. 필수는 아니지만 항상 의도한 응답 코드를 지정하세요.
model Entities::* JSON 응답에 필수 응답 본문에 반환되는 Grape::Entity 클래스. 모델이 없으면 OpenAPI 스펙에서 응답 스키마나 예시가 생성되지 않습니다. 204 No Content나 리다이렉트와 같이 본문이 없는 응답에만 생략하세요.
message String 아니요 응답에 대한 짧은 설명.
is_array Boolean 아니요 응답이 모델의 배열인 경우 true로 설정. 해시 형식에서만 필요합니다. 배열로 Entity 클래스를 감싸는 것(success [Entities::MyEntity])과 동일합니다.
example Hash 아니요 응답 본문의 단일 인라인 예시. examples와 상호 배타적. model이 필요합니다.
examples Hash 아니요 응답 본문의 명명된 예시. example과 상호 배타적. model이 필요합니다.

엔드포인트가 반환하는 내용에 따라 success 값을 형식화하세요:

엔드포인트가 객체로 응답하는 경우, Grape::Entity 클래스를 직접 전달하거나 model: 옵션을 사용하세요:

# Direct form
success Entities::System::BroadcastMessage

# Hash form
success code: 200, model: Entities::System::BroadcastMessage

엔드포인트가 컬렉션으로 응답하는 경우, 배열로 Entity 클래스를 감싸거나 해시 형식에서 is_array: true를 사용하세요. 둘 다 동일합니다:

# Direct form
success [Entities::System::BroadcastMessage]

# Hash form — use when you also need to specify other options
success code: 200, model: Entities::System::BroadcastMessage, is_array: true

엔드포인트가 객체로 응답하지 않는 경우, 상태 코드와 메시지를 포함하세요:

success code: 204, message: 'Record was deleted'

엔드포인트가 여러 가능한 성공 코드를 반환하는 경우, 배열을 전달하세요:

success [
  { code: 200, model: Entities::Security::VulnerabilityScanning::SbomScan },
  { code: 202, message: 'Scan in progress' }
]

example: 또는 examples:가 제공되지 않고 model:이 정의된 경우, 예시가 자동으로 생성됩니다. Entity 필드의 documentation: { example: ... } 값에서 생성되거나, 필드 수준 예시가 정의되지 않은 경우 필드 타입에서 생성됩니다.

엔드포인트가 객체로 응답하고 전체 응답 본문을 보여주거나 여러 가능한 응답 본문 예시를 제공하려는 경우, 단일 인라인 값에는 example:, 여러 명명된 시나리오에는 examples:를 사용하세요. 둘 다 model:을 필요로 하며 상호 배타적입니다:

# Single example
success code: 200, model: Entities::System::BroadcastMessage,
        example: {
          id: 1,
          message: 'Scheduled maintenance at 23:00',
          starts_at: '2024-03-01T23:00:00.000Z',
          ends_at: '2024-03-02T01:00:00.000Z',
          active: false
        }

# Multiple named examples
success code: 200, model: Entities::System::BroadcastMessage,
        examples: {
          active_message: {
            summary: 'An active broadcast message',
            value: {
              id: 1,
              message: 'Scheduled maintenance at 23:00',
              starts_at: '2024-03-01T23:00:00.000Z',
              ends_at: '2024-03-02T01:00:00.000Z',
              active: true
            }
          },
          expired_message: {
            summary: 'An expired broadcast message',
            value: {
              id: 2,
              message: 'Maintenance complete',
              starts_at: '2024-03-01T23:00:00.000Z',
              ends_at: '2024-03-02T01:00:00.000Z',
              active: false
            }
          }
        }

엔드포인트를 더 이상 사용 안 함으로 표시#

엔드포인트를 더 이상 사용하지 않을 때, desc 블록에 다음을 추가하세요:

  • deprecated true 옵션을 추가하세요. 이렇게 하면 작업에 표준 OpenAPI deprecated: true 플래그가 설정됩니다.

  • 더 이상 사용하지 않는 시점과 마이그레이션 안내를 detail 옵션에 추가하세요.

더 이상 사용하지 않는 엔드포인트에는 route_setting :lifecycle을 사용하지 마세요. 실험 및 베타 Stage와 달리, 더 이상 사용하지 않음은 deprecated 필드를 통해 OpenAPI 사양에서 기본적으로 지원되며, deprecated true가 이에 직접 매핑됩니다.

desc 'Get legacy broadcast messages' do
  detail 'Deprecated in GitLab 17.0. Use /api/v4/broadcast_messages instead.'
  deprecated true
  success Entities::System::BroadcastMessage
  tags ['broadcast_messages']
end

이를 통해 OpenAPI 사양에서 더 이상 사용하지 않음을 프로그래밍 방식으로 검색할 수 있게 됩니다.

엔드포인트 라이프사이클 표시#

엔드포인트가 아직 일반적으로 사용 가능하지 않을 때, route_setting :lifecycle을 사용하여 개발 Stage를 표시하세요. 유효한 값은 :experiment:beta입니다.

desc 요약이나 detail 문자열에 라이프사이클 정보를 넣지 마세요. API/LifecycleInDescription RuboCop cop이 이 규칙을 시행합니다.

일반적으로 사용 가능한 엔드포인트에는 route_setting :lifecycle을 생략하세요.

# bad -- Specifies "experimental" in "detail"
desc 'Get all widgets' do
  detail 'This feature is experimental.'
  tags %w[widgets]
end

# good -- Specifies "experiment" as route_setting
route_setting :lifecycle, :experiment
desc 'Get all widgets' do
  detail 'Introduced in GitLab 18.10.'
  tags %w[widgets]
end

# good -- Specifies "beta" as route_setting
route_setting :lifecycle, :beta
desc 'Get all widgets' do
  detail 'Introduced in GitLab 18.10.'
  tags %w[widgets]
end

route_setting :lifecycle 값은 x-gitlab-lifecycle 벤더 확장으로서 생성된 OpenAPI 사양에 포함됩니다. 이를 통해 라이프사이클 상태를 프로그래밍 방식으로 검색할 수 있게 됩니다.

개발 Stage에 대한 자세한 내용은 개발 Stage 및 지원을 참조하세요.

태그 선택#

모든 엔드포인트는 desc 블록마다 tags에 최소한 하나의 값이 정의되어야 합니다. 태그는 API 호출에서 작업 대상 객체 유형을 복수형으로 설명해야 합니다.

대부분의 경우, API의 파일명이 충분하지만 너무 세분화될 수도 있습니다.

좋은 태그 이름#

  • audit_events

  • users

  • clusters

나쁜 태그 이름#

  • commit (단수형)

  • epic_management (Entity가 아닌 제품 카테고리와 결합됨)

태그의 올바른 이름이 명확하지 않은 경우, 기술 문서 작성자에게 안내를 요청하세요.

String 파라미터 제한#

클라이언트가 무제한 페이로드를 전송할 수 없도록 String 파라미터를 제한하세요. 무제한 문자열은 호출자가 서버 메모리와 처리 시간을 소비하는 대용량 요청을 제출할 수 있게 하며, 남용의 표면적을 넓힙니다.

가능한 경우, 모든 String 파라미터에 다음 유효성 검사기 중 하나를 사용하세요:

  • 파라미터가 고정된 허용 값 집합을 허용하는 경우 values:.

  • 자유 형식 문자열의 최대 문자 수를 제한하기 위한 limit:.

  • 파라미터가 특정 형식과 일치해야 하는 경우 regexp:.

params do
  optional :state, type: String, values: %w[opened closed], desc: 'Filter by state'
  optional :name, type: String, limit: 255, desc: 'Name of the resource'
end

limit: 유효성 검사기는 API::Validations::Validators::Limit에 구현되어 있으며 구성된 크기보다 긴 값을 거부합니다.

브레이킹 체인지#

REST API v4에 브레이킹 체인지를 하면 안 됩니다. 주요 GitLab 릴리즈에서도 마찬가지입니다. 브레이킹 체인지란 무엇인가브레이킹 체인지가 아닌 것은 무엇인가를 참조하세요.

REST API는 GitLab 버전 관리와 독립적으로 자체 버전 관리를 유지합니다. 현재 REST API 버전은 4입니다. REST API에 대해 시맨틱 버전 관리를 따르기로 약속했기 때문에 브레이킹 체인지를 할 수 없습니다. REST API의 주요 버전 변경(가장 가능성 높은 경우 5)은 현재 계획되거나 예정되지 않았습니다.

예외는 실험 또는 베타로 표시된 API 기능입니다. 이러한 기능은 언제든지 제거되거나 변경될 수 있습니다.

브레이킹 체인지 대신 할 수 있는 것#

다음 섹션에서는 브레이킹 체인지를 하는 대신의 대안을 제안합니다.

브레이킹 없이 스키마를 변경에 맞게 조정#

기능이 변경되면, API에 브레이킹 체인지 없이 하위 호환성을 수용하는 것을 목표로 해야 합니다.

브레이킹 체인지를 도입하는 대신, API 컨트롤러 레이어를 변경하여 API 소비자에게 어떤 변경도 표시하지 않는 방식으로 기능 변경에 적응하세요.

예를 들어, 머지 리퀘스트의 WIP 기능을 Draft로 이름을 변경했습니다. 변경을 달성하기 위해:

  • API 응답에 새 draft 필드를 추가했습니다.

  • 또한 이전 work_in_progress 필드도 유지했습니다.

고객은 기존 API 통합에 어떤 혼란도 겪지 않았습니다.

기능 제거 시 해야 할 것#

엔드포인트가 인터페이스했던 기능이 주요 GitLab 버전에서 제거됨에 따라, API 하위 호환성 유지와 사용자가 신뢰할 수 있는 결과 반환 간의 균형을 유지해야 합니다.

컨텍스트에 따라 적절한 접근 방식을 선택하세요:

조용한 성능 저하 - 오류가 더 넓은 기능을 방해할 때 사용:

  • 필드에서 합리적인 정적 값이나 빈 응답(예: null 또는 [])을 반환하세요.

  • 인수를 no-op으로 전환하여 계속 인수를 허용하지만 더 이상 작동하지 않도록 하세요.

  • 하나의 설정을 제거해도 전체 엔드포인트가 실패하지 않아야 하는 Application Settings와 같은 엔드포인트에 가장 적합합니다.

오류 응답 - 기능이 완전히 제거된 경우 사용:

  • 제거된 기능이 엔드포인트의 주요 목적이었을 때 404 Not Found를 반환하세요.

  • 이는 기능이 더 이상 존재하지 않음을 사용자에게 명확하게 알립니다.

핵심 원칙은 기존 고객 API 통합이 가능한 경우 우아하게 성능을 저하시키고, 기능이 더 이상 사용 불가능할 때 명확한 피드백을 제공해야 한다는 것입니다. 엔드포인트는 동일한 필드로 계속 응답하고 동일한 인수를 허용하지만, 기본 기능 상호작용은 더 이상 작동하지 않습니다.

의도한 변경 사항은 미리 문서화되어야 합니다. v4 더 이상 사용 안 함 가이드를 따르세요.

예를 들어, 애플리케이션 설정을 제거했을 때, 합리적인 정적 값을 반환하는 이전 API 필드를 유지했습니다.

브레이킹 체인지란 무엇인가#

브레이킹 체인지의 예시는 다음과 같습니다:

  • 필드, 인수 또는 열거형 값 제거 또는 이름 변경. JSON 응답에서 필드는 모든 JSON 키입니다.

  • 엔드포인트 제거.

  • 새로운 리다이렉트 추가 (모든 클라이언트가 리다이렉트를 따르는 것은 아닙니다).

  • 응답의 콘텐츠 타입 변경.

  • 응답에서 필드의 타입 변경. JSON 응답에서 이는 Number, String, Boolean, Array 또는 Object 타입이 다른 타입으로 변경되는 것입니다.

  • 새로운 필수 인수 추가.

  • 인증, 인가 또는 기타 헤더 요구 사항 변경.

  • 500 이외의 임의의 상태 코드 변경.

브레이킹 체인지가 아닌 것은 무엇인가#

비-브레이킹 체인지의 예시는 다음과 같습니다:

  • 엔드포인트, 비필수 인수, 필드 또는 열거형 값 추가와 같은 모든 추가적 변경.

  • 오류 메시지 변경.

  • 500 상태 코드에서 지원되는 임의의 상태 코드로의 변경 (이는 버그 수정입니다).

  • 응답에서 반환되는 필드의 순서 변경.

실험, 베타, 일반적으로 사용 가능한 기능#

API 요소를 실험 및 베타 기능으로 추가할 수 있습니다. 이는 추가적 변경이어야 하며, 그렇지 않으면 브레이킹 체인지로 분류됩니다.

실험 또는 베타로 표시된 API 요소는 브레이킹 체인지 정책에서 면제되며, 사전 통보 없이 언제든지 변경되거나 제거될 수 있습니다.

실험 상태에 있는 동안:

추가된 엔드포인트는 404 Not Found를 반환해야 합니다.

베타 상태에 있는 동안:

기능이 일반적으로 사용 가능해지면:

선언된 파라미터#

Grape는 params 블록으로 선언된 파라미터에만 접근할 수 있게 합니다. 전달되었지만 허용되지 않은 파라미터를 필터링합니다. 자세한 내용은 Ruby Grape의 declared()에 대한 문서를 참조하세요.

부모 네임스페이스에서 파라미터 제외#

기본적으로 declared(params)는 모든 부모 네임스페이스에서 정의된 파라미터를 포함합니다. 자세한 내용은 Ruby Grape의 include_parent_namespaces에 대한 문서를 참조하세요.

대부분의 경우 부모 네임스페이스에서 파라미터를 제외해야 합니다:

declared(params, include_parent_namespaces: false)

declared(params) 사용 시기#

파라미터 해시를 메서드 호출의 인수로 전달할 때는 항상 declared(params)를 사용해야 합니다.

예를 들어:

# bad
User.create(params) # imagine the user submitted `admin=1`... :)

# good
User.create(declared(params, include_parent_namespaces: false).to_h)

declared(params)Hashie::Mash 객체를 반환하며, 이 객체에는 반드시 .to_h를 호출해야 합니다.

하지만 단일 요소에 접근할 때는 params[key]를 직접 사용할 수 있습니다.

예를 들어:

# good
Model.create(foo: params[:foo])

Array 타입#

Grape v1.3+에서 Array 타입은 coerce_with 블록으로 정의되어야 하며, 그렇지 않으면 API 요청에서 문자열이 전달될 때 파라미터 유효성 검사에 실패합니다. 자세한 내용은 Grape 업그레이드 문서를 참조하세요.

nil 입력의 자동 강제 변환#

Grape v1.3.3 이전에는 nil 값을 가진 Array 파라미터가 자동으로 빈 Array로 강제 변환되었습니다. 그러나 v1.3.3의 이 풀 리퀘스트로 인해 이제는 더 이상 그렇지 않습니다. 예를 들어, 선택적 파라미터가 있는 PUT /test 요청을 정의한다고 가정해 보겠습니다:

optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The user ids for this rule'

일반적으로 PUT /test?user_ids에 대한 요청은 Grape가 { user_ids: nil }params를 전달하게 합니다.

이는 빈 배열을 예상하는 엔드포인트에서 nil 입력을 제대로 처리하지 못하면 오류를 유발할 수 있습니다. 이전 동작을 유지하기 위해, 모든 API 호출의 before 블록에서 사용되는 coerce_nil_params_to_array! 헬퍼 메서드가 있습니다:

before do
  coerce_nil_params_to_array!
end

이 변경으로 PUT /test?user_ids에 대한 요청은 Grape가 params{ user_ids: [] }로 전달하게 합니다.

이를 더 쉽게 만들기 위한 Grape 트래커의 열린 이슈가 있습니다.

Workhorse 보조 업로드#

파일 콘텐츠를 허용하는 모든 REST API 엔드포인트는 Workhorse 보조 업로드를 사용해야 합니다.

구현 세부 사항은 Workhorse 업로드 문서를 참조하세요.

HTTP 상태 헬퍼 사용#

200이 아닌 HTTP 응답의 경우, lib/api/helpers.rb에 제공된 헬퍼(not_found! 또는 no_content! 등)를 사용하여 올바른 동작을 보장하세요. 이러한 헬퍼는 Grape 내부에서 throw를 호출하여 엔드포인트 실행을 중단합니다.

DELETE 요청의 경우, 일반적으로 destroy_conditionally! 헬퍼를 사용해야 하며, 이 헬퍼는 기본적으로 성공 시 204 No Content 응답을 반환하거나, 주어진 If-Unmodified-Since 헤더가 범위를 벗어난 경우 412 Precondition Failed 응답을 반환합니다. 이 헬퍼는 전달된 리소스에서 #destroy를 호출하지만, 블록을 전달하여 커스텀 삭제 메서드를 구현할 수도 있습니다.

HTTP 동사 선택#

새로운 API 라우트를 정의할 때, 올바른 HTTP 요청 메서드를 사용하세요.

PATCH와 PUT 간의 결정#

Rails 애플리케이션에서 PATCHPUT 요청 메서드는 모두 컨트롤러의 update 메서드로 라우팅됩니다. GitLab API를 작성하는 데 사용하는 프레임워크인 Grape에서는 업데이트를 수행하는 엔드포인트에 대해 PATCH 또는 PUT HTTP 동사를 명시적으로 설정해야 합니다.

엔드포인트가 주어진 리소스의 모든 속성을 업데이트하는 경우, PUT 요청 메서드를 사용하세요. 엔드포인트가 주어진 리소스의 일부 속성을 업데이트하는 경우, PATCH 요청 메서드를 사용하세요.

PATCH의 좋은 예시: PATCH /projects/:id/protected_branches/:name PUT의 좋은 예시: PUT /projects/:id/merge_requests/:merge_request_iid/approve

종종 좋은 PUT 엔드포인트는 ID와 동사만 있습니다(위의 예시에서 "approve"). 또는 단일 값만 있고 키/값 쌍을 나타냅니다.

Rails 블로그에는 PATCH가 업데이트를 수행하는 웹 API 엔드포인트에 일반적으로 가장 적합한 동사인 이유에 대한 자세한 설명이 있습니다.

GitLab Rails 코드베이스에서 API 경로 헬퍼 사용#

상대 URL 아래 GitLab 설치를 지원하기 때문에, Grape로 생성된 API 경로 헬퍼를 사용할 때 이를 고려해야 합니다. 그러한 API 경로 헬퍼 사용은 expose_path 헬퍼 호출로 감싸져야 합니다.

예를 들어:

- endpoint = expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid))

커스텀 유효성 검사기#

API 요청에서 일부 파라미터를 유효성 검사하기 위해, 추가로 전달하기 전에 (예: Gitaly) 유효성 검사를 수행합니다. 다음은 지금까지 추가한 커스텀 유효성 검사기와 사용 방법입니다. 또한 새 커스텀 유효성 검사기를 추가하는 방법에 대한 가이드도 작성했습니다.

커스텀 유효성 검사기 사용#

FilePath:

GitLab은 파일 경로를 탐색해야 하는 다양한 기능을 지원합니다. FilePath 유효성 검사기는 다양한 경우에 대해 파라미터 값을 유효성 검사합니다. 주로 경로가 상대 경로인지, File::Separator를 사용하는 ../../ 상대 탐색을 포함하는지, 그리고 /etc/passwd/와 같이 경로가 절대 경로인지 확인합니다. 기본적으로 절대 경로는 허용되지 않습니다. 그러나 다음과 같은 방법으로 선택적으로 허용된 절대 경로의 허용 목록을 전달할 수 있습니다: requires :file_path, type: String, file_path: { allowlist: ['/foo/bar/', '/home/foo/', '/app/home'] }

Git SHA:

Git SHA 유효성 검사기는 Git SHA 파라미터가 유효한 SHA인지 확인합니다. commit.rb 파일에 언급된 정규식을 사용하여 확인합니다.

Absence:

Absence 유효성 검사기는 주어진 파라미터 해시에서 특정 파라미터가 없는지 확인합니다.

IntegerNoneAny:

IntegerNoneAny 유효성 검사기는 주어진 파라미터의 값이 Integer, None 또는 Any인지 확인합니다. 요청을 계속하려면 언급된 값 중 하나만 허용합니다.

ArrayNoneAny:

ArrayNoneAny 유효성 검사기는 주어진 파라미터의 값이 Array, None 또는 Any인지 확인합니다. 요청을 계속하려면 언급된 값 중 하나만 허용합니다.

EmailOrEmailList:

EmailOrEmailList 유효성 검사기는 문자열이나 문자열 목록의 값에 유효한 이메일 주소만 포함되는지 확인합니다. 모든 유효한 이메일 주소가 있는 목록만 요청을 계속하도록 허용합니다.

새 커스텀 유효성 검사기 추가#

커스텀 유효성 검사기는 파라미터를 플랫폼으로 추가 처리를 위해 전송하기 전에 유효성을 검사하는 좋은 방법입니다. 처음부터 잘못된 파라미터를 식별하면 서버와 플랫폼 사이의 왕복을 줄일 수 있습니다.

커스텀 유효성 검사기를 추가해야 한다면, validators 디렉터리에 자체 파일로 추가됩니다. API를 추가하기 위해 Grape를 사용하기 때문에 유효성 검사기 클래스에서 Grape::Validations::Validators::Base 클래스를 상속합니다. 이제 params 해시와 유효성 검사할 param 이름이라는 두 파라미터를 받는 validate_param! 메서드만 정의하면 됩니다.

메서드의 본문은 파라미터 값의 유효성을 검사하는 작업을 수행하고 호출 메서드에 적절한 오류 메시지를 반환합니다.

마지막으로, 아래 줄을 사용하여 유효성 검사기를 등록합니다:

Grape::Validations.register_validator(<validator name as symbol>, ::API::Helpers::CustomValidators::)

유효성 검사기를 추가한 후에는 validators 디렉터리의 자체 파일에 rspec을 추가해야 합니다.

내부 API#

내부 API는 내부 사용을 위해 문서화되어 있습니다. 다른 구성 요소가 어떤 엔드포인트를 사용하는지 알 수 있도록 최신 상태로 유지하세요.

N+1 문제 방지#

API 엔드포인트에서 레코드 컬렉션을 반환할 때 일반적으로 발생하는 N+1 문제를 방지하기 위해 즉시 로딩을 사용해야 합니다.

API 내에서 이를 수행하는 표준 방법은 모델이 API에서 반환된 연관 관계와 데이터를 미리 로드하는 with_api_entity_associations 스코프를 구현하는 것입니다. 이 스코프의 예시는 Issue 모델에서 볼 수 있습니다.

API에서 동일한 모델이 여러 Entity를 가지는 상황(예: UserBasic, User, UserPublic)에서는 이 스코프를 적용할 때 재량을 사용해야 합니다. 가장 기본적인 Entity에 최적화하고, 그 다음 Entity들이 해당 스코프를 기반으로 구축할 수 있습니다.

with_api_entity_associations 스코프는 to-dos API에서 반환될 때 Todo 타깃에 대해 자동으로 데이터를 미리 로드합니다.

미리 로딩에 대한 더 많은 컨텍스트와 토론은 스코프를 도입한 이 머지 리퀘스트를 참조하세요.

테스트로 검증#

API 엔드포인트가 컬렉션을 반환할 때, 지금과 미래에 API 엔드포인트에 N+1 문제가 없음을 검증하는 테스트를 항상 추가하세요. ActiveRecord::QueryRecorder를 사용하여 이를 수행할 수 있습니다.

예시:

def make_api_request
  get api('/foo', personal_access_token: pat)
end

it 'avoids N+1 queries', :request_store do
  # Firstly, record how many PostgreSQL queries the endpoint will make
  # when it returns a single record
  create_record

  control = ActiveRecord::QueryRecorder.new { make_api_request }

  # Now create a second record and ensure that the API does not execute
  # any more queries than before
  create_record

  expect { make_api_request }.not_to exceed_query_limit(control)
end

테스트#

새 API 엔드포인트에 대한 테스트를 작성할 때, /spec/fixtures/api/schemas에 위치한 스키마 픽스처를 사용하는 것을 고려하세요. 응답이 주어진 스키마와 일치하는지 expect할 수 있습니다:

expect(response).to match_response_schema('merge_requests')

또한 테스트에서 N+1 성능 검증도 참조하세요.

체인지로그 항목 포함#

모든 클라이언트 대면 변경 사항은 반드시 체인지로그 항목을 포함해야 합니다. 내부 API는 해당하지 않습니다.

API 스타일 가이드

GitLab v19.1
원문 보기
요약

이 스타일 가이드는 API 개발에 대한 모범 사례를 권장합니다. 고객에게 두 가지 유형의 API를 제공합니다: 두 API를 병렬로 지원하는 기술적 부담을 줄이기 위해, 가능한 한 많은 구현을 공유해야 합니다. 프론트엔드 개발 시 어떤 API를 사용할지에 대한 자세한 내용은 프론트엔드 가이드를 참조하세요.

이 스타일 가이드는 API 개발에 대한 모범 사례를 권장합니다.

GraphQL 및 REST API#

고객에게 두 가지 유형의 API를 제공합니다:

두 API를 병렬로 지원하는 기술적 부담을 줄이기 위해, 가능한 한 많은 구현을 공유해야 합니다. 예를 들어, 동일한 서비스를 공유할 수 있습니다.

Frontend#

프론트엔드 개발 시 어떤 API를 사용할지에 대한 자세한 내용은 프론트엔드 가이드를 참조하세요.

인스턴스 변수#

인스턴스 변수를 사용하지 마세요. 인스턴스 변수는 필요하지 않습니다(Rails 뷰에서처럼 접근할 필요가 없습니다). 지역 변수를 사용하면 됩니다.

Entities#

엔드포인트의 페이로드를 표시할 때는 항상 Entity를 사용하세요.

Entity 필드 정의#

Entity에 노출된 모든 필드는 유효한 타입을 포함하거나 참조해야 합니다.

다른 Entity 참조#

다른 Entity를 참조하는 필드를 노출할 때는 using 옵션을 사용하세요. using 옵션은 API::Entities 클래스를 가리키는 상수만 허용합니다. 좋은 예시는 다음과 같습니다:

  expose :project, using: ::API::Entities::BasicProjectDetails

유효한 필드 타입#

필드 타입은 문자열로 지정해야 합니다. 다음 타입이 허용됩니다:

카테고리 타입
스칼라 Integer, Float, BigDecimal, Numeric, Date, DateTime, Time, String, Symbol, Boolean
구조체 Hash, Array, Set
특수 JSON, File
Entity 참조 임의의 API::Entities::* 클래스 (문자열로)

필드 타입 정의#

필드 타입은 documentation 해시에 정의해야 합니다:

  expose :id, documentation: { type: 'Integer', example: 1 }
  expose :name, documentation: { type: 'String', example: 'John Doe' }
  expose :active, documentation: { type: 'Boolean', example: true }
  expose :project, documentation: { type: 'API::Entities::BasicProject'}

고영향 Entity와 기능 범위 Entity#

UserBasic, ProjectIdentity, Commit과 같은 일부 기반 Entity는 여러 API 엔드포인트에 임베드되거나 중첩됩니다. 이러한 Entity 중 하나에 단 하나의 expose 호출을 추가하면 해당 Entity를 직접 또는 간접적으로 사용하는 모든 엔드포인트의 JSON 응답이 커집니다. 예를 들어, UserBasic에 단 하나의 expose 호출을 추가하면 212개의 엔드포인트에 영향을 미치고, CustomAttribute에 추가하면 238개에 영향을 미칩니다.

API 응답 페이로드의 통제되지 않는 증가를 방지하기 위해 일련의 고영향 EntityAPI/EntityExposureGrowth RuboCop cop으로 보호됩니다. 이 cop은 api_entity_exposure_baseline.yml에서 Entity별로 허용된 필드의 허용 목록을 관리합니다. 보호된 Entity에 추가된 새로운 expose 호출이 허용 목록에 없으면 위반이 트리거됩니다.

왜 중요한가#

  • 성능: 추가 필드는 해당 Entity를 포함하는 모든 응답에 대해 직렬화되어 페이로드 크기와 직렬화 시간이 증가합니다.

  • 브레이킹 체인지 위험: 기존에 노출된 필드를 제거하는 것은 브레이킹 체인지로 간주됩니다. 기반 Entity에 추가된 필드는 많은 소비자에게 영향을 미치기 때문에 제거하기가 특히 어렵습니다.

  • 연쇄 영향: Entity는 상속(class User < UserBasic)과 임베딩(expose :author, using: UserBasic)을 통해 구성됩니다. UserBasic에 추가된 단 하나의 필드가 User, UserPublic, 그리고 이를 임베드하는 모든 Entity에 연쇄 영향을 미칩니다.

권장 패턴#

고영향 Entity에 필드를 추가하는 대신, 기능 범위 Entity를 생성하세요: 새 필드가 필요한 엔드포인트에서만 사용되는 새로운 목적 중심 Entity 클래스입니다.

가장 간단한 접근 방법은 기반 Entity를 상속받아 필요한 필드를 추가하는 새 Entity를 생성하는 것입니다:

# bad - adds :notification_email to every endpoint using UserBasic (212 endpoints)
module API
  module Entities
    class UserBasic < UserSafe
      expose :state
      expose :avatar_url
      expose :web_url
      expose :notification_email  # <-- new field inflates 212 endpoint responses
    end
  end
end

# good - create a domain-scoped entity used only by the endpoints that need it
module API
  module Entities
    module Ci
      class JobOwner < UserBasic
        expose :notification_email, documentation: { type: 'String', example: 'user@example.com' }
      end
    end
  end
end

Entity 이름은 필드 내용(예: UserWithNotificationEmail)이 아닌 도메인 컨텍스트에서 무엇을 나타내는지(예: Ci::JobOwner)에 따라 지정하세요. UserWithNotificationEmail과 같은 이름은 관련 없는 도메인에서 재사용을 유도하여 연쇄 문제를 다시 만들게 됩니다. 도메인 범위 이름은 Entity를 단일 사용 사례에 집중시킵니다.

그런 다음 해당 Entity가 필요한 엔드포인트에서만 새 Entity를 사용하세요:

# In your API endpoint file
desc 'List CI job owners' do
  detail 'Returns the owners of CI jobs with notification details.'
  success Entities::Ci::JobOwner
  tags ['ci']
end
get ':id/ci/job_owners' do
  owners = find_job_owners(params[:id])
  present owners, with: Entities::Ci::JobOwner
end

허용 목록 업데이트#

api_entity_exposure_baseline.yml의 허용 목록은 각 보호된 Entity에 대해 허용된 필드 이름을 기록합니다. 새 필드를 추가하기 위해 허용 목록을 수동으로 편집하지 마세요; 대신 위에서 설명한 대로 기능 범위 Entity를 생성하세요.

필드가 실제로 고영향 Entity에 속한다고 생각되는 경우(예를 들어, 대다수의 소비자에게 필요한 경우), 진행하기 전에 트레이드오프를 평가하기 위해 API Platform 팀과 토의를 여세요.

문서#

새로운 또는 업데이트된 각 API 엔드포인트에는 문서가 포함되어야 합니다. 문서는 동일한 머지 리퀘스트에 있어야 하며, 꼭 필요한 경우에만 원본 머지 리퀘스트와 동일한 마일스톤의 후속 머지 리퀘스트에 포함될 수 있습니다.

Markdown 및 OpenAPI 정의 파일에서 API 리소스를 문서화하는 방법에 대한 자세한 내용은 문서 스타일 가이드 RESTful API 페이지를 참조하세요.

메서드 및 파라미터 설명#

모든 메서드는 Grape DSL을 사용하여 설명해야 합니다 (좋은 예시는 environments.rb를 참조하세요):

  • 메서드 요약을 위한 desc. 120자를 초과하지 않는 요약 문자열을 포함해야 합니다.

  • desc 블록을 위한 detail. 문자열이어야 합니다.

  • desc 블록을 위한 success. 성공 응답을 정의합니다.

  • desc 블록을 위한 tags. 문자열 또는 문자열 배열이어야 합니다.

  • 메서드 파라미터를 위한 params. 파라미터의 설명, 유효성 검사 및 강제 변환 역할을 합니다.

좋은 예시는 다음과 같습니다:

desc 'Get all broadcast messages' do
  detail 'This feature was introduced in GitLab 8.12.'
  success Entities::System::BroadcastMessage
  tags ['broadcast_messages']
end
params do
  optional :page,     type: Integer, desc: 'Current page number'
  optional :per_page, type: Integer, desc: 'Number of messages per page'
end
get do
  messages = System::BroadcastMessage.all

  present paginate(messages), with: Entities::System::BroadcastMessage
end

엔드포인트 desc 정의#

모든 엔드포인트는 desc 블록에 요약 문자열을 포함해야 합니다. 요약은 REST 리소스에 대한 작업을 설명하며 생성된 OpenAPI 문서에 사용됩니다.

요약은 다음을 권장합니다:

  • HTTP 메서드에 맞는 동사로 시작 (Get, List, Create, Update, Delete)

  • 작업 대상 리소스 식별

  • 유사한 엔드포인트를 구분하기 위한 한정어 포함(필요한 경우)

요약은 다음을 반드시 해야 합니다:

  • 문자열 리터럴 또는 보간된 문자열이어야 함(변수나 메서드 호출 불가)

  • 120자를 초과하지 않아야 함

좋은 예시는 다음과 같습니다:

  desc 'Get a specific environment' do
    detail 'Returns environment details. This feature was introduced in GitLab 18.12.'
    success Entities::Environment
  end

나쁜 예시는 다음과 같습니다:

  desc 'Get a specific environment. Returns environment details. This feature was introduced in GitLab 18.12.' do
    detail 'Only available to authenticated project owner.'
    success Entities::Environment
  end

엔드포인트 details 정의#

모든 엔드포인트는 각 desc 블록에 detail 값을 가져야 합니다. 값은 문자열이어야 합니다. detaildesc에서 다루지 않은 추가 세부 사항을 설명해야 합니다:

  • 엔드포인트가 추가된 GitLab 버전.

  • 기능 플래그 뒤에 있는 경우, 대신 다음과 같이 언급하세요: This feature is gated by the :feature\_flag\_symbol feature flag.

  • 엔드포인트가 더 이상 사용되지 않는 경우 및 계획된 제거 날짜.

detail 또는 desc 요약 문자열에 "experiment", "experimental", "general availability", "GA", "beta"와 같은 라이프사이클 용어를 포함하지 마세요. 대신 route_setting :lifecycle을 사용하세요. 자세한 내용은 엔드포인트 라이프사이클 표시를 참조하세요.

엔드포인트 success 정의#

모든 엔드포인트는 각 desc 블록에 success 값을 가져야 합니다. 값은 엔드포인트의 성공 응답을 정확하게 설명해야 합니다.

성공 응답을 문서화하기 위해 http_codes 옵션을 사용하지 마세요.

success 옵션은 다음 중 하나를 허용합니다:

  • Grape::Entity 클래스를 직접

  • 옵션 해시

해시 형식을 사용할 때 다음 옵션을 사용할 수 있습니다:

옵션 타입 필수 여부 설명
code Integer 아니요 HTTP 상태 코드. 필수는 아니지만 항상 의도한 응답 코드를 지정하세요.
model Entities::* JSON 응답에 필수 응답 본문에 반환되는 Grape::Entity 클래스. 모델이 없으면 OpenAPI 스펙에서 응답 스키마나 예시가 생성되지 않습니다. 204 No Content나 리다이렉트와 같이 본문이 없는 응답에만 생략하세요.
message String 아니요 응답에 대한 짧은 설명.
is_array Boolean 아니요 응답이 모델의 배열인 경우 true로 설정. 해시 형식에서만 필요합니다. 배열로 Entity 클래스를 감싸는 것(success [Entities::MyEntity])과 동일합니다.
example Hash 아니요 응답 본문의 단일 인라인 예시. examples와 상호 배타적. model이 필요합니다.
examples Hash 아니요 응답 본문의 명명된 예시. example과 상호 배타적. model이 필요합니다.

엔드포인트가 반환하는 내용에 따라 success 값을 형식화하세요:

엔드포인트가 객체로 응답하는 경우, Grape::Entity 클래스를 직접 전달하거나 model: 옵션을 사용하세요:

# Direct form
success Entities::System::BroadcastMessage

# Hash form
success code: 200, model: Entities::System::BroadcastMessage

엔드포인트가 컬렉션으로 응답하는 경우, 배열로 Entity 클래스를 감싸거나 해시 형식에서 is_array: true를 사용하세요. 둘 다 동일합니다:

# Direct form
success [Entities::System::BroadcastMessage]

# Hash form — use when you also need to specify other options
success code: 200, model: Entities::System::BroadcastMessage, is_array: true

엔드포인트가 객체로 응답하지 않는 경우, 상태 코드와 메시지를 포함하세요:

success code: 204, message: 'Record was deleted'

엔드포인트가 여러 가능한 성공 코드를 반환하는 경우, 배열을 전달하세요:

success [
  { code: 200, model: Entities::Security::VulnerabilityScanning::SbomScan },
  { code: 202, message: 'Scan in progress' }
]

example: 또는 examples:가 제공되지 않고 model:이 정의된 경우, 예시가 자동으로 생성됩니다. Entity 필드의 documentation: { example: ... } 값에서 생성되거나, 필드 수준 예시가 정의되지 않은 경우 필드 타입에서 생성됩니다.

엔드포인트가 객체로 응답하고 전체 응답 본문을 보여주거나 여러 가능한 응답 본문 예시를 제공하려는 경우, 단일 인라인 값에는 example:, 여러 명명된 시나리오에는 examples:를 사용하세요. 둘 다 model:을 필요로 하며 상호 배타적입니다:

# Single example
success code: 200, model: Entities::System::BroadcastMessage,
        example: {
          id: 1,
          message: 'Scheduled maintenance at 23:00',
          starts_at: '2024-03-01T23:00:00.000Z',
          ends_at: '2024-03-02T01:00:00.000Z',
          active: false
        }

# Multiple named examples
success code: 200, model: Entities::System::BroadcastMessage,
        examples: {
          active_message: {
            summary: 'An active broadcast message',
            value: {
              id: 1,
              message: 'Scheduled maintenance at 23:00',
              starts_at: '2024-03-01T23:00:00.000Z',
              ends_at: '2024-03-02T01:00:00.000Z',
              active: true
            }
          },
          expired_message: {
            summary: 'An expired broadcast message',
            value: {
              id: 2,
              message: 'Maintenance complete',
              starts_at: '2024-03-01T23:00:00.000Z',
              ends_at: '2024-03-02T01:00:00.000Z',
              active: false
            }
          }
        }

엔드포인트를 더 이상 사용 안 함으로 표시#

엔드포인트를 더 이상 사용하지 않을 때, desc 블록에 다음을 추가하세요:

  • deprecated true 옵션을 추가하세요. 이렇게 하면 작업에 표준 OpenAPI deprecated: true 플래그가 설정됩니다.

  • 더 이상 사용하지 않는 시점과 마이그레이션 안내를 detail 옵션에 추가하세요.

더 이상 사용하지 않는 엔드포인트에는 route_setting :lifecycle을 사용하지 마세요. 실험 및 베타 Stage와 달리, 더 이상 사용하지 않음은 deprecated 필드를 통해 OpenAPI 사양에서 기본적으로 지원되며, deprecated true가 이에 직접 매핑됩니다.

desc 'Get legacy broadcast messages' do
  detail 'Deprecated in GitLab 17.0. Use /api/v4/broadcast_messages instead.'
  deprecated true
  success Entities::System::BroadcastMessage
  tags ['broadcast_messages']
end

이를 통해 OpenAPI 사양에서 더 이상 사용하지 않음을 프로그래밍 방식으로 검색할 수 있게 됩니다.

엔드포인트 라이프사이클 표시#

엔드포인트가 아직 일반적으로 사용 가능하지 않을 때, route_setting :lifecycle을 사용하여 개발 Stage를 표시하세요. 유효한 값은 :experiment:beta입니다.

desc 요약이나 detail 문자열에 라이프사이클 정보를 넣지 마세요. API/LifecycleInDescription RuboCop cop이 이 규칙을 시행합니다.

일반적으로 사용 가능한 엔드포인트에는 route_setting :lifecycle을 생략하세요.

# bad -- Specifies "experimental" in "detail"
desc 'Get all widgets' do
  detail 'This feature is experimental.'
  tags %w[widgets]
end

# good -- Specifies "experiment" as route_setting
route_setting :lifecycle, :experiment
desc 'Get all widgets' do
  detail 'Introduced in GitLab 18.10.'
  tags %w[widgets]
end

# good -- Specifies "beta" as route_setting
route_setting :lifecycle, :beta
desc 'Get all widgets' do
  detail 'Introduced in GitLab 18.10.'
  tags %w[widgets]
end

route_setting :lifecycle 값은 x-gitlab-lifecycle 벤더 확장으로서 생성된 OpenAPI 사양에 포함됩니다. 이를 통해 라이프사이클 상태를 프로그래밍 방식으로 검색할 수 있게 됩니다.

개발 Stage에 대한 자세한 내용은 개발 Stage 및 지원을 참조하세요.

태그 선택#

모든 엔드포인트는 desc 블록마다 tags에 최소한 하나의 값이 정의되어야 합니다. 태그는 API 호출에서 작업 대상 객체 유형을 복수형으로 설명해야 합니다.

대부분의 경우, API의 파일명이 충분하지만 너무 세분화될 수도 있습니다.

좋은 태그 이름#

  • audit_events

  • users

  • clusters

나쁜 태그 이름#

  • commit (단수형)

  • epic_management (Entity가 아닌 제품 카테고리와 결합됨)

태그의 올바른 이름이 명확하지 않은 경우, 기술 문서 작성자에게 안내를 요청하세요.

String 파라미터 제한#

클라이언트가 무제한 페이로드를 전송할 수 없도록 String 파라미터를 제한하세요. 무제한 문자열은 호출자가 서버 메모리와 처리 시간을 소비하는 대용량 요청을 제출할 수 있게 하며, 남용의 표면적을 넓힙니다.

가능한 경우, 모든 String 파라미터에 다음 유효성 검사기 중 하나를 사용하세요:

  • 파라미터가 고정된 허용 값 집합을 허용하는 경우 values:.

  • 자유 형식 문자열의 최대 문자 수를 제한하기 위한 limit:.

  • 파라미터가 특정 형식과 일치해야 하는 경우 regexp:.

params do
  optional :state, type: String, values: %w[opened closed], desc: 'Filter by state'
  optional :name, type: String, limit: 255, desc: 'Name of the resource'
end

limit: 유효성 검사기는 API::Validations::Validators::Limit에 구현되어 있으며 구성된 크기보다 긴 값을 거부합니다.

브레이킹 체인지#

REST API v4에 브레이킹 체인지를 하면 안 됩니다. 주요 GitLab 릴리즈에서도 마찬가지입니다. 브레이킹 체인지란 무엇인가브레이킹 체인지가 아닌 것은 무엇인가를 참조하세요.

REST API는 GitLab 버전 관리와 독립적으로 자체 버전 관리를 유지합니다. 현재 REST API 버전은 4입니다. REST API에 대해 시맨틱 버전 관리를 따르기로 약속했기 때문에 브레이킹 체인지를 할 수 없습니다. REST API의 주요 버전 변경(가장 가능성 높은 경우 5)은 현재 계획되거나 예정되지 않았습니다.

예외는 실험 또는 베타로 표시된 API 기능입니다. 이러한 기능은 언제든지 제거되거나 변경될 수 있습니다.

브레이킹 체인지 대신 할 수 있는 것#

다음 섹션에서는 브레이킹 체인지를 하는 대신의 대안을 제안합니다.

브레이킹 없이 스키마를 변경에 맞게 조정#

기능이 변경되면, API에 브레이킹 체인지 없이 하위 호환성을 수용하는 것을 목표로 해야 합니다.

브레이킹 체인지를 도입하는 대신, API 컨트롤러 레이어를 변경하여 API 소비자에게 어떤 변경도 표시하지 않는 방식으로 기능 변경에 적응하세요.

예를 들어, 머지 리퀘스트의 WIP 기능을 Draft로 이름을 변경했습니다. 변경을 달성하기 위해:

  • API 응답에 새 draft 필드를 추가했습니다.

  • 또한 이전 work_in_progress 필드도 유지했습니다.

고객은 기존 API 통합에 어떤 혼란도 겪지 않았습니다.

기능 제거 시 해야 할 것#

엔드포인트가 인터페이스했던 기능이 주요 GitLab 버전에서 제거됨에 따라, API 하위 호환성 유지와 사용자가 신뢰할 수 있는 결과 반환 간의 균형을 유지해야 합니다.

컨텍스트에 따라 적절한 접근 방식을 선택하세요:

조용한 성능 저하 - 오류가 더 넓은 기능을 방해할 때 사용:

  • 필드에서 합리적인 정적 값이나 빈 응답(예: null 또는 [])을 반환하세요.

  • 인수를 no-op으로 전환하여 계속 인수를 허용하지만 더 이상 작동하지 않도록 하세요.

  • 하나의 설정을 제거해도 전체 엔드포인트가 실패하지 않아야 하는 Application Settings와 같은 엔드포인트에 가장 적합합니다.

오류 응답 - 기능이 완전히 제거된 경우 사용:

  • 제거된 기능이 엔드포인트의 주요 목적이었을 때 404 Not Found를 반환하세요.

  • 이는 기능이 더 이상 존재하지 않음을 사용자에게 명확하게 알립니다.

핵심 원칙은 기존 고객 API 통합이 가능한 경우 우아하게 성능을 저하시키고, 기능이 더 이상 사용 불가능할 때 명확한 피드백을 제공해야 한다는 것입니다. 엔드포인트는 동일한 필드로 계속 응답하고 동일한 인수를 허용하지만, 기본 기능 상호작용은 더 이상 작동하지 않습니다.

의도한 변경 사항은 미리 문서화되어야 합니다. v4 더 이상 사용 안 함 가이드를 따르세요.

예를 들어, 애플리케이션 설정을 제거했을 때, 합리적인 정적 값을 반환하는 이전 API 필드를 유지했습니다.

브레이킹 체인지란 무엇인가#

브레이킹 체인지의 예시는 다음과 같습니다:

  • 필드, 인수 또는 열거형 값 제거 또는 이름 변경. JSON 응답에서 필드는 모든 JSON 키입니다.

  • 엔드포인트 제거.

  • 새로운 리다이렉트 추가 (모든 클라이언트가 리다이렉트를 따르는 것은 아닙니다).

  • 응답의 콘텐츠 타입 변경.

  • 응답에서 필드의 타입 변경. JSON 응답에서 이는 Number, String, Boolean, Array 또는 Object 타입이 다른 타입으로 변경되는 것입니다.

  • 새로운 필수 인수 추가.

  • 인증, 인가 또는 기타 헤더 요구 사항 변경.

  • 500 이외의 임의의 상태 코드 변경.

브레이킹 체인지가 아닌 것은 무엇인가#

비-브레이킹 체인지의 예시는 다음과 같습니다:

  • 엔드포인트, 비필수 인수, 필드 또는 열거형 값 추가와 같은 모든 추가적 변경.

  • 오류 메시지 변경.

  • 500 상태 코드에서 지원되는 임의의 상태 코드로의 변경 (이는 버그 수정입니다).

  • 응답에서 반환되는 필드의 순서 변경.

실험, 베타, 일반적으로 사용 가능한 기능#

API 요소를 실험 및 베타 기능으로 추가할 수 있습니다. 이는 추가적 변경이어야 하며, 그렇지 않으면 브레이킹 체인지로 분류됩니다.

실험 또는 베타로 표시된 API 요소는 브레이킹 체인지 정책에서 면제되며, 사전 통보 없이 언제든지 변경되거나 제거될 수 있습니다.

실험 상태에 있는 동안:

추가된 엔드포인트는 404 Not Found를 반환해야 합니다.

베타 상태에 있는 동안:

기능이 일반적으로 사용 가능해지면:

선언된 파라미터#

Grape는 params 블록으로 선언된 파라미터에만 접근할 수 있게 합니다. 전달되었지만 허용되지 않은 파라미터를 필터링합니다. 자세한 내용은 Ruby Grape의 declared()에 대한 문서를 참조하세요.

부모 네임스페이스에서 파라미터 제외#

기본적으로 declared(params)는 모든 부모 네임스페이스에서 정의된 파라미터를 포함합니다. 자세한 내용은 Ruby Grape의 include_parent_namespaces에 대한 문서를 참조하세요.

대부분의 경우 부모 네임스페이스에서 파라미터를 제외해야 합니다:

declared(params, include_parent_namespaces: false)

declared(params) 사용 시기#

파라미터 해시를 메서드 호출의 인수로 전달할 때는 항상 declared(params)를 사용해야 합니다.

예를 들어:

# bad
User.create(params) # imagine the user submitted `admin=1`... :)

# good
User.create(declared(params, include_parent_namespaces: false).to_h)

declared(params)Hashie::Mash 객체를 반환하며, 이 객체에는 반드시 .to_h를 호출해야 합니다.

하지만 단일 요소에 접근할 때는 params[key]를 직접 사용할 수 있습니다.

예를 들어:

# good
Model.create(foo: params[:foo])

Array 타입#

Grape v1.3+에서 Array 타입은 coerce_with 블록으로 정의되어야 하며, 그렇지 않으면 API 요청에서 문자열이 전달될 때 파라미터 유효성 검사에 실패합니다. 자세한 내용은 Grape 업그레이드 문서를 참조하세요.

nil 입력의 자동 강제 변환#

Grape v1.3.3 이전에는 nil 값을 가진 Array 파라미터가 자동으로 빈 Array로 강제 변환되었습니다. 그러나 v1.3.3의 이 풀 리퀘스트로 인해 이제는 더 이상 그렇지 않습니다. 예를 들어, 선택적 파라미터가 있는 PUT /test 요청을 정의한다고 가정해 보겠습니다:

optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The user ids for this rule'

일반적으로 PUT /test?user_ids에 대한 요청은 Grape가 { user_ids: nil }params를 전달하게 합니다.

이는 빈 배열을 예상하는 엔드포인트에서 nil 입력을 제대로 처리하지 못하면 오류를 유발할 수 있습니다. 이전 동작을 유지하기 위해, 모든 API 호출의 before 블록에서 사용되는 coerce_nil_params_to_array! 헬퍼 메서드가 있습니다:

before do
  coerce_nil_params_to_array!
end

이 변경으로 PUT /test?user_ids에 대한 요청은 Grape가 params{ user_ids: [] }로 전달하게 합니다.

이를 더 쉽게 만들기 위한 Grape 트래커의 열린 이슈가 있습니다.

Workhorse 보조 업로드#

파일 콘텐츠를 허용하는 모든 REST API 엔드포인트는 Workhorse 보조 업로드를 사용해야 합니다.

구현 세부 사항은 Workhorse 업로드 문서를 참조하세요.

HTTP 상태 헬퍼 사용#

200이 아닌 HTTP 응답의 경우, lib/api/helpers.rb에 제공된 헬퍼(not_found! 또는 no_content! 등)를 사용하여 올바른 동작을 보장하세요. 이러한 헬퍼는 Grape 내부에서 throw를 호출하여 엔드포인트 실행을 중단합니다.

DELETE 요청의 경우, 일반적으로 destroy_conditionally! 헬퍼를 사용해야 하며, 이 헬퍼는 기본적으로 성공 시 204 No Content 응답을 반환하거나, 주어진 If-Unmodified-Since 헤더가 범위를 벗어난 경우 412 Precondition Failed 응답을 반환합니다. 이 헬퍼는 전달된 리소스에서 #destroy를 호출하지만, 블록을 전달하여 커스텀 삭제 메서드를 구현할 수도 있습니다.

HTTP 동사 선택#

새로운 API 라우트를 정의할 때, 올바른 HTTP 요청 메서드를 사용하세요.

PATCH와 PUT 간의 결정#

Rails 애플리케이션에서 PATCHPUT 요청 메서드는 모두 컨트롤러의 update 메서드로 라우팅됩니다. GitLab API를 작성하는 데 사용하는 프레임워크인 Grape에서는 업데이트를 수행하는 엔드포인트에 대해 PATCH 또는 PUT HTTP 동사를 명시적으로 설정해야 합니다.

엔드포인트가 주어진 리소스의 모든 속성을 업데이트하는 경우, PUT 요청 메서드를 사용하세요. 엔드포인트가 주어진 리소스의 일부 속성을 업데이트하는 경우, PATCH 요청 메서드를 사용하세요.

PATCH의 좋은 예시: PATCH /projects/:id/protected_branches/:name PUT의 좋은 예시: PUT /projects/:id/merge_requests/:merge_request_iid/approve

종종 좋은 PUT 엔드포인트는 ID와 동사만 있습니다(위의 예시에서 "approve"). 또는 단일 값만 있고 키/값 쌍을 나타냅니다.

Rails 블로그에는 PATCH가 업데이트를 수행하는 웹 API 엔드포인트에 일반적으로 가장 적합한 동사인 이유에 대한 자세한 설명이 있습니다.

GitLab Rails 코드베이스에서 API 경로 헬퍼 사용#

상대 URL 아래 GitLab 설치를 지원하기 때문에, Grape로 생성된 API 경로 헬퍼를 사용할 때 이를 고려해야 합니다. 그러한 API 경로 헬퍼 사용은 expose_path 헬퍼 호출로 감싸져야 합니다.

예를 들어:

- endpoint = expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: @issue.iid))

커스텀 유효성 검사기#

API 요청에서 일부 파라미터를 유효성 검사하기 위해, 추가로 전달하기 전에 (예: Gitaly) 유효성 검사를 수행합니다. 다음은 지금까지 추가한 커스텀 유효성 검사기와 사용 방법입니다. 또한 새 커스텀 유효성 검사기를 추가하는 방법에 대한 가이드도 작성했습니다.

커스텀 유효성 검사기 사용#

FilePath:

GitLab은 파일 경로를 탐색해야 하는 다양한 기능을 지원합니다. FilePath 유효성 검사기는 다양한 경우에 대해 파라미터 값을 유효성 검사합니다. 주로 경로가 상대 경로인지, File::Separator를 사용하는 ../../ 상대 탐색을 포함하는지, 그리고 /etc/passwd/와 같이 경로가 절대 경로인지 확인합니다. 기본적으로 절대 경로는 허용되지 않습니다. 그러나 다음과 같은 방법으로 선택적으로 허용된 절대 경로의 허용 목록을 전달할 수 있습니다: requires :file_path, type: String, file_path: { allowlist: ['/foo/bar/', '/home/foo/', '/app/home'] }

Git SHA:

Git SHA 유효성 검사기는 Git SHA 파라미터가 유효한 SHA인지 확인합니다. commit.rb 파일에 언급된 정규식을 사용하여 확인합니다.

Absence:

Absence 유효성 검사기는 주어진 파라미터 해시에서 특정 파라미터가 없는지 확인합니다.

IntegerNoneAny:

IntegerNoneAny 유효성 검사기는 주어진 파라미터의 값이 Integer, None 또는 Any인지 확인합니다. 요청을 계속하려면 언급된 값 중 하나만 허용합니다.

ArrayNoneAny:

ArrayNoneAny 유효성 검사기는 주어진 파라미터의 값이 Array, None 또는 Any인지 확인합니다. 요청을 계속하려면 언급된 값 중 하나만 허용합니다.

EmailOrEmailList:

EmailOrEmailList 유효성 검사기는 문자열이나 문자열 목록의 값에 유효한 이메일 주소만 포함되는지 확인합니다. 모든 유효한 이메일 주소가 있는 목록만 요청을 계속하도록 허용합니다.

새 커스텀 유효성 검사기 추가#

커스텀 유효성 검사기는 파라미터를 플랫폼으로 추가 처리를 위해 전송하기 전에 유효성을 검사하는 좋은 방법입니다. 처음부터 잘못된 파라미터를 식별하면 서버와 플랫폼 사이의 왕복을 줄일 수 있습니다.

커스텀 유효성 검사기를 추가해야 한다면, validators 디렉터리에 자체 파일로 추가됩니다. API를 추가하기 위해 Grape를 사용하기 때문에 유효성 검사기 클래스에서 Grape::Validations::Validators::Base 클래스를 상속합니다. 이제 params 해시와 유효성 검사할 param 이름이라는 두 파라미터를 받는 validate_param! 메서드만 정의하면 됩니다.

메서드의 본문은 파라미터 값의 유효성을 검사하는 작업을 수행하고 호출 메서드에 적절한 오류 메시지를 반환합니다.

마지막으로, 아래 줄을 사용하여 유효성 검사기를 등록합니다:

Grape::Validations.register_validator(<validator name as symbol>, ::API::Helpers::CustomValidators::)

유효성 검사기를 추가한 후에는 validators 디렉터리의 자체 파일에 rspec을 추가해야 합니다.

내부 API#

내부 API는 내부 사용을 위해 문서화되어 있습니다. 다른 구성 요소가 어떤 엔드포인트를 사용하는지 알 수 있도록 최신 상태로 유지하세요.

N+1 문제 방지#

API 엔드포인트에서 레코드 컬렉션을 반환할 때 일반적으로 발생하는 N+1 문제를 방지하기 위해 즉시 로딩을 사용해야 합니다.

API 내에서 이를 수행하는 표준 방법은 모델이 API에서 반환된 연관 관계와 데이터를 미리 로드하는 with_api_entity_associations 스코프를 구현하는 것입니다. 이 스코프의 예시는 Issue 모델에서 볼 수 있습니다.

API에서 동일한 모델이 여러 Entity를 가지는 상황(예: UserBasic, User, UserPublic)에서는 이 스코프를 적용할 때 재량을 사용해야 합니다. 가장 기본적인 Entity에 최적화하고, 그 다음 Entity들이 해당 스코프를 기반으로 구축할 수 있습니다.

with_api_entity_associations 스코프는 to-dos API에서 반환될 때 Todo 타깃에 대해 자동으로 데이터를 미리 로드합니다.

미리 로딩에 대한 더 많은 컨텍스트와 토론은 스코프를 도입한 이 머지 리퀘스트를 참조하세요.

테스트로 검증#

API 엔드포인트가 컬렉션을 반환할 때, 지금과 미래에 API 엔드포인트에 N+1 문제가 없음을 검증하는 테스트를 항상 추가하세요. ActiveRecord::QueryRecorder를 사용하여 이를 수행할 수 있습니다.

예시:

def make_api_request
  get api('/foo', personal_access_token: pat)
end

it 'avoids N+1 queries', :request_store do
  # Firstly, record how many PostgreSQL queries the endpoint will make
  # when it returns a single record
  create_record

  control = ActiveRecord::QueryRecorder.new { make_api_request }

  # Now create a second record and ensure that the API does not execute
  # any more queries than before
  create_record

  expect { make_api_request }.not_to exceed_query_limit(control)
end

테스트#

새 API 엔드포인트에 대한 테스트를 작성할 때, /spec/fixtures/api/schemas에 위치한 스키마 픽스처를 사용하는 것을 고려하세요. 응답이 주어진 스키마와 일치하는지 expect할 수 있습니다:

expect(response).to match_response_schema('merge_requests')

또한 테스트에서 N+1 성능 검증도 참조하세요.

체인지로그 항목 포함#

모든 클라이언트 대면 변경 사항은 반드시 체인지로그 항목을 포함해야 합니다. 내부 API는 해당하지 않습니다.