API 스타일 가이드

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

인스턴스 변수

인스턴스 변수를 사용하지 마세요. (우리는 Rails 뷰에서 하는 것과 같이 접근할 필요가 없기 때문에) 지역 변수로 충분합니다.

개체

모든 엔드포인트의 페이로드를 제시하기 위해 Entity를 항상 사용하십시오.

문서

새로운 또는 업데이트된 API 엔드포인트는 내부적이거나 피처 플래그 뒤에 숨겨지지 않는 한 문서와 함께 제공되어야 합니다. 문서는 동일한 합병 요청 내에 있어야 하며, 엄격하게 필요한 경우에는 합병 요청과 동일한 마일스톤을 가진 후속 요청에 있어야 합니다.

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

메서드 및 매개변수 설명

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

  • 메서드 요약에 대한 desc를 사용하세요. 추가 세부 정보를 위해 블록을 전달해야 합니다.
    • 엔드포인트가 추가된 GitLab 버전. 피처 플래그 뒤에 숨겨진 경우에는 그 대신 언급하세요: 본 기능은 :feature_flag_symbol 피처 플래그로 제한됩니다.
    • 엔드포인트가 사용되지 않게 될 경우, 그 계획된 제거 날짜
  • 메서드 매개변수에 대한 설명, 검증 및 매개변수 변환을 위해 사용됩니다.

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

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 스키마에 계속해서 적응시켜 API에서 역호환성을 유지할 수 있습니다. 예를 들어, 우리의 REST API는 work_in_progressdraft 필드를 모두 노출합니다.

예외

이 예외는 다음 경우에만 해당됩니다:

  • 어떤 기능이 대규모 GitLab 릴리스에서 제거되어야 할 때
  • 역호환성을 유지할 수 없을 때 어떤 형태에서도.

이 예외는 희귀해야 합니다.

이 예외인 경우라도, 필드 또는 매개변수를 제거하는 대신 항상 다음을 수행해야 합니다:

  • 필드에 대해 빈 응답 반환하기 (예: "null" 또는 [])
  • 매개변수를 무작위로 변환시키기

호환성을 깨뜨리는 변경 사항이란

호환성을 깨뜨리는 변경 사항의 몇 가지 예는 다음과 같습니다:

  • 필드, 매개변수 또는 enum 값 제거 또는 이름 바꾸기. JSON 응답에서 필드는 어떠한 JSON 키든 포함합니다.
  • 엔드포인트 제거
  • 새로운 리디렉션 추가 (모든 클라이언트가 리디렉션을 따르는 것은 아닙니다)
  • 어떤 응답의 콘텐츠 유형 변경
  • 응답에서 필드 유형 변경. JSON 응답에서는 어떠한 Number, String, Boolean, Array, 또는 Object 유형에서 다른 유형으로의 변경입니다.
  • 새로운 필수 매개변수 추가
  • 인증, 승인 또는 다른 헤더 요구 사항 변경
  • 500 이외의 어떤 상태 코드 변경

호환성을 깨뜨리지 않는 변경 사항이란

호환성을 깨뜨리지 않는 변경 사항의 몇 가지 예는 다음과 같습니다:

  • 엔드포인트, 필수가 아닌 매개변수, 필드 또는 enum 값 등 추가 변경
  • 에러 메시지 변경
  • 500 상태 코드에서 지원되는 어떠한 상태 코드로 변경 (이는 버그 수정입니다)
  • 응답에서 반환된 필드 순서 변경

선언된 매개변수

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)
caution
declared(params)는 반드시 .to_h를 호출해야 하는 Hashie::Mash 객체를 반환합니다.

하지만 우리가 단일 요소에 액세스할 때에는 params[key]를 직접 사용할 수 있습니다.

예를 들어:

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

배열 유형

Grape v1.3+에서는 배열 유형은 coerce_with 블록 또는 매개변수로 정의되어야 합니다. API 요청에서 문자열이 전달될 때 유효성 검사에 실패합니다. 자세한 내용은 Grape 업그레이드 문서를 참조하십시오.

자동으로 nil 입력 강제 형변환

Grape v1.3.3 이전에는 nil 값을 갖는 Array 매개변수가 자동으로 빈 Array로 강제 형변환되었습니다. 그러나 v1.3.3의 이번 pull 요청으로 인해 더 이상 해당되지 않습니다. 예를 들어, 선택적 매개변수를 갖는 PUT /test 요청을 정의하는 경우:

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

보통 PUT /test?user_ids 요청은 Grape가 params{ user_ids: nil }로 전달하게 됩니다.

이는 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 트래커에는 열려있는 이슈가 있습니다 이를 더 쉽게 만들기 위해.

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 경로 도우미 사용하기

상대 URL에서 GitLab을 설치할 수 있기 때문에, Grape에서 생성된 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를 사용하는지, 절대 경로인지 여부를 확인합니다. 기본적으로 절대 경로가 허용되지 않습니다. 그러나 다음과 같이 허용되는 절대 경로에 대한 allowlist를 선택적으로 전달할 수 있습니다: 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::<YourCustomValidatorClassName>)

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

내부 API

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

N+1 문제 회피

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

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

같은 모델이 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 '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는 이에 해당되지 않습니다.