본문 바로가기
Tech/GraphQL

GraphQL 쿼리, 뮤테이션

by egas 2021. 6. 24.

GraphQL 서비스가 실행되면 (일반적으로는 웹 서비스의 URL) GraphQL 쿼리를 전송하여 유효성을 검사하고 실행한다. 수신된 쿼리는 먼저 정의된 타입과 필드만 참조하도록 검사한 다음, 함수를 실행하여 결과를 생성한다. 

{
  me {
    name
  }
}

위 쿼리문을 보내면 아래와 같은 JSON을 얻을 수 있다.

{
  "me": {
    "name": "Luke Skywalker"
  }
}

 

쿼리 & 뮤테이션

 

필드

 

GraphQL은 객체에 대한 특정 필드를 요청하는 것이 무척 간단하다. 쿼리와 결과가 정확히 동일한 형태이다. 이것이 GraphQL의 핵심이다. 항상 기대한 결과를 얻을 수 있다. 서버에서 클라이언트가 요청하는 필드를 정확히 알고 있기 때문이다.

 

출처: https://graphql-kr.github.io/learn/queries/

GraphQL 쿼리는 연관된 객체와 필드를 탐색할 수 있어서 클라이언트는 기존 REST 구조처럼 여러 번 요청을 수행하는 대신 한 번의 요청으로 많은 데이터를 가져올 수 있다.

{
  hero {
    name
    # 쿼리에 주석을 쓸 수도 있습니다!
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

 

인자

 

출처: https://graphql-kr.github.io/learn/queries/

REST와 같은 시스템에서는 요청에 쿼리 파라미터와 URL 세그먼트 같은 단일 인자들만 전달할 수 있다. 하지만 GraphQL에서는 모든 필드와 중첩된 객체가 인자를 가질 수 있으므로 GraphQL은 여러 번의 API fetch를 완벽하게 대체할 수 있다. 필드에 인자를 전달하면, 모든 클라이언트에서 개별적으로 처리하는 대신 서버에서 데이터 변환을 한 번만 구현할 수도 있다.

출처: https://graphql-kr.github.io/learn/queries/

인자는 다양한 타입이 될 수 있다. 위 예제에서는 열거형(Enumeration) 타입을 사용했다. 이 타입은 다양한 옵션들 (이 경우에는 길이 단위 METER, FOOT) 중 하나를 나타낸다. GraphQL은 기본 타입과 함께 제공되지만, GraphQL 서버는 데이터를 직렬화 할 수 있는 한 직접 커스텀 타입을 선언할 수도 있다.

 

프래그먼트

 

친구(friends)를 가진 두 영웅(hero)을 순서대로 요청한다고 했을 때, 쿼리가 복잡해질 수 있다. 이렇게 되면 필드를 최소 두 번 반복해야 한다. 우리는 `프래그먼트`라는 재사용 가능한 단위를 사용해서 필드 셋을 구성한 다음 필요한 쿼리에 포함할 수 있다.

{
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}
{
  "data": {
    "leftComparison": {
      "name": "Luke Skywalker",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        },
        {
          "name": "C-3PO"
        },
        {
          "name": "R2-D2"
        }
      ]
    },
    "rightComparison": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

프래그먼트 안에서 아래와 같이 변수를 사용할 수 있다.

query HeroComparison($first: Int = 3) {
  leftComparison: hero(episode: EMPIRE) {
    ...comparisonFields
  }
  rightComparison: hero(episode: JEDI) {
    ...comparisonFields
  }
}

fragment comparisonFields on Character {
  name
  friendsConnection(first: $first) {
    totalCount
    edges {
      node {
        name
      }
    }
  }
}

 

작업 이름 

지금까지는 query 키워드와 query 이름을 모두 생략 한 단축 문법을 사용했지만, 실제 애플리케이션에서는 코드를 덜 헷갈리게 작성하는 것이 좋다. 다음은 query를 작업 타입, HeroNameAndFriends를 작업 이름으로 한 예제이다.

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

작업 타입 쿼리(query), 뮤테이션(mutation), 구독(subscription) 이 될 수 있다.

 

작업 이름은 의미 있고 명시적으로 작성할 경우 디버깅이나 서버 측에서 로깅하는데 매우 유용하다. 네트워크 로그나 GraphQL 서버에 문제가 발생하면 내용을 확인하는 대신 코드에서 쿼리 이름을 찾아내는 것이 GraphQL에서는 더 쉽다.

 

변수

 

지금까지는 모든 인자를 쿼리 문자열 안에 작성했다. 그러나 대부분의 응용프로그램에서 필드에 대한 인자는 동적이다. 예를 들어, 어떤 스타워즈 에피소드에 관심이 있는지를 선택할 수 있는 드롭다운, 검색 필드, 필터 등이 있을 수도 있다. 

 

클라이언트 측 코드는 쿼리 문자열을 런타임에 동적으로 조작하고 이를 GraphQL의 특정한 포맷으로 직렬화 해야 하기 때문에 이러한 동적 인자를 쿼리 문자열에 직접 전달하는 것은 좋은 방법이 아니다. GraphQL은 동적 값을 쿼리에서 없애고, 변수로서 이를 별도로 전달하는 방법을 제공한다. 

query HeroNameAndFriends($episode: Episode) {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "friends": [
        {
          "name": "Luke Skywalker"
        },
        {
          "name": "Han Solo"
        },
        {
          "name": "Leia Organa"
        }
      ]
    }
  }
}

 

