GraphQL 정리

Jhoon·2023년 2월 6일
0

GraphQL 에 대해 관심이 많이 생겨나기 시작한다.
Documetation 도 공부할겸 해서 하나씩 읽어 GraphQL 에 대해 알아보도록 한다.
오늘은 Quries and Mutaions 까지 알아본다.

GraphQL 이란

GraphQL 이란 API 를 위한 query 언어이다.

GraphQL 은 다른 특정한 database 또는 storage enting 에 결합되어 있지 않아 사용이 좋다.

이말은 db 구현시 어떠한 특정 db 에 종속되지 않으며, 널리 사용가능하다는 것이다.

db 언어에 상관없이 GraphQL 만 안다면 GraphQL 언어를 통해 언제든 query 할 수 있으므로, 여러개의 db 언어를 알 필요없어 단순한 API 구성이 가능하다.

GraphQL 을 이해하기 위해서는 가장 기본적은 query 에 대해서 알아야 한다

Queries and Mutaions

Fields

GraphQLfields 를 가지는데. 각 fieldsObject 안에 존재한다.
GraphQL Query 시 다음처럼 사용한다.

아래는 GraphQLDocumenation 에 있는 예시이다.

{
	hero {
		name    
    }
}

위는 이러한 문법을 다음처럼 바꾸어준다.

{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

이는 같은 shape 이다.
사실 보면서 이게 뭔가 싶었지만, 계속 보다보면 확실히 직관적인 부분이 있다.

여기서 nameString 타입을 반환한다.
위의 예시가 Object 의 형태를 띄고 있으니 당연히 sub fields 도 존재할 것이다.

{
  hero {
    name
    # Queries can have comments! <-- comment 역시 사용가능한듯 싶다.
    friends {
      name
    }
  }
}

이는 다음과 같다.

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

여기서 중요한 부분은 GraphQLClassic REST architecture 처럼 해당 data 를 받기위해 왕복하지 않고, 연관된 Objectfields 를 탐색하여 바로 return 해 준다는 것이다.

Arguments

GraphQLdata fetching 시 매우 유용하며, argument 에 값을 지정하여 원하는 query 를 가능하게 만든다.


{
  human(id: "1000") {
    name
    height(unit: FOOT)
  }
}
{
  "data": {
    "human": {
      "name": "Luke Skywalker",
      "height": 5.6430448
    }
  }
}

Alias

graphQL 의 멋진점은 querydata field 자체를 변경할 수 있다는 것이다.
즉, server 에서 data 를 받아올때 alias 를 지정하여 전혀 새로운 명칭으로 변경 가능하다.

{
  empireHero: hero(episode: EMPIRE) {
    name
  }
  jediHero: hero(episode: JEDI) {
    name
  }
}
{
  "data": {
    "empireHero": {
      "name": "Luke Skywalker"
    },
    "jediHero": {
      "name": "R2-D2"
    }
  }
}

위의 예시는 충돌되는 heroempireHerojediHero 로 변경한 것이다.
이렇게 받아오는field 의 명칭(Alias) 를 client 단에서 쉽게 변경하여 사용가능하다.

Fragments

Fragments 는 재사용을 위해 사용된다.
Documentation 에서는 복잡한 query 시 쉽게 사용하기 위해 Fragments 를 사용하라고 한다. 만약, 공통적으로 사용되는 field 가 있다고 하면, 해당 fieldFragments 로 만든이후, query 를 위한 ObjectFraments 를 사용하여 간단한 문장으로 query 가 가능하다.

다음 예시를 보면 쉽게 이해할 수 있다.

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

fragment comparisonFields on Character {
  name
  appearsIn
  friends {
    name
  }
}

이는 AliasFraments 를 사용하여 Query 한 예시이며, 아래처럼 출력된다고 한다.

{
  "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"
        }
      ]
    }
  }
}

hero 는 각 leftCoparisonrightComparison 으로 나누어지며,
Character Type 을 가진 comparisonFields Fragment 를 사용하여 공통된 fields 를 나열했다.

굉장히 깔끔하게 정리되는 것을 볼 수 있다.
여기서 fragment 를 사용할 때, ... notaion 을 사용한것을 보면, 마치 Javascriptspread operator 를 연상시켜, 쉽게 이해할 수 있을것 같다.

이러한 방식은 복잡한 것을 작은 chunks 로 쪼개서 사용가능하고, 많은 UI components 에 결합하는데 효과적이므로 필요하다고 설명한다.

그냥 봐도 굉장히 유용해 보인다.

Operation name

queryquery name 을 명확히 작성할 필요가 있다.

query HeroNameAndFriends {
  hero {
    name
    friends {
      name
    }
  }
}

밑은 이에대한 결과물이다.

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

operation name 을 사용하면, query, mutationsubscription 에 대해 지정한 유형의 작업을 수행한다.

