현인

GraphQL 학습 - 4장 유효성 검증(Validation) 본문

기술 학습/GraphQL

GraphQL 학습 - 4장 유효성 검증(Validation)

현인(Hyeon In) 2024. 11. 14. 17:48

타입 시스템을 활용하면, GraphQL 쿼리가 유효한지 미리 정의할 수 있다. 서버와 클라이언트 개발자에게 유효하지 않은 쿼리가 생성되었을 때 런타임 확인을 따로 거치지 않고 정보를 효율적으로 전달할 수 있다.

Star Wars 예제에서, starWarsValidation-test.ts 파일은 다양한 유효하지 않은 쿼리들을 보여주며, 참조 구현의 검증기를 실행하는 테스트 파일이다.

먼저, 복잡하지만 유효한 쿼리를 살펴보자. 이는 이전 섹션의 예제와 유사한 중첩 쿼리지만, 중복된 필드들이 프래그먼트로 분리되어 있다:

{
  hero {
    ...NameAndAppearances
    friends {
      ...NameAndAppearances
      friends {
        ...NameAndAppearances
      }
    }
  }
}

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

프래그먼트는 자기 자신을 참조하거나 순환을 만들 수 없다. 이는 무한한 결과를 초래할 수 있기 때문이다! 다음은 위와 동일한 쿼리지만 명시적인 3단계 중첩 없이 작성된 예시이다:

{
  hero {
    ...NameAndAppearancesAndFriends
  }
}

fragment NameAndAppearancesAndFriends on Character {
  name
  appearsIn
  friends {
    ...NameAndAppearancesAndFriends
  }
}
{
  "errors": [
    {
      "message": "Cannot spread fragment \\"NameAndAppearancesAndFriends\\" within itself.",
      "locations": [
        {
          "line": 11,
          "column": 5
        }
      ]
    }
  ]
}

필드를 쿼리할 때, 주어진 타입에 존재하는 필드를 쿼리해야 한다. 따라서 hero가 Character를 반환하므로, Character에 있는 필드를 쿼리해야 한다. 이 타입에는 favoriteSpaceship 필드가 없으므로 다음 쿼리는 유효하지 않다:

# INVALID: favoriteSpaceship does not exist on Character
{
  hero {
    favoriteSpaceship
  }
}

필드를 쿼리할 때마다 스칼라나 enum이 아닌 것을 반환하는 경우, 해당 필드에서 어떤 데이터를 가져올지 지정해야 한다. Hero는 Character를 반환하며, 우리는 그것에 대해 name과 appearsIn 같은 필드들을 요청해왔다. 만약 이를 생략하면 쿼리는 유효하지 않을 것이다:

# INVALID: hero is not a scalar, so fields are needed
{
  hero
}

동일한 방식으로, 만약 필드가 스칼라일 때 추가적인 필드를 요청하는 것은 말이 안되기에 유효하지 않은 쿼리로 처리된다.

# INVALID: name is a scalar, so fields are not permitted
{
  hero {
    name {
      firstCharacterOfName
    }
  }
}

이전에 쿼리는 해당 타입에 존재하는 필드만 요청할 수 있다고 했다. hero가 Character를 반환할 때, Character에 있는 필드만 요청할 수 있다. 그럼 R2-D2의 주요 기능(primary function)을 쿼리하고 싶을 때는 어떻게 해야 할까?

# INVALID: primaryFunction does not exist on Character
{
  hero {
    name
    primaryFunction
  }
}
{
  "errors": [
    {
      "message": "Cannot query field \\"primaryFunction\\" on type \\"Character\\". Did you mean to use an inline fragment on \\"Droid\\"?",
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ]
    }
  ]
}

위 쿼리는 유효하지 않다. primaryFunction은 Character의 필드가 아니기 때문이다. 우리는 Character가 Droid일 경우에만 primaryFunction을 가져오고, 그렇지 않으면 해당 필드를 무시하는 방법이 필요하다. 이를 위해 앞서 소개한 프래그먼트를 사용할 수 있다. Droid에 정의된 프래그먼트를 설정하고 이를 포함함으로써, primaryFunction이 정의된 곳에서만 쿼리할 수 있도록 보장할 수 있다.

{
  hero {
    name
    ...DroidFields
  }
}

fragment DroidFields on Droid {
  primaryFunction
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

이 쿼리는 유효하지만 다소 장황하다. 위에서 여러 번 사용할 때는 명명된 프래그먼트가 가치 있었지만, 여기서는 한 번만 사용하고 있다. 명명된 프래그먼트 대신 인라인 프래그먼트를 사용할 수 있다. 이를 통해 여전히 우리가 쿼리하고 있는 타입을 나타낼 수 있지만, 별도의 프래그먼트를 명명할 필요는 없다:

{
  hero {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

이는 검증 시스템의 표면만을 긁은 것에 불과하다. GraphQL 쿼리가 의미론적으로 의미 있는지 확인하기 위해 여러 검증 규칙이 존재한다. 사양은 "검증" 섹션에서 이 주제에 대해 더 자세히 다루고 있으며, GraphQL.js의 validation 디렉토리에는 사양을 준수하는 GraphQL 검증기를 구현하는 코드가 포함되어 있다.

출처

https://graphql.org/learn/validation/

 

Validation | GraphQL

Copyright © 2024 The GraphQL Foundation. All rights reserved. For web site terms of use, trademark policy and general project policies please see https://lfprojects.org

graphql.org

https://graphql-kr.github.io/learn/validation/

 

GraphQL: API를 위한 쿼리 언어

GraphQL은 API에 있는 데이터에 대한 완벽하고 이해하기 쉬운 설명을 제공하고 클라이언트에게 필요한 것을 정확하게 요청할 수 있는 기능을 제공하며 시간이 지남에 따라 API를 쉽게 진화시키고

graphql-kr.github.io

반응형