실시간 뷰 컴포넌트 빌드 및 배포

GitLab은 사용자 입력을 받아들이고 상태 변경을 사용자에게 다시 표시하는 개별 뷰 컴포넌트를 통해 대화형 사용자 경험을 제공합니다. 예를 들어, Merge Request 페이지에서 사용자는 승인, 코멘트 남기기, CI/CD 파이프라인과 상호 작용 등을 할 수 있습니다.

그러나 GitLab은 종종 상태 업데이트를 즉시 반영하지 못하는 경우가 있습니다. 이는 페이지의 일부가 사용자가 페이지를 다시로드한 후에 업데이트된만큼만 오래된 데이터를 표시한다는 것을 의미합니다.

이에 대응하여, GitLab은 뷰 컴포넌트가 웹소켓을 통해 실시간으로 상태 업데이트를 받는 기술과 프로그래밍 API를 도입했습니다.

다음 설명서에서는 GitLab Ruby on Rails 서버로부터 실시간 업데이트를 수신하는 뷰 컴포넌트를 빌드하고 배포하는 방법을 안내합니다.

note
Action Cable 및 GraphQL 구독은 진행 중인 작업이며 활발히 개발 중입니다. 이러한 것들이 올바른 도구인지 확인하려면 사용 사례를 평가해야 합니다. 확신이 없다면, #f_real-time 내부 슬랙 채널에서 도움을 요청하세요.

실시간 뷰 컴포넌트 빌드

전제 조건:

다음을 읽어보세요:

GitLab에서 실시간 뷰 컴포넌트를 빌드하려면 다음을 수행해야 합니다:

  • Vue 컴포넌트를 Apollo 구독과 통합합니다.
  • GitLab Ruby on Rails 백엔드에서 GraphQL 구독을 추가하고 트리거합니다.

Vue 컴포넌트를 Apollo 구독과 통합

note
현재의 실시간 스택은 클라이언트 코드가 Vue를 렌더링 레이어로 사용하고 Apollo를 상태 및 네트워킹 레이어로 사용한다고 가정합니다. Vue + Apollo로 마이그레이션되지 않은 GitLab 프론트엔드 부분에서 작업하는 경우에는 그 작업을 먼저 완료해야 합니다.

가정하는 바가 단순화되었지만, IssueView Vue 컴포넌트는 GitLab Issue 데이터를 관찰하고 렌더링하는 가상의 컴포넌트입니다. 여기서는 모든 것이 문제의 원인이 되는 이슈의 제목과 설명을 렌더링하는 것으로 가정합니다:

import issueQuery from '~/issues/queries/issue_view.query.graqhql';

export default {
  //...
};

쿼리는 다음과 같아야 합니다:

  • app/assets/javascripts/issues/queries/issue_view.query.graqhql에 정의되어야 합니다.
  • 다음과 같은 GraphQL 작업을 포함해야 합니다:

    query gitlabIssue($iid: String!) {
      # We hard-code the path here only for illustration. Don't do this in practice.
      project(fullPath: "gitlab-org/gitlab") {
        issue(iid: $iid) {
          title
          description
        }
      }
    }
    

이 시점까지 이 뷰 컴포넌트는 데이터로 자신을 채우기 위해 초기 검색 쿼리를 정의만 했습니다. 이는 뷰가 시작하는 HTTP POST 요청으로 전송되는 보통의 GraphQL query 작업으로, 서버의 이후 업데이트는 이 뷰를 오래된 상태로 만들 수 있습니다. 이것이 서버로부터 업데이트를 받으려면, 다음 작업을 해야 합니다:

  1. GraphQL 구독 정의 추가
  2. Apollo 구독 후크 정의

GraphQL 구독 정의 추가

구독은 GraphQL 쿼리를 정의하지만, 이는 GraphQL subscription 작업으로 래핑됩니다. 이 쿼리는 백엔드에서 시작되어 그 결과가 WebSocket을 통해 뷰 컴포넌트로 푸시됩니다.

