#3 GraphQL - Validation, Execution (번역) > 앱개발

앱개발

#3 GraphQL - Validation, Execution (번역) 정보

#3 GraphQL - Validation, Execution (번역)

본문

Validation

타입 시스템을 사용함으로써, GraphQL 질의가 유효한지 여부를 미리 결정할 수 있습니다. 이를 통해 런타임 검사에 의존하지 않고 서버와 클라이언트가 유효하지 않은 쿼리가 생성되었을 때 효과적으로 개발자에게 알릴 수 있습니다. 

 

Star Wars 예제의 경우 starWarsValidation-test.js 파일에는 다양한 무효를 입증하는 여러 쿼리가 들어 있으며 참조 구현의 유효성 검사기를 실행하기 위해 실행할 수있는 테스트 파일입니다. 

 

먼저 복잡한 쿼리를 작성해 보겠습니다. 이것은 이전 섹션의 예제와 비슷한 중첩 된 쿼리이지만 중복 된 필드는 프래그먼트로 계산됩니다. 

{

  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"

              ]

            }

          ]

        }

      ]

    }

  }

}

그리고이 쿼리는 유효합니다. 잘못된 검색어 몇 가지를 살펴 보겠습니다 ...

 

조각은 무한한 결과를 초래할 수 있으므로 조각 자체를 참조하거나 주기를 만들 수 없습니다! 위의 동일한 쿼리가 명시적으로 세 가지 수준의 중첩없이 있습니다.

{

  hero {

    ...NameAndAppearancesAndFriends

  }

}

 

fragment NameAndAppearancesAndFriends on Character {

  name

  appearsIn

  friends {

    ...NameAndAppearancesAndFriends

  }

}

{

  "errors": [

    {

      "message": "Cannot spread fragment \"NameAndAppearancesAndFriends\" within itself.",

      "locations": [

        {

          "line": 11,

          "column": 5

        }

      ]

    }

  ]

}

필드에 대답하기 위해 유형을 정의하십시오. 영웅 캐릭터를 쓰러 뜨릴 때 문자를 입력 해보십시오. 해당 유형은 favoriteSpaceship 필드가 없습니다. 

# INVALID: favoriteSpaceship does not exist on Character

{

  hero {

    favoriteSpaceship

  }

}

{

  "errors": [

    {

      "message": "Cannot query field \"favoriteSpaceship\" on type \"Character\".",

      "locations": [

        {

          "line": 4,

          "column": 5

        }

      ]

    }

  ]

}

필드를 쿼리 할 때마다 스칼라 또는 열거형이 아닌 다른 것을 반환 할 때마다 필드에서 가져올 데이터를 지정해야합니다. 영웅은 캐릭터를 반환하고 이름과 같은 필드를 요청했습니다. 이를 생략하면 쿼리가 유효하지 않습니다. 

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

{

  hero

}

{

  "errors": [

    {

      "message": "Field \"hero\" of type \"Character\" must have a selection of subfields. Did you mean \"hero { ... }\"?",

      "locations": [

        {

          "line": 3,

          "column": 3

        }

      ]

    }

  ]

}

마찬가지로, 필드가 스칼라인 경우 필드가 추가 필드를 쿼리하는 것은 의미가 없으므로 쿼리가 유효하지 않게됩니다. 

# INVALID: name is a scalar, so fields are not permitted

{

  hero {

    name {

      firstCharacterOfName

    }

  }

}

{

  "errors": [

    {

      "message": "Field \"name\" must not have a selection since type \"String!\" has no subfields.",

      "locations": [

        {

          "line": 4,

          "column": 10

        }

      ]

    }

  ]

}

이전에 쿼리는 해당 유형의 필드만 쿼리 할 수 있다는 점에 유의했습니다. Character를 반환하는 영웅을 쿼리하면 Character에 있는 필드만 쿼리 할 수 있습니다. R2-D2의 기본 기능을 쿼리하려면 어떻게해야합니까? 

# 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의 유효성 검사 디렉터리에는 사양을 준수하는 GraphQL 유효성 검사기를 구현하는 코드가 들어 있습니다. 

Execution

유효성을 확인한 후, GraphQL 쿼리는 GraphQL 서버에 의해 실행되어 요청된 쿼리의 모양 (일반적으로 JSON)을 반영한 결과를 반환합니다.

 