마치 이는 function 처럼, 이름을 지정하고, 정해진 로직을 실행하는 것 처럼 행동한다.

Variables

graphQL 은 변수를 받을 수 있는데, 이 변수를 받아서 operation name 에 원하는 query 를 하는데 사용한다.

다음을 보면 쉽게 이해할 수 있다

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

다음은 반환된 data 이다.

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

위의 예시를 보면 알 수 있듯이 $episodevariablesJEDI 를 주어 query 한다.

이는 마치 functionargument 를 주는듯하여 쉽게 이해할 수 있다.

이때 중요한 부분은 operator keyword operator name ($variable: Type) 순으로
variablesType 을 지정한후, {} 내부에 해당 타입을 지정한 $variable 을 사용하여 인자의 타입으로 준다는 것이다.

Documentation 에서는 이렇게 $variable 을 주는 행위를 Variable difinitions 라 한다.

Default variables

variables 설정시 default variables 를 설정가능하다.
마치 functionargumentdefault value 를 주는것과 비슷하다.

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

만약, episodevariable 을 주면, 기존의 default variablesoverride 하여 적용된다.

Directives

만약 $withFriendstrue 일때, friends field 를 포함할수 있도록 만드는 방법은 없을까?

이때 사용하는 방법이 Directives 이다.
Directives 를 사용하면, 어떠한 조건일때 field 를 포함할지 말지를 결정할 수 있다.
다음은 Directives 중 하나인 @include(if: Boolean) 을 사용한 예이다.

query Hero($episode: Episode, $withFriends: Boolean!) {
  hero(episode: $episode) {
    name
    friends @include(if: $withFriends) {
      name
    }
  }
}

이때 definition variables 는 다음과 같다.

// variables

{
  "episode": "JEDI",
  "withFriends": false
}

여기서 withFriendsfalse 인것을 알 수 있다.
즉, @include(if: false) 가 되어 friends 는 제외되어 출력된다.

{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}

directives 를 사용한다면 특정 조건에 맞는 field 를 가져오기에 수월할 듯 보인다.
현재 보고있는 Document page 에서는 2개의 directive 를 소개해준다.

@include(if: Boolean): 만약 argumenttrue 라면 결과에 해당 field 를 포함한다.

@skip(if: Boolean): 만약 argumenttrue 라면 해당 fieldskip 한다.

Mutations

data write 를 위한 grapyQLoperation 이다.
queryread 라면, mutaioncreact, update, delete 관련 operation 이다.

이는 operation name 을 사용하여 query 를 보냈을때와 같으며, operation keywordmutaion 으로 변경하여 사용하면 된다.

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

mutaionsupdate 이후 새상태를 가져오는데 유용하게 사용될 수 있다.
다음을 보면 mutaions 을 통해 create 된 이후 해당 관련된 값을 return 해주는것을 알 수 있다

{
  "data": {
    "createReview": {
      "stars": 5,
      "commentary": "This is a great movie!"
    }
  }
}

여기서 생성된 starscommentary 는 새롭게 생성된 reviewfield 이다.

inline Fragments

앞써서 본 Fragments 를 기억할 것이다.
해당 FragmentsCharacter Type 을 이용하여 지정된 타입을 ... spread 연산자와 비슷한 방식으로 field 를 구성했다.

그럼, 만약, FragmentsCharacter Type 을 기반으로하여 field 를 구성했다면, 귀찬게 Fragments 를 선언하지 않고 이미 주어진 Type 을 바로 ... 로 사용한다면 되지 않을까?

역시나 GraphQL 은 가능하게 만들었다.
Fragments 를 선언하지 않고, Type 을 바로 사용하여 처리한다고 해서 inline Fragments 라고 명명된듯 하다.

이때 중요한것은 Union 타입을 사용하여, 해당 queryType 이 지정되어 있고, Union 타입에 포함되어진 Typeinline Fragment 로 사용한다는 것이다.

다음을 보자.

query HeroForEpisode($ep: Episode!) {
  hero(episode: $ep) {
    name
    ... on Droid {
      primaryFunction
    }
    ... on Human {
      height
    }
  }
}
// variables
{
  "ep": "JEDI"
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

위의 HeroForEposiodetypeCharacter 이다.
이때, Droid 에서 ...label 이 지정되어 있기 때문에, hero 에서 반환된 CharacterDroid 유형인 경우에만 primaryFunction field 가 실행된다.

여기서 의문인것은 Human 타입인데... 여기서 height 값이 선택되지 않았다.
이말은 HumanHeight 값이 CharacterType 이 아니기에 선택이 되지 않은것 같다.

이부분은 해석상 명확하게 확인되지 않고있다. 다시 일고 확인해보아야 겠다.

일단 여기까지 오늘은 마무리하고, 내일은 Schemas and Types 를 읽고 내용을 정리하도록 해야겠다.

profile
익숙해지면 다 할수 있어!!

0개의 댓글