선언된 모든 변수는 scalars, enums, input object types 중에 하나여야한다. 

 

변수를 옵셔널이거나 필수로 지정할 수 있다. ! 가 없으면 옵셔널이다. 기본값 또한 아래와 같이 할당 할 수 있다.

query HeroNameAndFriends($episode: Episode = "JEDI") {
  hero(episode: $episode) {
    name
    friends {
      name
    }
  }
}

 

지시어

우리는 지시어와 변수를 사용하여 쿼리의 구조와 형태을 동적으로 변경할 수 있다. 지시어는 필드나 프래그먼트 안에 삽입될 수 있다. GraphQL 에서는 두 가지 지시어가 포함돼있고, GraphQL 서버에서 지원해야한다.

  • @include(if: Boolean): 인자가 true 인 경우에만 이 필드를 결과에 포함합니다.
  • @skip(if: Boolean) 인자가 true 이면 이 필드를 건너뜁니다.

출처: https://graphql-kr.github.io/learn/queries/
출처: https://graphql-kr.github.io/learn/queries/

지시어는 쿼리의 필드를 추가하고 제거하기 위해 문자열을 조작을 해야하는 상황을 피하는데 유용할 수 있다. 서버에서는 새로운 지시어를 정의하여 실험적인 기능을 추가할 수 있다.

 

뮤테이션

지금까지 GraphQL에 대한 대부분의 이야기는 데이터 가져오기(fetch)에 초점을 맞추었다. 지금부턴 서버 측 데이터를 수정할 수 있는 방법을 배워보자.

 

GraphQL은 기술적으로 어떠한 쿼리든 데이터를 수정할 수 있지만, 변경을 발생시키는 작업에 대해 명시적으로 뮤테이션을 통해 전송하는 규칙을 정하는게 좋다.

 

#variable
{
  "ep": "JEDI",
  "review": {
    "stars": 5,
    "commentary": "This is a great movie!"
  }
}
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}
{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

이 예제에서 전달한 review 변수는 스칼라값이 아닌, 인자로 전달될 수 있는 특별한 종류의 객체 타입인 input object type이다. 쿼리와 뮤테이션 사이의 가장 중요한 차이점은

 

쿼리 필드는 병렬로 실행되지만 뮤테이션 필드는 하나씩 차례대로 실행됩니다.

 

이다. 하나의 요청에서 두 개의 incrementCredits 뮤테이션를 보내면 첫 번째는 두 번째 요청 전에 완료되는 것이 보장된다.

 

인라인 프래그먼트 

인터페이스나 유니언 타입을 반환하는 필드를 쿼리하는 경우, 인라인 프래그먼트 을 사용해야한다.

 

출처: https://graphql-kr.github.io/learn/queries/

이 쿼리에서 hero 필드는 Character 를 반환하는데, episode 인자에 따라서 Human이나 Droid 중 하나일 수 있다. 

 

필드를 직접 선택할 때에는 name 과 같이 Character 인터페이스에 존재하는 필드만 요청할 수 있다. 특정한 타입의 필드를 요청하려면 타입 조건과 함께 인라인 프래그먼트 을 사용해야 한다. 첫 번째 프래그먼트는 ... on Droid 라는 레이블이 붙어있기 때문에 primaryFunction 필드는 hero 에서 반환된 Character  Droid 타입인 경우에만 실행된다.

 

메타 필드

GraphQL 서비스에서 리턴될 타입을 모르는 상황이 발생하면 클라이언트에서 해당 데이터를 처리하는 방법을 결정할 방법이 필요하다. 쿼리의 어느 지점에서나 메타 필드인 __typename 을 요청하여 그 시점에서 객체 타입의 이름을 얻을 수 있다.

 

출처: https://graphql-kr.github.io/learn/queries/

위 쿼리에서 search 는 3 가지 중 하나인 유니언 타입을 반환한다. __typename 필드가 없으면 클라이언트가 다른 타입을 구별하는 것은 불가능하다.

728x90

'Tech > GraphQL' 카테고리의 다른 글

GraphQL Schema  (0) 2021.07.01
GraphQL best practice  (0) 2021.06.24
GraphQL 스카마 & 타입  (0) 2021.06.24
GraphQL 간단 소개  (0) 2021.06.24

댓글