GraphQL은 타입 시스템없이 쿼리를 실행할 수 없습니다. 예제 시스템을 사용하여 쿼리 실행을 설명하겠습니다. 이것은 이 기사의 예제에서 사용 된 것과 동일한 유형 시스템의 일부입니다.

type Query {
  human(id: ID!): Human
}

type Human {
  name: String
  appearsIn: [Episode]
  starships: [Starship]
}

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

type Starship {
  name: String
}

쿼리가 실행될 때 일어나는 일을 설명하기 위해 예제를 사용하여 살펴 보겠습니다. 

{

  human(id: 1002) {

    name

    appearsIn

    starships {

      name

    }

  }

}

{

  "data": {

    "human": {

      "name": "Han Solo",

      "appearsIn": [

        "NEWHOPE",

        "EMPIRE",

        "JEDI"

      ],

      "starships": [

        {

          "name": "Millenium Falcon"

        },

        {

          "name": "Imperial shuttle"

        }

      ]

    }

  }

}

GraphQL 쿼리의 각 필드는 다음 유형을 반환하는 이전 유형의 함수 또는 메소드로 생각할 수 있습니다. 실제로 이것은 GraphQL의 작동 방식입니다. 각 유형의 각 필드는 GraphQL 서버 개발자가 제공하는 리졸버 (resolver)라는 함수로 뒷받침 됩니다. 필드가 실행되면 해당 리졸버가 호출되어 다음 값을 생성합니다. 

 

필드가 문자열이나 숫자와 같은 스칼라 값을 생성하면 실행이 완료됩니다. 그러나 필드가 개체 값을 생성하면 쿼리는 해당 개체에 적용되는 다른 필드 선택 항목을 포함하게 됩니다. 이것은 스칼라 값에 도달 할 때까지 계속됩니다. GraphQL 쿼리는 항상 스칼라 값으로 끝납니다. 

Root fields & resolvers

모든 GraphQL 서버의 최상위 레벨은 GraphQL API에 가능한 모든 진입 점을 나타내는 유형으로, 루트 유형 또는 쿼리 유형이라고도합니다.

 

이 예제에서 Query 유형은 인수 id를 허용하는 human 필드를 제공합니다. 이 필드의 해석자 함수는 데이터베이스에 액세스 한 다음 Human 객체를 생성하고 반환합니다.

Query: {
  human(obj, args, context) {
    return context.db.loadHumanByID(args.id).then(
      userData => new Human(userData)
    )
  }
}

이 예제는 자바 스크립트로 작성되었지만 GraphQL 서버는 다양한 언어로 작성 될 수 있습니다. 해석기 함수는 세 개의 인수를받습니다. 

 

  • obj 루트 쿼리 유형의 필드에 대해 자주 사용되지 않는 이전 객체입니다.
  • args GraphQL 쿼리의 필드에 제공된 인수입니다.
  • context (컨텍스트) 모든 결정자에게 제공되고 현재 로그인 한 사용자 또는 데이터베이스에 대한 액세스와 같은 중요한 컨텍스트 정보를 보유하는 값입니다.

 

Asynchronous resolvers

이 해석자 함수에서 어떤 일이 일어나는지 자세히 살펴 보겠습니다. 

human(obj, args, context) {
  return context.db.loadHumanByID(args.id).then(
    userData => new Human(userData)
  )
}

컨텍스트는 GraphQL 쿼리에서 인수로 제공된 ID로 사용자의 데이터를 로드하는 데 사용되는 데이터베이스에 대한 액세스를 제공하는 데 사용됩니다. 데이터베이스에서 로딩은 비동기 작업이기 때문에 Promise를 반환합니다. JavaScript에서 Promise는 비동기 값을 처리하는 데 사용되지만 동일한 개념이 여러 언어로 존재합니다 (종종 Futures, Tasks 또는 Deferred라고 함). 데이터베이스가 반환되면 새로운 Human 객체를 생성하고 반환 할 수 있습니다. 

 

