GraphQL이란 무엇인가요?

Zexuan Luo

Zexuan Luo

November 4, 2022

Technology

GraphQL이란 무엇이며, 얼마나 인기가 있나요?

GraphQL은 2015년 페이스북이 발표한 API 지향 쿼리 조작 언어입니다. 다른 API 설계와 달리, GraphQL은 클라이언트가 미리 합의된 데이터 구조를 기반으로 쿼리 문장을 구성하고, 서버가 이를 해석하여 필요한 데이터만 반환할 수 있도록 합니다. 이를 통해 GraphQL은 풍부함과 유연성을 제공하면서도 불필요한 데이터로 인한 성능 저하를 방지합니다. 이로 인해 GraphQL은 많은 복잡한 데이터 객체를 다루어야 하는 애플리케이션에 적합한 선택지가 됩니다.

2018년, GraphQL은 완전한 명세와 안정적인 버전을 출시했습니다. 같은 해, 페이스북은 GraphQL 프로젝트를 리눅스 재단 산하의 GraphQL 재단에 기부했습니다. 이후로 GraphQL은 많은 오픈소스 프로젝트와 상업 조직에서 사용되기 시작했습니다. 현재 시장에는 여러 주요 클라이언트 측 GraphQL 구현체가 있으며, 서버 측 구현체는 모든 주요 서버 측 프로그래밍 언어에서 사용 가능하며, D와 R 같은 특수 언어에서도 사용할 수 있습니다.

GraphQL의 실제 시나리오와 도전 과제

GraphQL의 가장 잘 알려진 예는 GitHub의 GraphQL API입니다.

GraphQL을 도입하기 전, GitHub는 수백만 개의 호스팅된 프로젝트에서 생성된 풍부한 데이터를 노출하기 위해 REST API를 제공했습니다. 이는 매우 성공적이어서 REST API를 설계할 때 사람들이 모방할 모델이 되었습니다. 그러나 데이터 객체의 수가 증가하고 객체 내 필드가 커지면서 REST API는 점점 더 많은 단점을 드러내기 시작했습니다. 서버 측에서는 각 호출마다 생성되는 데이터 양 때문에 비용을 줄이기 위해 호출 빈도에 엄격한 제한을 설정해야 했습니다. 개발자 측에서는 단일 호출이 많은 데이터를 반환하지만 대부분이 쓸모없는 데이터이기 때문에 이 제한에 맞서야 했습니다. 특정 정보를 얻기 위해 개발자들은 종종 여러 쿼리를 실행하고, 쿼리 결과에서 의미 있는 데이터를 원하는 내용으로 조합하기 위해 많은 접착 코드를 작성해야 했습니다. 이 과정에서 "호출 횟수"라는 족쇄를 착용해야 했습니다.

따라서 Github은 GraphQL이 출시되자마자 이를 도입했습니다. GitHub는 GraphQL의 "대사"가 되어 수천 명의 개발자에게 이를 전달했습니다. 이제 GraphQL API는 Github의 최우선 선택지가 되었습니다. GraphQL 지원을 처음 발표한 이후, GitHub는 매년 여러 글을 게시했습니다. 개발자들이 GraphQL로 마이그레이션할 수 있도록 GitHub는 이를 위해 특별히 인터랙티브 쿼리 애플리케이션을 작성했습니다: https://docs.github.com/en/graphql/overview/explorer. 개발자들은 이 애플리케이션을 통해 GraphQL 작성 방법을 배울 수 있습니다.

그러나 GraphQL은 만병통치약이 아닙니다. 최근에 GitHub는 패키지 API의 자체 GraphQL 구현을 폐기했습니다. 많은 사람들이 GraphQL의 일부 단점에 대해 논의하기 시작했습니다. GraphQL의 많은 문제는 그 구조가 HTTP 표준과 너무 다르기 때문에 GraphQL의 일부 개념을 HTTP 경로/헤더와 같은 구조로 쉽게 매핑할 수 없다는 사실에서 비롯됩니다. GraphQL을 일반 HTTP API로 취급하려면 추가 개발 작업이 필요합니다. 결과적으로, 자체 GraphQL API를 관리하려는 개발자는 GraphQL을 지원하는 API 게이트웨이를 사용해야 합니다.

APISIX가 GraphQL을 지원하는 방법

현재 APISIX는 GraphQL의 일부 속성을 통해 동적 라우팅을 지원합니다. 이 기능을 통해 특정 GraphQL 요청만 수락하거나 다른 GraphQL을 다른 업스트림으로 전달할 수 있습니다.

다음 GraphQL 문장을 예로 들어보겠습니다:

query getRepo {
    owner {
        name
    }
    repo {
        created
    }
}

APISIX는 라우팅을 위해 GraphQL의 다음 세 가지 속성을 추출합니다:

  • graphql_operation
  • graphql_name
  • graphql_root_fields

위의 GraphQL 문장에서:

  • graphql_operationquery에 해당합니다.
  • graphql_namegetRepo에 해당합니다.
  • graphql_root_fields["owner", "repo"]에 해당합니다.

APISIX의 GraphQL에 대한 세밀한 라우팅 기능을 보여주기 위해 라우트를 생성해 보겠습니다:

curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
    "methods": ["POST"],
    "uri": "/graphql",
    "vars": [
        ["graphql_operation", "==", "query"],
        ["graphql_name", "==", "getRepo"],
        ["graphql_root_fields", "has", "owner"]
    ],
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "127.0.0.1:2022": 1
        }
    }
}'

다음으로, GraphQL 문장이 포함된 요청으로 접근해 보겠습니다:

curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
    owner {
        name
    }
    repo {
        created
    }
}'
HTTP/1.1 200 OK
...

쿼리 문장이 세 가지 조건 모두에 일치하므로 요청이 업스트림에 도달한 것을 확인할 수 있습니다.

반대로, 일치하지 않는 문장으로 접근하면, 예를 들어 owner 필드가 포함되지 않은 경우:

curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
    repo {
        created
    }
}'
HTTP/1.1 404 Not Found
...

해당 라우팅 규칙에 일치하지 않습니다.

owner 필드가 포함되지 않은 문장을 다른 업스트림으로 라우팅할 수 있는 라우트를 추가로 생성할 수 있습니다:

curl http://127.0.0.1:9180/apisix/admin/routes/2 \
-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
    "methods": ["POST"],
    "uri": "/graphql",
    "vars": [
        ["graphql_operation", "==", "query"],
        ["graphql_name", "==", "getRepo"],
        ["graphql_root_fields", "!", "has", "owner"]
    ],
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "192.168.0.1:2022": 1
        }
    }
}'
curl -i -H 'content-type: application/graphql' \
-X POST http://127.0.0.1:9080/graphql -d '
query getRepo {
    repo {
        created
    }
}'
HTTP/1.1 200 OK
...

APISIX의 향후 GraphQL 지원 전망

동적 라우팅 외에도, APISIX는 향후 GraphQL의 특정 필드를 기반으로 한 더 많은 작업을 도입할 수 있습니다. 예를 들어, GitHub의 GraphQL API는 특정 공식에 따라 속도 제한을 적용하며, 우리도 유사한 규칙을 적용하여 단일 GraphQL 요청을 해당 수의 "가상 호출"로 변환하여 GraphQL 특정 속도 제한을 달성할 수 있습니다.

또 다른 방식으로 문제를 생각해 볼 수도 있습니다. 애플리케이션 자체는 여전히 REST API를 제공하고, 게이트웨이는 가장 바깥쪽에서 GraphQL 요청을 REST 요청으로 변환하고 REST 응답을 GraphQL 응답으로 변환합니다. 이렇게 제공된 GraphQL API는 특별한 플러그인을 개발하지 않고도 RBAC, 속도 제한, 캐싱 등의 기능을 수행할 수 있습니다. 기술적인 관점에서 이 아이디어는 구현하기 그리 어렵지 않습니다. 결국 2022년에는 REST API도 OpenAPI 명세를 스키마로 제공하는 경향이 있으며, 이는 단지 GraphQL 스키마와 OpenAPI 스키마 간의 전환과 GraphQL 특정 필드 필터링일 뿐입니다. (물론, 제가 직접 실습해보지는 않았으므로, 아직 극복해야 할 몇 가지 세부 사항에서 도전이 있을 수 있습니다.)

세심한 독자라면 이렇게 변환된 GraphQL API는 한 번에 하나의 모델만 조작할 수 있다는 것을 알 수 있을 것입니다. 이는 분명히 GraphQL의 유연성 요구 사항을 충족하지 못하며, GraphQL의 옷을 입은 REST API에 불과합니다. 그러나 GraphQL에는 스키마 스티칭이라는 개념이 있어 구현자가 여러 스키마를 결합할 수 있습니다.

예를 들어, GetEvent와 GetLocation이라는 두 개의 API가 있으며, 각각 Event와 Location 타입을 반환합니다.

type Event {
    id: string
    location_id: string
}

type Location {
    id: string
    city: string
}

type Query {
    GetEvent(id: string): Event
    GetLocation(id: string): Location
}

이 두 API를 결합하여 GetEventWithLocation이라는 새로운 API를 만들 수 있습니다. 이는 다음과 같습니다:

type EventWithLocation {
    id: string
    location: Location
}

type Query {
    GetEventWithLocation(id: string): EventWithLocation
}

스티칭의 구체적인 구현은 게이트웨이가 담당합니다. 위의 예에서 게이트웨이는 API를 두 개로 분할하여 GetEvent를 호출하여 location_id를 얻고, 그 다음 GetLocation을 호출하여 결합된 데이터를 얻습니다.

요약하면, REST를 GraphQL로 변환함으로써 각 REST API를 해당하는 GraphQL 모델로 변환할 수 있으며, 스키마 스티칭을 통해 여러 모델을 하나의 GraphQL API로 결합할 수 있습니다. 이렇게 하면 기존 REST API 위에 풍부하고 유연한 GraphQL API를 구축하고, REST API의 세분화된 플러그인을 관리할 수 있습니다. 이 설계는 부수적으로 일부 API 오케스트레이션 문제를 해결합니다. 위의 예에서와 같이, 우리는 하나의 API(Event.location_id)의 출력을 다른 API(Location.id)의 입력으로 사용합니다.

Tags: