API 스타일 가이드

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

GraphQL 및 REST API

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

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

프론트엔드

프론트엔드에서 개발할 때 사용할 API에 대한 자세한 정보는 프론트엔드 가이드를 참조하십시오.

인스턴스 변수

인스턴스 변수를 사용하지 마십시오. Rails 뷰에서 하는 것처럼 액세스할 필요가 없으므로 로컬 변수를 사용하세요.

엔터티

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

문서

새로운 또는 업데이트된 API 엔드포인트는 내부적이거나 기능 플래그 뒤에 숨겨진 경우를 제외하고 문서와 함께 제공되어야 합니다. 문서는 동일한 머지 리퀘스트에 있어야 하며, 엄격하게 필요한 경우에는 원래의 머지 리퀘스트와 동일한 마일스톤으로 이후에 제공되어야 합니다.

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

메소드 및 매개변수 설명

모든 메소드는 Grape DSL을 사용하여 설명되어야 합니다. (좋은 예는 environments.rb를 참조하십시오):

  • 메소드 요약에 대한 desc. 추가 정보를 위해 블록을 전달해야 합니다. 예를 들어:
    • 엔드포인트가 추가된 GitLab 버전. 기능 플래그 뒤에 숨겨진 경우 대신 언급하세요: 이 기능은 :feature_flag_symbol 기능 플래그에 의해 제한됩니다.
    • 엔드포인트가 폐기될 경우 해당 폐기 예정일
  • 메소드 매개변수에 대한 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

호환성 파괴적인 변경

우리는 GitLab 주요 릴리스에서 신뢰할 수 있는 REST API v4에 대한 호환성 파괴적인 변경을 가해서는 안 됩니다.

우리의 REST API는 GitLab 버전과 독립적으로 버전을 유지합니다. 현재 REST API 버전은 4입니다. REST API에 대한 의미 있는 버전 설정을 따르기로 했습니다, 즉 주요 버전 변경(아마도 5가 가장 가능성이 높음)이 발생할 때까지 파괴적인 변경을 가할 수 없습니다.

버전 5가 예정되지 않았기 때문에 드문 예외을 허용합니다.

호환성 파괴적인 변경 대신의 백워드 호환성 수용

변경된 기능을 이전 API 스키마에 계속적으로 적응하여 대부분의 경우에 백워드 호환성을 유지할 수 있습니다. 예를 들어, 우리의 REST API는 work_in_progressdraft 필드를 모두 노출합니다.

예외

예외 사항은 다음과 같습니다:

이 예외 사항은 드문 경우여야 합니다.

이 예외 사항에서 실현된 기능을 제거하는 대신 언제나 다음과 같이 수행해야 합니다:

  • 필드나 인수에 대한 빈 응답 반환(예: "null" 또는 []).
  • 인수를 번역하지 마십시오.

파괴적 변경이란

파괴적 변경의 몇 가지 예는 다음과 같습니다:

  • 필드, 매개변수 또는 열거형 값의 제거 또는 이름 변경. 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에서의 이 PR로 인해 더는 해당하지 않습니다. 예를 들어, 선택적 매개변수를 가진 PUT /test 요청을 정의했다고 가정해봅시다.

일반적으로 PUT /test?user_ids로의 요청은 Grape에서 params{ user_ids: nil }로 전달합니다.

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

이 변경으로 PUT /test?user_ids 요청은 Grape에서 params{ user_ids: [] }로 전달됩니다.

이에 대한 더 간단한 사용을 위한 Grape 트래커에서의 오픈 이슈가 있습니다.

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 요청 방법을 사용해야 합니다.

PATCHPUT 중에서 선택하기

Rails 애플리케이션에서 PATCHPUT 요청 방법은 모두 컨트롤러의 update 메소드로 라우팅됩니다. 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와 동사만 가지고 있거나, 단일 값을 가지고 있어서 키/값 쌍을 나타내야 합니다.

Rails 블로그에는 웹 API 엔드포인트에서 PATCH가 일반적으로 가장 적합한 동사인 이유에 대해 자세히 설명되어 있습니다.

GitLab Rails 코드베이스에서 API 경로 도우미 사용하기

Graped에 의해 생성된 API 경로 도우미를 사용할 때 상대적인 URL 아래에 GitLab을 설치하기 때문에 이를 고려해야 합니다. 이러한 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 파일에 언급된 정규식을 사용하여 확인합니다.

  • 부재:

    부재 유효성 검사기 는 지정된 매개변수 해시에서 특정 매개변수가 없는지 확인합니다.

  • IntegerNoneAny:

    IntegerNoneAny 유효성 검사기 는 주어진 매개변수의 값이 정수, 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::<YourCustomValidatorClassName>)

유효성 검사기를 추가한 후 해당 유효성 검사기에 대한 rspec를 검사기의 별도 파일에 추가하는 것을 잊지 마십시오. 이 파일은 validators 디렉토리에 있어야 합니다.

내부 API

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

N+1 문제 회피

API 엔드포인트에서 레코드 모음을 반환할 때 흔히 발생하는 N+1 문제를 피하기 위해 이전 로딩을 사용해야 합니다.

API 내에서 이를 수행하는 표준 방법은 모델이 API에서 반환된 연결 및 데이터를 사전로드하는 with_api_entity_associations라는 스코프를 구현하는 것입니다. 이러한 스코프의 예는 Issue 모델에서 확인할 수 있습니다.

동일한 모델이 API에서 여러 개체를 가지는 상황(예: UserBasic, User, UserPublic)에서 이 스코프를 적용할지에 대해서는 재량으로 결정해야 합니다. 연속적인 엔티티가 해당 스코프에 따라 최적화되었을 수 있습니다.

with_api_entity_associations 스코프는 데이터를 자동으로 사전로딩 하여 할 일 API에서 반환될 때 할 일 대상을 위해 실행됩니다.

사전로드에 대한 자세한 내용 및 토론은 이 병합 요청에서 확인할 수 있습니다.

테스트로 검증

API 엔드포인트가 컬렉션을 반환할 때 항상 N+1 문제가 발생하지 않는지 현재와 미래에 대해 확인하기 위해 테스트를 추가하세요. ActiveRecord::QueryRecorder를 사용하여 이 작업을 수행할 수 있습니다.

예시:

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

it 'N+1 쿼리를 회피합니다', :request_store do
  # 먼저, API가 단일 레코드를 반환할 때 PostgreSQL 쿼리를 얼마나 실행하는지 기록합니다
  control = ActiveRecord::QueryRecorder.new { make_api_request }

  # 이제 두 번째 레코드를 만들고, API가 이전보다 더 많은 쿼리를 실행하지 않도록 확인합니다
  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')

또한 테스트에서 테스트로 확인하기도 바랍니다.

변경 로그 항목 포함

모든 고객에게 노출되는 변경 사항은 반드시 변경 로그 항목을 포함해야 합니다. 내부 API는 이에 포함되지 않습니다.