최초 검색 쿼리와 유사하게 다음을 수행해야 합니다:

  • app/assets/javascripts/issues/queries/issue_updated.subscription.graqhql에 구독 파일을 정의해야 합니다.
  • 파일에 다음과 같은 GraphQL 작업을 포함해야 합니다:

    subscription issueUpdatedSubscription($iid: String!) {
      issueUpdated($issueId: IssueID!) {
        issue(issueId: $issueId) {
          title
          description
        }
      }
    

새로운 구독을 추가할 때는 다음과 같은 명명 지침을 사용하세요:

  • 구독의 작업 이름을 Subscription으로 끝내거나 GitLab EE에만 해당하는 경우 SubscriptionEE으로 끝내세요. 예: issueUpdatedSubscription, 또는 issueUpdatedSubscriptionEE.
  • 구독의 이벤트 이름에 “발생한” 동사를 사용하세요. 예: issueUpdated.

구독 정의는 보통의 쿼리와 유사해 보이지만, 이해하는 데 중요한 몇 가지 주요 차이점이 있습니다:

  • query:
    • 프론트엔드에서 시작됨.
    • URL에서 개체가 일반적으로 참조되는 방식인 내부 ID(iid, 숫자)를 사용합니다. 이 내부 ID는 포함된 네임스페이스에 상대적이므로(project를 이용한 이 예시에서), fullPath 아래에서 쿼리를 중첩해야 합니다.
  • subscription:
    • 전면에서 백엔드로부터 미래의 업데이트를 받기 위한 요청입니다.
    • 다음을 포함합니다:
      • 구독 자체를 설명하는 작업 이름(issueUpdatedSubscription 예시에서).
      • 중첩된 이벤트 쿼리(issueUpdated 예시에서). 중첩된 이벤트 쿼리:
        • 동일한 이름으로 GraphQL 트리거를 실행할 때 실행됨, 그래서 구독에 사용된 이벤트 이름은 백엔드에서 사용된 트리거 필드와 일치해야 합니다.
        • GraphQL에서 리소스를 식별하는 주요 방법인 숫자 내부 ID 대신 글로벌 ID 문자열을 사용합니다. 자세한 내용은 GraphQL 글로벌 ID를 참조하세요.

Apollo 구독 후크 정의

구독을 정의한 후, 이를 Apollo의 subscribeToMore 속성을 사용하여 뷰 컴포넌트에 추가하세요:

import issueQuery from '~/issues/queries/issue_view.query.graqhql';
import issueUpdatedSubscription from '~/issues/queries/issue_updated.subscription.graqhql';

export default {
  // 이전과 같이...
};

이제 웹소켓 연결을 통해 뷰 컴포넌트로 업데이트를 받아볼 수 있게 되었습니다. 다음으로 백엔드에서 이벤트를 트리거하기 위해 수행해야 할 작업을 다룹니다.

GraphQL 구독 트리거

WebSocket에서 업데이트를 받을 수 있는 뷰 컴포넌트를 작성하는 것은 전체 이야기의 절반에 불과합니다. GitLab Rails 애플리케이션에서는 다음 단계를 수행해야합니다:

  1. GraphQL::Schema::Subscription 클래스를 구현하세요. 이 클래스는:
    • graphql-ruby에 의해 전송된 subscription 작업을 해결하는 데 사용됩니다.
    • 구독이 가져야 하는 인수 및 호출자에게 반환할 페이로드를 정의합니다.
    • 호출자가 이 구독을 만들기에 적격한지를 확인하기 위해 필요한 비즈니스 로직을 실행합니다.
  2. Types::SubscriptionType 클래스에 새로운 field를 추가하세요. 이 필드는 Vue 컴포넌트와 통합할 때 사용한 이벤트 이름GraphQL::Schema::Subscription 클래스에 매핑합니다.
  3. GraphqlTriggers에 이벤트 이름과 일치하는 메소드를 추가하세요. 이 메소드는 해당 GraphQL 트리거를 실행합니다.
  4. 도메인 로직의 일부로서 새로운 트리거를 실행하기 위해 서비스나 액티브 레코드 모델 클래스를 사용하세요.

구독 구현하기

이미 GraphQL::Schema::Subscription으로 구현된 이벤트에 가입할 경우, 이 단계는 선택 사항입니다. 그렇지 않은 경우, app/graphql/subscriptions/ 하위에 새로운 클래스를 생성하여 새로운 구독을 구현합니다. Issue가 업데이트되는 issueUpdated 이벤트에 대한 예시로, 구독 구현은 다음과 같습니다:

module Subscriptions
  class IssueUpdated < BaseSubscription
    include Gitlab::Graphql::Laziness
    
    payload_type Types::IssueType
    
    argument :issue_id, Types::GlobalIDType[Issue],
              required: true,
              description: '이슈의 ID.'
    
    def authorized?(issue_id:)
      issue = force(GitlabSchema.find_by_gid(issue_id))
      
      unauthorized! unless issue && Ability.allowed?(current_user, :read_issue, issue)
      
      true
    end
  end
end

새로운 클래스를 생성할 때:

  • 모든 구독 유형이 Subscriptions::BaseSubscription에서 상속되는지 확인합니다.
  • 적절한 payload_type을 사용하여 구독된 쿼리가 액세스할 수 있는 데이터를 나타냈거나 노출하려는 개별 field를 정의합니다.
  • 각 클라이언트가 가입하거나 이벤트가 발생할 때 호출되는 사용자 정의 subscribeupdate 후크를 정의할 수도 있습니다. 이러한 메서드를 사용하는 방법에 대해선 공식 문서를 참조하세요.
  • 필요한 권한 확인을 수행하기 위해 authorized?를 구현합니다. 이러한 확인은 subscribe 또는 update 호출마다 실행됩니다.

GraphQL 구독 클래스에 대해 더 읽어보려면 공식 문서를 확인하세요.

구독 연결하기

새로운 구독 클래스를 구현하지 않은 경우, 이 단계를 건너뜁니다.

새로운 구독 클래스를 구현한 후, 해당 클래스를 SubscriptionTypefield에 매핑해야 실행할 수 있습니다. Types::SubscriptionType 클래스를 열고 새로운 필드를 추가합니다:

module Types
  class SubscriptionType < ::Types::BaseObject
    graphql_name 'Subscription'
    
    # 기존 필드
    # ...
    
    field :issue_updated,
      subscription: Subscriptions::IssueUpdated, null: true,
      description: '이슈가 업데이트될 때 트리거됩니다.'
  end
end
note
EE 구독을 연결하는 경우 EE::Types::SubscriptionType을 업데이트합니다.

issue_updated 인수가 앞단에서 클라이언트에 의해 전송된 subscription 요청에서 사용된 이름과 일치하는지 확인합니다. 그렇지 않으면 graphql-ruby는 어떤 구독자에게 정보를 제공해야 하는지 모릅니다. 이제 이벤트가 트리거될 수 있습니다.

새로운 트리거 추가하기

기존 트리거를 재사용할 수 있다면, 이 단계를 건너뜁니다.

우리는 GitlabSchema.subscriptions.trigger 주변에 퍼사드를 사용하여 이벤트를 트리거하는 것을 더 간단하게 만듭니다. GraphqlTriggers에 새로운 트리거를 추가합니다:

module GraphqlTriggers
  # 기존 트리거
  # ...
  
  def self.issue_updated(issue)
    GitlabSchema.subscriptions.trigger(:issue_updated, { issue_id: issue.to_gid }, issue)
  end
end
note
트리거가 EE 구독에 대한 것인 경우, EE::GraphqlTriggers을 업데이트합니다.
  • 첫 번째 인자인 :issue_updated는 이전 단계에서 사용된 field 이름과 일치해야 합니다.
  • 인수 해시는 이벤트를 게시해야 할 주제를 식별하기 위해 GraphQL이 이 해시를 사용합니다.

마지막 단계는 이 트리거 함수를 호출하는 것입니다.

트리거 실행하기

이 단계의 구현은 구체적으로 무엇을 구축하고 있는지에 따라 달라집니다. 예를 들어 이슈의 필드가 변경되는 경우, Issues::UpdateService를 확장하여 GraphqlTriggers.issue_updated를 호출할 수 있습니다.

실시간 뷰 컴포넌트는 이제 기능적입니다. 이제 이슈의 업데이트가 GitLab UI로 즉시 전파되어야 합니다.

실시간 뷰 컴포넌트 배포

웹소켓은 GitLab에서 비교적 새로운 기술이며, 규모에 걸쳐 지원하는 것은 몇 가지 도전을 야기시킵니다. 이러한 이유로 새로운 기능은 아래 지침에 따라 롤아웃되어야 합니다.

실시간 컴포넌트 출시

웹소켓을 통한 업데이트는 적절한 백엔드 코드 없이 시뮬레이션하기 어렵기 때문에 프론트엔드와 백엔드에서 동시에 작업할 수 있습니다.

그러나 안전을 위해 변경 사항을 별도의 Merge Request으로 보내고 먼저 백엔드 변경 사항을 배포하는 것이 좋습니다. 이렇게 하면 프론트엔드가 이벤트를 구독하기 시작할 때 백엔드가 이미 준비되어 있음이 보장됩니다.

기존 웹소켓 연결 재사용

기존 연결을 재사용하는 기능은 최소한의 위험을 수반합니다. 피처 플래그 롤아웃은 셀프 호스팅 고객에게 더 많은 제어를 제공하기 위해 권장됩니다. 그러나 GitLab.com에 대한 새로운 연결을 배포하는 데 있어서 백분율로 롤아웃하거나 새로운 연결을 추정할 필요는 없습니다.

새로운 웹소켓 연결 도입

GitLab 애플리케이션 일부에 대한 새로운 웹소켓 연결을 도입하는 모든 변경 사항은 유지하는 노드 및 Redis 및 기본 데이터베이스 같은 하위 서비스에 대한 확장 가능성 위험을 야기합니다.

최대 연결 추정

GitLab.com에서 완전히 활성화된 첫 번째 실시간 기능은 실시간 담당자였습니다. 이 이슈 페이지에서 최대 처리량을 최대 동시 웹소켓 연결과 비교함으로써 새로운 요청 당 약 4200개의 웹소켓 연결이 대략 추가된다는 추정이 가능합니다.

새로운 기능이 어떤 영향을 미칠지 이해하려면 해당 페이지의 최대 처리량(RPS)을 합산하고 다음 공식을 적용하세요:

(n * 4200) / peak_active_connections

현재 활성 연결은 이 그라파나 차트에서 확인할 수 있습니다.

이 계산은 관대하며 새로운 기능이 배포될 때 재고해야 합니다. 기존 용량의 일부로 지원되어야 하는 용량을 대략적으로 추정합니다.

순차적 롤아웃

현재 포화도 및 필요한 새로운 연결의 비율에 따라 변경 사항을 지원하는 새로운 용량이 확보될 수 있습니다. 대부분의 경우 쿠버네티스를 사용하면 이 작업을 비교적 쉽게 수행할 수 있지만 하위 서비스에는 여전히 위험이 남아 있습니다.

이를 완화하기 위해 새로운 웹소켓 연결을 설정하는 코드가 피처 플래그로 설정되고 기본적으로 off으로 설정되도록 하십시오. 피처 플래그의 주의 깊은, 백분율로 된 롤아웃은 새로운 기능에 대한 영향을 감시할 수 있도록 합니다. 11. WebSocket 대시보드에서 영향을 확인할 수 있는 신중한 백분율로 된 피처 플래그 롤아웃

  1. 피처 플래그 롤아웃 이슈를 생성합니다.
  2. 우리는 무엇을 기대하고 있나요 섹션에 새로 생성된 연결을 기대합니다.
  3. 계획 및 확장성 팀의 구성원을 복사하여 백분율 기반의 롤아웃 계획을 추정하도록 하세요.

역호환성

피처 플래그 롤아웃 기간에는 실시간 기능이 역방향 호환되거나 최소한 우아하게 쇠퇴되어야 합니다. 모든 고객이 Action Cable을 활성화하지 않았으며, Action Cable을 기본적으로 활성화할 수 있을 때까지 추가 작업이 필요합니다.

실시간을 요구 사항으로 만들면 별도의 변경이 발생하므로 이를 다음 버전 15.0에 수행하는 것이 바람직합니다.

GitLab.com의 실시간 인프라

GitLab.com에서 WebSocket 연결은 쿠버네티스로 배포된 일반 웹 플릿 및 전용 인프라에서 제공됩니다. 이는 요청을 처리하는 노드에 대한 위험을 제한하지만 공유 서비스에 대한 위험은 없어지지 않습니다. WebSocket Kubernetes 배포에 대한 자세한 내용은 이 이픽을 참조하세요.

깊이 들어가는 GitLab 실시간 스택

서버에서 시작된 푸시는 네트워크를 통해 전파되어 클라이언트에서 사용자 상호 작용 없이보기를 트리거해야 하므로 실시간 기능은 프론트엔드 및 백엔드를 모두 포함한 스택을 살펴보는 것 외에는 이해가 안될 수밖에 없습니다.

note
역사적인 이유로 클라이언트의 변경사항에 응답하여 업데이트를 처리하는 컨트롤러 루트는 realtime_changes로 불립니다. 이들은 조건부 GET 요청을 사용하며, 이 가이드에서 다루는 실시간 행동과는 무관합니다.

클라이언트로부터 푸시된 모든 실시간 업데이트는 GitLab Rails 애플리케이션에서 시작됩니다. 우리는 다음의 기술을 사용하여 이러한 업데이트를 시작하고 제공합니다:

GitLab Rails 백엔드에서:

  • 구독 상태를 관리하기 위해 Redis PubSub.
  • 웹소켓 연결 및 데이터 전송을 처리하기 위한 Action Cable.
  • GraphQL 구독과 트리거를 구현하기 위한 graphql-ruby.

GitLab 프론트엔드에서:

  • GraphQL 요청, 라우팅 및 캐싱을 처리하기 위한 Apollo Client.
  • 실시간으로 업데이트되는 보기 컴포넌트를 정의하고 렌더링하기 위한 Vue.js.

다음 그림은 이러한 계층 간의 데이터 전파를 설명하는데 사용됩니다.

액션 케이블과 웹소켓

Action Cable은 Ruby on Rails에 WebSocket 지원을 추가하는 라이브러리입니다. 웹소켓은 기존의 HTTP 기반 서버 및 애플리케이션을 양방향 통신이 가능한 단일 TCP 연결을 통해 강화하기 위한 HTTP 친화적인 솔루션으로 개발되었습니다. 클라이언트는 먼저 서버에게 일반적인 HTTP 요청을 보내어 연결을 웹소켓으로 업그레이드하도록 요청합니다. 성공적으로 업그레이드되면 동일한 TCP 연결을 통해 클라이언트와 서버가 양방향으로 데이터를 송수신할 수 있습니다.

웹소켓 프로토콜은 전송된 데이터를 어떻게 인코딩하거나 구조화해야 하는지를 명시하지 않기 때문에, 우리는 Action Cable과 같은 라이브러리가 이러한 문제들을 처리하도록 해야 합니다. Action Cable은 다음을 수행합니다.

  • HTTP에서 웹소켓 프로토콜로의 초기 연결 업그레이드를 처리합니다. 이후 ws:// 스킴을 사용한 요청은 Action Pack이 아닌 Action Cable 서버에서 처리됩니다.
  • 웹소켓을 통해 전송된 데이터의 인코딩 방법을 정의합니다. Action Cable은 이를 JSON으로 지정합니다. 이를 통해 애플리케이션은 데이터를 루비 해시로 제공하고, Action Cable은 이를 JSON으로 (없애거나 분리하여) 직렬화 및 역직렬화합니다.
  • 클라이언트의 연결 또는 끊김 및 클라이언트 인증을 처리하기 위해 콜백 후크를 제공합니다.
  • 게시/구독 및 원격 프로시저 호출을 구현하기 위한 개발자 추상화로 ActionCable::Channel를 제공합니다.

Action Cable은 ActionCable::Channel에 누가 어떤 ActionCable::Channel에 가입했는지 추적하기 위해 다양한 구현을 지원합니다. GitLab에서는 Redis 어댑터를 사용하여, 여러 Puma 인스턴스에서도 동일한 Action Cable 채널에 다른 클라이언트가 연결될 수 있도록 Redis PubSub 채널을 분산 메시지 버스로 사용합니다. 다른 클라이언트가 동일한 Action Cable 채널에 연결될 수 있기 때문에 공유 리포지터리가 필요합니다.

note
Action Cable 채널과 Redis PubSub 채널을 혼동하지 마세요. Action Cable Channel 객체는 WebSocket 연결을 통해 전송되는 다양한 종류의 데이터를 분류하고 처리하기 위한 프로그래밍 추상화입니다. Action Cable에서는 기본으로 PubSub 채널을 브로드캐스팅이라고 하며, 클라이언트와 브로드캐스팅 간의 연결은 구독이라고 합니다. 특히, 각 Action Cable Channel에는 여러 브로드캐스팅(PubSub 채널) 및 구독이 있을 수 있습니다.

Action Cable을 사용하면 각 Channel에 대한 서로 다른 종류의 동작을 표현할 수 있으며, 모든 Channel의 업데이트는 동일한 웹소켓 연결을 사용하므로 각 GitLab 페이지에는 뷰 컴포넌트를 실시간 동작으로 향상시키기 위해 하나의 웹소켓 연결만 필요합니다.

GitLab 페이지의 실시간 업데이트를 구현하기 위해 개별적인 Channel 구현을 작성하는 것이 아니라, GitLab에서는 GitLab 페이지의 모든 페이지가 push 기반 업데이트를 필요로 하는 경우 GraphqlChannel을 제공합니다.

GraphQL 구독: 백엔드

GitLab은 클라이언트가 GraphQL 쿼리를 사용하여 서버에서 구조화된 데이터를 요청할 수 있도록 GraphQL를 지원합니다. GraphQL을 채택한 이유를 알아보려면 GitLab GraphQL 개요를 참조하세요. GitLab 백엔드의 GraphQL 지원은 graphql-ruby 젬으로 제공됩니다.

일반적으로 GraphQL 쿼리는 표준 요청-응답 주기를 따르는 클라이언트 기반 HTTP POST 요청입니다. 실시간 기능을 위해 클라이언트에서는 발행/구독 패턴의 구현인 GraphQL 구독을 사용합니다. 이 접근 방식에서 클라이언트는 먼저 GraphqlChannel에 구독 요청을 보냅니다. 이때 사용되는 정보는 다음과 같습니다.

  • 구독 필드의 이름(이벤트 이름).
  • 이 이벤트가 트리거될 때 실행할 GraphQL 쿼리.

이 정보는 서버가 이 이벤트 스트림을 나타내는 topic을 생성하는 데 사용됩니다. 이 topic은 구독 인수 및 이벤트 이름에서 유도된 고유한 이름으로, 이벤트가 트리거되면 알림을 받아야 하는 모든 구독자를 식별하는 데 사용됩니다. 하나의 topic에 여러 클라이언트가 구독할 수 있습니다. 예를 들어 issuableAssigneesUpdated:issuableId:<hashed_id>은 주어진 ID의 이슈에 대한 담당자가 변경될 때 업데이트를 받기 원하는 클라이언트가 구독하는 것으로 사용될 수 있습니다.

백엔드는 “이픽에 이슈 추가” 또는 “이슈에 사용자 할당”과 같은 도메인 이벤트에 대한 응답으로 구독을 트리거하며, GitLab에서는 서비스 객체 또는 ActiveRecord 모델 객체로 이를 수행할 수 있습니다. 트리거는 이와 같은 이벤트 이름 및 인수를 사용하여 GitlabSchema.subscriptions.trigger를 호출하여 실행되며, 이로부터 graphql-ruby가 topic을 유도합니다. 그런 다음 서버는 이 topic에 대한 모든 구독자를 찾아 각 구독자의 쿼리를 실행하고, 결과를 모든 topic 구독자에게 다시 푸시합니다.

Action Cable을 사용하여 GraphQL 구독의 기본 전송으로 사용하기 때문에, topic은 Action Cable 브로드캐스팅으로 구현됩니다. 앞서 설명한대로 이는 Redis PubSub 채널을 나타내며, 각 구독자에 대해 두 개의 PubSub 채널이 사용됩니다.

  • 각 topic당 하나의 graphql-event:<namespace>:<topic> 채널입니다. 이 채널은 어떤 클라이언트가 어떤 이벤트에 구독되었는지 추적하기 위해 모든 잠재적인 클라이언트 사이에서 공유되며, namespace의 사용은 선택 사항이며 공백일 수 있습니다.
  • 각 클라이언트당 하나의 graphql-subscription:<subscription-id> 채널입니다. 이 채널은 쿼리 결과를 해당 클라이언트에 다시 전송하기 위해 사용되며, 따라서 다른 클라이언트 사이에서 공유될 수 없습니다.

다음 섹션에서는 GitLab 프론트엔드가 실시간 업데이트를 구현하는 데 GraphQL 구독을 사용하는 방법에 대해 설명합니다.

GraphQL 구독: 프론트엔드

GitLab 프론트엔드는 JavaScript를 실행하기 때문에 클라이언트에서 서버로 GraphQL 쿼리, 뮤테이션 및 구독을 전송하기 위한 다른 GraphQL 구현이 필요합니다. 이를 위해 Apollo를 사용합니다.

Apollo는 JavaScript에서의 GraphQL의 포괄적인 구현으로, apollo-serverapollo-client로 분리되어 있으며 추가적인 유틸리티 모듈이 있습니다. Ruby 백엔드를 실행하기 때문에, 우리는 apollo-server 대신 apollo-client를 사용합니다.

이는 다음을 단순화합니다.

  • 네트워킹, 연결 관리 및 요청 라우팅.
  • 클라이언트 측 상태 관리 및 응답 캐싱.
  • 뷰 컴포넌트를 이용한 GraphQL의 통합을 위한 브릿지 모듈.
note
Apollo Client 문서를 읽을 때, 뷰 렌더링에 React.js가 사용된다고 가정합니다. 그러나 GitLab에서는 React.js를 사용하지 않고 Vue.js를 사용하며, 이는 Vue.js 어댑터를 사용하여 Apollo와 통합됩니다.

Apollo는 다음을 정의하는데 사용되는 함수 및 후크를 제공합니다.

  • 뷰가 쿼리, 뮤테이션 또는 구독을 어떻게 전송하는지.
  • 응답을 다루어야 하는 방법.
  • 응답 데이터를 캐싱하는 방법.

입구점은 ApolloClient로, 이는 페이지의 모든 뷰 컴포넌트 간에 공유된 GraphQL 클라이언트 객체입니다. 모든 뷰 컴포넌트는 서버와 통신하기 위해 내부적으로 이를 사용합니다.

서로 다른 종류의 요청을 라우팅하는 방법을 결정하기 위해 Apollo는 ApolloLink 추상화를 사용합니다. 구체적으로, ActionCableLink를 사용하여 실시간 서버 구독을 다른 GraphQL 요청에서 분리합니다.

  • 웹소켓 연결을 Action Cable에 설정합니다.
  • 서버 푸시를 클라이언트의 Observable 이벤트 스트림에 매핑하여 뷰가 자신을 업데이트하기 위해 해당 이벤트 스트림을 구독할 수 있도록 합니다.

Apollo 및 Vue.js에 대한 자세한 정보는 GitLab GraphQL 개발 가이드를 참조하세요.