해석자 함수가 Promise를 인식하고 있어야하지만 GraphQL 쿼리는 Promise를 인식 할 필요가 없습니다. 그것은 단순히 인간 필드가 그 다음에 이름을 물어볼 수 있는 어떤 것을 반환 할 것으로 기대합니다. 실행 중에 GraphQL은 Promises, Futures 및 Tasks가 완료되기 전에 완료 될 때까지 기다렸다가 계속해서 최적의 동시성을 유지합니다. 

Trivial resolvers

Human 객체를 사용할 수있게 되었으므로 GraphQL 실행은 요청된 필드를 계속 사용할 수 있습니다. 

Human: {
  name(obj, args, context) {
    return obj.name
  }
}

GraphQL 서버는 다음에 수행할 작업을 결정하는 데 사용되는 유형 시스템에 의해 구동됩니다. 휴먼 필드가 아무 것도 반환하기 전에도, GraphQL은 다음 단계는 휴먼 시스템이 휴먼 필드를 반환한다는 것을 타입 시스템이 알려주기 때문에 휴먼 타입의 필드를 해결하는 것이라는 것을 압니다.. 

 

이 경우 이름을 해석하는 것은 매우 간단합니다. 이름 해석기 함수가 호출되고 obj 인수는 이전 필드에서 리턴된 새로운 Human 오브젝트입니다. 이 경우, 우리는 Human 객체가 우리가 직접 읽고 읽을 수 있는 이름 속성을 가질 것으로 기대합니다. 

 

사실, 많은 GraphQL 라이브러리를 사용하면 리졸버를 생략 할 수 있으며, 리졸버가 필드에 제공되지 않으면 동일한 이름의 속성을 읽고 반환해야한다고 가정합니다. 

Scalar coercion

이름 필드가 해결되는 동안, appearIn 및 starships 필드를 동시에 확인할 수 있습니다. appearIn 필드는 간단한 해결자를 가질 수도 있지만 자세히 살펴 보겠습니다. 

Human: {
  appearsIn(obj) {
    return obj.appearsIn // returns [ 4, 5, 6 ]
  }
}

우리의 타입 시스템 claimIn은 알려진 값을 가진 Enum 값을 반환하지만,이 함수는 숫자를 반환합니다! 실제로 결과를 살펴보면 적절한 Enum 값이 반환되는 것을 볼 수 있습니다. 무슨 일이야? 

 

이것은 스칼라 강압의 예입니다. 타입 시스템은 예상되는 것을 알고 있으며 리졸버 함수에 의해 반환된 값을 API 계약을 유지하는 것으로 변환합니다. 이 경우 내부적으로 4, 5 및 6과 같은 숫자를 사용하지만 GraphQL 유형 시스템에서 Enum 값으로 나타내는 Enum이 서버에 정의되어 있을 수 있습니다. 

List resolvers

필드가 위의 appearIn 필드를 사용하여 목록을 반환하면 어떻게 되는지 알 수 있습니다. 그것은 열거 형 값 목록을 반환하고 그 이후에 유형 시스템에서 예상 한대로 목록의 각 항목이 적절한 열거 형 값으로 강제 변환되었습니다. starships 필드가 해결되면 어떻게 됩니까? 

Human: {
  starships(obj, args, context) {
    return obj.starshipIDs.map(
      id => context.db.loadStarshipByID(id).then(
        shipData => new Starship(shipData)
      )
    )
  }
}

이 필드의 리졸버는 약속을 반환하는 것이 아니라 약속 목록을 반환합니다. 휴먼 오브젝트에는 조종사가 지정한 우주선의 ID 목록이 있지만 실제 우주선 객체를 얻으려면 해당 ID를 모두 로드해야 합니다.

 

GraphQL은 계속하기 전에 이러한 모든 약속을 동시에 기다릴 것이며, 객체의 목록을 남겨두면 동시에 계속해서 각 항목에 이름 필드를 다시로드합니다.

공감
0

댓글 0개

전체 756 |RSS
앱개발 내용 검색

회원로그인

(주)에스아이알소프트 / 대표:홍석명 / (06211) 서울특별시 강남구 역삼동 707-34 한신인터밸리24 서관 1402호 / E-Mail: admin@sir.kr
사업자등록번호: 217-81-36347 / 통신판매업신고번호:2014-서울강남-02098호 / 개인정보보호책임자:김민섭(minsup@sir.kr)
© SIRSOFT