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 를 읽고 내용을 정리하도록 해야겠다.