API 스타일 가이드

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

GraphQL 및 REST API

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

두 API를 병렬로 지원하는 기술적 부담을 줄이기 위해, 가능한 한 구현을 공유해야 합니다.

예를 들어, 같은 서비스를 공유할 수 있습니다.

프론트엔드

프론트엔드 개발 시 어떤 API를 사용할지에 대한 세부 정보는 프론트엔드 가이드를 참조하세요.

인스턴스 변수

인스턴스 변수를 사용하지 마세요. 필요가 없습니다 (Rails 뷰에서처럼 접근할 필요가 없습니다). 로컬 변수면 충분합니다.

엔티티

항상 엔드포인트의 페이로드를 전달하기 위해 Entity를 사용하세요.

문서화

모든 새로운 API 엔드포인트 또는 업데이트된 API 엔드포인트는 문서와 함께 제공되어야 하며, 내부적이거나 기능 플래그 뒤에 있지 않은 경우에 해당합니다.

문서는 같은 병합 요청에 포함되어야 하거나, 엄격하게 필요하다면 원래 병합 요청과 동일한 마일스톤의 후속 요청에서 제공되어야 합니다.

Markdown 및 OpenAPI 정의 파일에서 API 리소스를 문서화하는 세부 정보는 문서화 스타일 가이드 RESTful API 페이지를 참조하세요.

메서드 및 매개변수 설명

모든 메서드는 Grape DSL을 사용하여 설명해야 합니다.

(좋은 예시는 environments.rb에서 확인할 수 있습니다):

  • desc는 메서드 요약을 위한 것입니다. 추가 세부사항을 위한 블록을 전달해야 합니다.
    • 엔드포인트가 추가된 GitLab 버전. 기능 플래그 뒤에 있다면 대신 언급합니다: 이 기능은 :feature_flag_symbol 기능 플래그로 차단됩니다.
    • 엔드포인트가 Deprecated 되었는지 및 그렇다면 예정된 제거 날짜
  • params는 메서드 매개변수를 위한 것입니다. 이는 설명, 매개변수 검증 및 코에르전 역할을 합니다.

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

desc '모든 방송 메시지 가져오기' do
  detail '이 기능은 GitLab 8.12에서 도입되었습니다.'
  success Entities::System::BroadcastMessage
end
params do
  optional :page,     type: Integer, desc: '현재 페이지 번호'
  optional :per_page, type: Integer, desc: '페이지당 메시지 수'
end
get do
  messages = System::BroadcastMessage.all

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

중단 변경사항

REST API v4에 대한 중단 변경사항을 만들어서는 안 되며, 주요 GitLab 릴리즈에서도 마찬가지입니다.

우리의 REST API는 GitLab 버전 관리와 독립적으로 자체 버전 관리를 유지합니다.

현재 REST API 버전은 4입니다. REST API에 대해 의미론적 버전 관리를 따를 것을 약속합니다.

이는 주요 버전 변경(대부분 5)이 있기 전에는 중단 변경을 할 수 없음을 의미합니다.

버전 5가 계획되어 있지 않으므로 드문 예외를 허용합니다.

중단 변경 대신 하위 호환성 수용

종종 하위 호환성은 API에서 변경된 기능을 구형 API 스키마에 계속 적응시켜 수용할 수 있습니다.

예를 들어, 우리의 REST API는 work_in_progressdraft 필드를 모두 노출합니다.

예외

예외는 다음의 경우에만 적용됩니다:

이 예외는 드물어야 합니다.

이 예외에서도 필드나 인수를 제거하는 것보다 항상 다음과 같이 해야 합니다:

  • 필드에 대해 빈 응답을 반환합니다 (예: "null" 또는 []).
  • 인수를 no-op로 전환합니다.

중단 변경 사항이란

중단 변경 사항의 몇 가지 예는 다음과 같습니다:

  • 필드, 인수 또는 열거형 값의 제거 또는 이름 변경. JSON 응답에서 필드는 모든 JSON 키입니다.
  • 엔드포인트 제거.
  • 새로운 리디렉션 추가 (모든 클라이언트가 리디렉션을 따르지는 않음).
  • 응답의 콘텐츠 유형 변경.
  • 응답의 필드 유형 변경. JSON 응답에서 이는 Number, String, Boolean, Array 또는 Object 유형의 변경을 의미합니다.
  • 새로운 필수 인수를 추가.
  • 인증, 인가 또는 기타 헤더 요구사항 변경.
  • 아무 상태 코드 변경 500 이외.

중단 변경 사항이 아닌 것

중단 변경 사항이 아닌 몇 가지 예는 다음과 같습니다:

  • 엔드포인트, 비필수 인수, 필드 또는 열거형 값을 추가하는 모든 추가 변경 사항.
  • 오류 메시지 변경.
  • 500 상태 코드를 지원되는 상태 코드로 변경 (이것은 버그 수정입니다).
  • 응답에서 반환되는 필드의 순서 변경.

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

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

실험 또는 베타로 표시된 API 요소는 하위 호환성 보장 정책에서 면제되며,
사전 통지 없이 언제든지 변경되거나 제거될 수 있습니다.

실험 상태일 때:

베타 상태일 때:

기능이 일반적으로 사용 가능해질 때:

