API 스타일 가이드

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

인스턴스 변수

인스턴스 변수를 사용하지 마세요. (Rails 뷰에서처럼 접근할 필요가 없기 때문에) 로컬 변수를 사용하는 것이 좋습니다.

엔터티(Entities)

엔드포인트의 페이로드를 표현하기 위해 항상 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 필드를 모두 노출합니다.

예외

예외는 다음과 같은 경우에만 허용됩니다:

  • 주요 GitLab 릴리스에서 기능을 제거해야 하는 경우
  • 후향 호환성을 유지할 수 없는 경우 어떤 형태로든.

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

심지어 이 예외의 경우에도 필드 또는 인자를 제거하는 대신 항상 다음을 수행해야 합니다:

  • 필드에 대한 빈 응답 반환 (예: "null" 또는 [])
  • 인자를 무효한 조치로 전환함.

무엇이 호환되지 않는 변경인가

호환되지 않는 변경의 몇 가지 예는 다음과 같습니다:

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

무엇이 호환되는 변경이 아닌가

호환되는 변경의 몇 가지 예는 다음과 같습니다:

  • 엔드포인트, 필수가 아닌 인자, 필드 또는 열거형 값 추가와 같이 추가적인 변경.
  • 오류 메시지 변경.
  • 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)
note
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 값을 가진 배열 매개변수가 자동으로 빈 배열로 강제 변환되었습니다. 그러나 v1.3.3의 이 PR으로 인해 그렇지 않게 되었습니다. 예를 들어, 선택적 매개변수를 가진 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 요청의 경우, 일반적으로 성공할 경우 기본적으로 204 No Content 응답을 반환하거나 주어진 If-Unmodified-Since 헤더가 범위를 벗어나는 경우 412 Precondition Failed 응답을 반환하는 destroy_conditionally! 도우미를 사용해야 합니다. 이 도우미는 전달된 리소스에서 #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와 동사만 가지거나(위의 예제에서는 “approve”) 단일 값만 가지는 경우입니다.

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

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 유효성 검사기는 매개변수 값의 다른 경우에 대해 유효성을 검사합니다. 주로 경로가 상대적인지, ../../을 통한 경로 이동을 포함하는지 및 경로가 절대적인지(예: /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 유효성 검사기는 주어진 매개변수의 값이 정수, 없음, 또는 아무 값인지 확인합니다. 요청에서는 이러한 값 중 하나만 허용됩니다.

  • ArrayNoneAny:

    ArrayNoneAny 유효성 검사기는 주어진 매개변수의 값이 배열, 없음, 또는 아무 값인지 확인합니다. 요청에서는 이러한 값 중 하나만 허용됩니다.

  • 이메일 또는 이메일 디렉터리:

    이메일 또는 이메일 디렉터리 유효성 검사기은 문자열 또는 문자열 디렉터리의 값이 유효한 이메일 주소만 포함하는지 확인합니다. 요청에서는 모든 유효한 이메일 주소가 포함된 디렉터리만 허용됩니다.

새로운 사용자 정의 유효성 검사기 추가하기

사용자 정의 유효성 검사기는 매개변수를 플랫폼으로 전송하기 전에 유효성을 검증하는 좋은 방법입니다. 매개변수의 유효하지 않음을 초기에 식별하면 서버와 플랫폼 간의 번복을 줄일 수 있습니다.

새로운 사용자 정의 유효성 검사기를 추가해야 하는 경우, 해당 검사기는 validators 디렉터리에 고유한 파일로 추가됩니다. 저희는 우리의 API에 Grape를 사용하기 때문에, 사용자 정의 유효성 검사기 클래스에서 Grape::Validations::Validators::Base 클래스를 상속합니다. 이제 해야 할 일은 validate_param! 메서드를 정의하는 것인데, 이 메서드는 매개변수 해시와 유효성을 검사해야 하는 매개변수 이름을 인수로 받습니다.

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

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

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

검사기를 추가한 후에는 해당 검사기에 대한 rspecvalidators 디렉터리에 별도의 파일로 추가해야 합니다.

내부 API

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

N+1 문제 회피

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

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

같은 모델이 API에서 여러 엔티티(예: UserBasic, User, UserPublic)를 가지고 있는 경우, 해당 스코프를 적용할 때 재량을 사용해야 합니다. 연이어 엔티티가 해당 스코프를 기반으로 할 수 있도록 기본적인 엔티티를 최적화해야 할 수도 있습니다.

또한 with_api_entity_associations 스코프는 작업 디렉터리 API에서 Todo타겟 데이터를 자동으로 미리 로드합니다.

미리 로딩에 대한 컨텍스트와 토론에 대한 자세한 내용은 이 MR(merge request)을 참조하세요.

테스트로 확인하기

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 쿼리가 얼마나 많이 실행되는지 기록합니다
  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는 이에 포함되지 않습니다.