GraphQL 에 대해 관심이 많이 생겨나기 시작한다.
Documetation 도 공부할겸 해서 하나씩 읽어GraphQL
에 대해 알아보도록 한다.
오늘은Quries and Mutaions
까지 알아본다.
GraphQL 이란 API 를 위한 query 언어이다.
GraphQL
은 다른 특정한 database
또는 storage enting
에 결합되어 있지 않아 사용이 좋다.
이말은 db
구현시 어떠한 특정 db
에 종속되지 않으며, 널리 사용가능하다는 것이다.
db
언어에 상관없이 GraphQL
만 안다면 GraphQL
언어를 통해 언제든 query
할 수 있으므로, 여러개의 db
언어를 알 필요없어 단순한 API
구성이 가능하다.
GraphQL
을 이해하기 위해서는 가장 기본적은 query
에 대해서 알아야 한다
GraphQL
은 fields
를 가지는데. 각 fields
는 Object
안에 존재한다.
GraphQL
Query
시 다음처럼 사용한다.
아래는 GraphQL
의 Documenation
에 있는 예시이다.
{
hero {
name
}
}
위는 이러한 문법을 다음처럼 바꾸어준다.
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
이는 같은 shape
이다.
사실 보면서 이게 뭔가 싶었지만, 계속 보다보면 확실히 직관적인 부분이 있다.
여기서 name
은 String
타입을 반환한다.
위의 예시가 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"
}
]
}
}
}
여기서 중요한 부분은 GraphQL
은 Classic REST architecture
처럼 해당 data
를 받기위해 왕복하지 않고, 연관된 Object
와 fields
를 탐색하여 바로 return
해 준다는 것이다.
GraphQL
은 data fetching
시 매우 유용하며, argument
에 값을 지정하여 원하는 query
를 가능하게 만든다.
{
human(id: "1000") {
name
height(unit: FOOT)
}
}
{
"data": {
"human": {
"name": "Luke Skywalker",
"height": 5.6430448
}
}
}
graphQL
의 멋진점은 query
시 data field
자체를 변경할 수 있다는 것이다.
즉, server
에서 data
를 받아올때 alias
를 지정하여 전혀 새로운 명칭으로 변경 가능하다.
{
empireHero: hero(episode: EMPIRE) {
name
}
jediHero: hero(episode: JEDI) {
name
}
}
{
"data": {
"empireHero": {
"name": "Luke Skywalker"
},
"jediHero": {
"name": "R2-D2"
}
}
}
위의 예시는 충돌되는 hero
를 empireHero
와 jediHero
로 변경한 것이다.
이렇게 받아오는field
의 명칭(Alias
) 를 client
단에서 쉽게 변경하여 사용가능하다.
Fragments
는 재사용을 위해 사용된다.
Documentation
에서는 복잡한 query
시 쉽게 사용하기 위해 Fragments
를 사용하라고 한다. 만약, 공통적으로 사용되는 field
가 있다고 하면, 해당 field
를 Fragments
로 만든이후, query
를 위한 Object
에 Framents
를 사용하여 간단한 문장으로 query
가 가능하다.
다음 예시를 보면 쉽게 이해할 수 있다.
{
leftComparison: hero(episode: EMPIRE) {
...comparisonFields
}
rightComparison: hero(episode: JEDI) {
...comparisonFields
}
}
fragment comparisonFields on Character {
name
appearsIn
friends {
name
}
}
이는 Alias
와 Framents
를 사용하여 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
는 각 leftCoparison
과 rightComparison
으로 나누어지며,
Character Type
을 가진 comparisonFields
Fragment
를 사용하여 공통된 fields
를 나열했다.
굉장히 깔끔하게 정리되는 것을 볼 수 있다.
여기서 fragment
를 사용할 때, ...
notaion
을 사용한것을 보면, 마치 Javascript
의 spread operator
를 연상시켜, 쉽게 이해할 수 있을것 같다.
이러한 방식은 복잡한 것을 작은 chunks
로 쪼개서 사용가능하고, 많은 UI components
에 결합하는데 효과적이므로 필요하다고 설명한다.
그냥 봐도 굉장히 유용해 보인다.
query
시 query 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
, mutation
및 subscription
에 대해 지정한 유형의 작업을 수행한다.
마치 이는 function
처럼, 이름을 지정하고, 정해진 로직을 실행하는 것 처럼 행동한다.
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"
}
]
}
}
}
위의 예시를 보면 알 수 있듯이 $episode
에 variables
를 JEDI
를 주어 query
한다.
이는 마치 function
에 argument
를 주는듯하여 쉽게 이해할 수 있다.
이때 중요한 부분은 operator keyword
operator name
($variable: Type)
순으로
variables
의 Type
을 지정한후, {}
내부에 해당 타입을 지정한 $variable
을 사용하여 인자의 타입으로 준다는 것이다.
Documentation
에서는 이렇게 $variable
을 주는 행위를 Variable difinitions
라 한다.
variables
설정시 default variables
를 설정가능하다.
마치 function
의 argument
에 default value
를 주는것과 비슷하다.
query HeroNameAndFriends($episode: Episode = JEDI) {
hero(episode: $episode) {
name
friends {
name
}
}
}
만약, episode
에 variable
을 주면, 기존의 default variables
를 override
하여 적용된다.
만약 $withFriends
가 true
일때, 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
}
여기서 withFriends
가 false
인것을 알 수 있다.
즉, @include(if: false)
가 되어 friends
는 제외되어 출력된다.
{
"data": {
"hero": {
"name": "R2-D2"
}
}
}
directives
를 사용한다면 특정 조건에 맞는 field
를 가져오기에 수월할 듯 보인다.
현재 보고있는 Document page
에서는 2개의 directive
를 소개해준다.
@include(if: Boolean)
: 만약argument
가true
라면 결과에 해당field
를 포함한다.
@skip(if: Boolean)
: 만약argument
가true
라면 해당field
를skip
한다.
data write
를 위한 grapyQL
의 operation
이다.
query
가 read
라면, mutaion
은 creact, update, delete
관련 operation
이다.
이는 operation name
을 사용하여 query
를 보냈을때와 같으며, operation keyword
만 mutaion
으로 변경하여 사용하면 된다.
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!"
}
}
mutaions
는 update
이후 새상태를 가져오는데 유용하게 사용될 수 있다.
다음을 보면 mutaions
을 통해 create
된 이후 해당 관련된 값을 return
해주는것을 알 수 있다
{
"data": {
"createReview": {
"stars": 5,
"commentary": "This is a great movie!"
}
}
}
여기서 생성된 stars
와 commentary
는 새롭게 생성된 review
의 field
이다.
앞써서 본 Fragments
를 기억할 것이다.
해당 Fragments
는 Character Type
을 이용하여 지정된 타입을 ...
spread
연산자와 비슷한 방식으로 field
를 구성했다.
그럼, 만약, Fragments
가 Character Type
을 기반으로하여 field
를 구성했다면, 귀찬게 Fragments
를 선언하지 않고 이미 주어진 Type
을 바로 ...
로 사용한다면 되지 않을까?
역시나 GraphQL
은 가능하게 만들었다.
Fragments
를 선언하지 않고, Type
을 바로 사용하여 처리한다고 해서 inline Fragments
라고 명명된듯 하다.
이때 중요한것은 Union
타입을 사용하여, 해당 query
의 Type
이 지정되어 있고, Union
타입에 포함되어진 Type
을 inline 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"
}
}
}
위의 HeroForEposiode
의 type
은 Character
이다.
이때, Droid
에서 ...
로 label
이 지정되어 있기 때문에, hero
에서 반환된 Character
가 Droid
유형인 경우에만 primaryFunction field
가 실행된다.
여기서 의문인것은 Human
타입인데... 여기서 height
값이 선택되지 않았다.
이말은 Human
에 Height
값이 Character
의 Type
이 아니기에 선택이 되지 않은것 같다.
이부분은 해석상 명확하게 확인되지 않고있다. 다시 일고 확인해보아야 겠다.
일단 여기까지 오늘은 마무리하고, 내일은 Schemas and Types
를 읽고 내용을 정리하도록 해야겠다.