선언된 매개변수

Grape는 params 블록에 선언된 매개변수만 접근할 수 있습니다.

허용되지 않은 매개변수는 필터링됩니다.

https://github.com/ruby-grape/grape#declared

부모 네임스페이스에서 매개변수 제외하기

기본적으로 declared(params)는 모든 부모 네임스페이스에서 정의된 매개변수를 포함합니다.

https://github.com/ruby-grape/grape#include-parent-namespaces

대부분의 경우 부모 네임스페이스에서 매개변수를 제외해야 합니다:

declared(params, include_parent_namespaces: false)

declared(params) 사용 시기

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

예를 들어:

# 나쁜 예
User.create(params) # 사용자가 `admin=1`을 제출했다고 상상해 보세요... :)

# 좋은 예
User.create(declared(params, include_parent_namespaces: false).to_h)

참고: declared(params)Hashie::Mash 객체를 반환하며, 여기서 .to_h를 호출해야 합니다.

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

예를 들어:

# 좋은 예
Model.create(foo: params[:foo])

배열 타입

Grape v1.3+부터, 배열 타입은 coerce_with 블록으로 정의되어야 하며, 그렇지 않으면 API 요청에서 문자열이 전달될 때 유효성 검사가 실패합니다.

자세한 내용은 Grape 업그레이드 문서를 참조하세요.

nil 입력 자동 변환

Grape v1.3.3 이전에는 nil 값을 가진 배열 매개변수가 자동으로 빈 배열로 변환되었습니다.

그러나 v1.3.3에서의 이 풀 리퀘스트로 인해 더 이상 그렇지 않습니다. 예를 들어, 선택적 매개변수를 가진 PUT /test 요청을 정의한다고 가정해 보겠습니다:

optional :user_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: '이 규칙에 대한 사용자 아이디'

보통 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가 { user_ids: [] }params를 전달하게 합니다.

Grape 트래커에 열린 문제가 이 작업을 더 쉽게 만들기 위해 있습니다.

HTTP 상태 헬퍼 사용하기

200이 아닌 HTTP 응답의 경우, 올바른 동작을 보장하기 위해 lib/api/helpers.rb에 제공된 헬퍼를 사용하세요(예: not_found! 또는 no_content!).

이들은 Grape 내에서 throw되며 엔드포인트의 실행을 중단합니다.

DELETE 요청의 경우, 일반적으로 성공 시 기본적으로 204 No Content 응답을 반환하거나 주어진 If-Unmodified-Since 헤더가 범위를 벗어날 경우 412 Precondition Failed 응답을 반환하는 destroy_conditionally! 헬퍼를 사용하는 것이 좋습니다.

이 헬퍼는 전달된 리소스에 대해 #destroy를 호출하지만 블록을 전달하여 사용자 지정 삭제 방법을 구현할 수도 있습니다.

HTTP 동사 선택하기

새로운 API 경로를 정의할 때는 올바른 HTTP 요청 방법을 사용하세요.

PATCHPUT 결정하기

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 블로그에서는 업데이트를 수행하는 웹 API 엔드포인트에 대해 PATCH가 일반적으로 가장 적합한 동사인 이유를 상세히 설명합니다.

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

GitLab을 상대 URL로 설치할 수 있기 때문에, 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 클래스를 상속합니다.

이제, 당신이 해야 할 일은 validate_param! 메서드를 정의하는 것입니다. 이 메서드는 두 개의 매개변수: 검증할 param 이름과 params 해시를 받습니다.

메서드 본문은 매개변수 값을 검증하는 작업을 수행하고 호출 메서드에 적절한 오류 메시지를 반환합니다.

마지막으로, 아래와 같이 검증기를 등록합니다:

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

검증기를 추가한 후에는 validators 디렉토리에 각자의 파일로 rspec를 추가하는 것을 잊지 마세요.

내부 API

내부 API는 내부 사용을 위해 문서화되어 있습니다.

다양한 컴포넌트가 어떤 엔드포인트를 사용하고 있는지 알 수 있도록 항상 최신 상태로 유지하세요.

N+1 문제 피하기

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

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

이 스코프의 예는 Issue 모델에서 볼 수 있습니다.

같은 모델이 API에 여러 개의 엔티티(예: UserBasic, UserUserPublic)가 있을 경우, 이 스코프를 적용하는 데 있어 자 discretion를 사용해야 합니다.

가장 기본적인 엔티티에 대해 최적화하고, 후속 엔티티가 그 스코프를 기반으로 발전할 수 있습니다.

with_api_entity_associations 스코프는 자동으로 데이터를 미리 로드합니다.

todo API에서 반환될 때 Todo _targets_에 대해.

미리 로드에 대한 더 많은 맥락과 논의는 이 스코프를 도입한 이 병합 요청을 확인하세요.

테스트로 검증하기

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
  # 우선, 엔드포인트가 단일 레코드를 반환할 때 몇 개의 PostgreSQL 쿼리를 기록합니다.
  create_record

  control = ActiveRecord::QueryRecorder.new { make_api_request }

  # 이제 두 번째 레코드를 생성하고 API가 이전보다 더 많은 쿼리를 실행하지 않는지 확인합니다.
  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를 포함하지 않습니다.