[GraphQL] GraphQL이란?

koline·2023년 11월 14일
0

GraphQL

목록 보기
1/8

GraphQL


GraphQL은 페이스북에서 만든 쿼리 언어로 Facebook, GitHub, Pinterest 등 다양한 서비스가 GraphQL을 사용하고 있으나, 국내에는 아직 GraphQL을 사용한 Open API는 많지않다.

이름에서 알 수 있듯이 Query Language인데 이 때문에 SQL과 같은 Database 질의 언어로 오해할 수 있으나, 백엔드 API 서버에 질의를 보내는 언어이다. 즉, GraphQL은 SQL 보다는 오히려 REST API와 더 유사하다고 할 수 있다.




REST 방식과의 차이


일반적으로 RESTful API라고 하면 명사로 이뤄진 URL(Endppoint)로 서비스를 구분하고 HTTP 메소드를 사용하여 요청을 보내면 해당 URI와 매핑된 메소드가 요청을 처리하는 형태의 서비스 방식을 REST 방식이라고 하는데 GraphQL의 가장 큰 차이는 바로 Endpoint가 하나라는 점이다.

GraphQL API에 요청을 보낼 때는 주로 /graphql 로 설정된 GraphQL 서버 주소로 요청을 보내는데, Query 또는 Mutation의 요청을 보낸다. 여기서 Query는 데이터를 조회하는 요청으로 REST 방식의 GET 요청과 유사하고, Mutation 요청은 데이터를 생성, 수정, 삭제 하는 요청으로 POST(PATCH/PUT/DELETE) 요청과 유사하다.

또 다른 GraphQL 방식의 가장 큰 특징중 하나는 요청 후 응답받을 데이터를 client 측에서 정할 수 있다는 것이다. GraphQL server로 요청을 보낼 때는 요청이 query인지 mutation인지와 함께 요청명과 리턴받을 필드를 보내게 되는데, 그러면 서버는 해당 요청을 처리한 후 요청된 필드에 해당하는 값을 JSON 형태로 리턴해주게 된다.




Query와 Mutation


GraphQL에서 요청은 앞서 언급했듯이 QueryMutation 두가지 종류가 있다.

Query

데이터를 조회하는 요청으로 REST 방식의 GET 요청과 유사하다.

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

위와 같은 형태로 작성하는데 query는 해당 요청이 query임을 나타내며 생략해도 무관하다. (생략될 경우 query인 것으로 인식한다.) 그 다음으로 오는 HeroNameAndFriends는 요청의 이름(operation name)이다. 이 또한 없어도 무관하다. 이 부분이 흥미로운데 operation name은 백엔드의 API 설계와 무관하게 프론트엔드에서 작성하고 관리할 수 있다. 즉, Schema의 형태만 지키면 REST 방식에 비해 API에 대한 의존도가 많이 내려가게 된다.

다음으로 나오는 ($episode: Episode)는 변수이다 달러표시가 붙은 $episode는 변수명이고 그 뒤에 나오는 Episode는 해당 변수의 타입을 나타낸다. 그리고 이 오퍼레이션 안에 들어있는 hero가 server-side의 resolver와 매핑되는 실제 쿼리이다. 즉 쿼리는 직접 파라미터를 받는게 아니라 오퍼레이션에게 파라미터를 전달하고 쿼리는 오퍼레이션이 전달받은 파라미터를 가져다 형태이다.

이러한 구조는 한번에 여러개의 쿼리를 전송하기 유용하게 만든다.

query getStudentInfomation($studentId: ID!, $year: Int!, $schoolCode: String){
  personalInfo(studentId: $studentId) {
    name
    address1
    address2
    major
  }
  classInfo(year: $year, studentId: $studentId) {
    classCode
    className
    teacher {
      name
      major
    }
    classRoom {
      id
      maintainer {
        name
      }
    }
  }
  SATInfo(schoolCode: $schoolCode, studentId: $studentId) {
    totalScore
    dueDate
  }
}

위와 같은 요청을 보내면 한번의 요청으로 personalInfo, classInfo, SATInfo 3개의 쿼리를 보낼 수 있다. $studentId와 같이 복수의 쿼리에 사용되는 변수도 한번에 전달해줄 수 있다.

참고로 변수 타입 뒤에 붙은 !(느낌표)는 해당 변수는 필수로 전달 되어야 함을 나타내는데 server-side의 resolver에서 해당 파라미터를 필수로 전달 받게 설계했다면, client-side에서 요청을 보낼 때 해당 파라미터를 보내더라도 operation에 느낌표를 붙임으로써 필수라는 것을 나타내지 않는다면 에러가 난다.

client-side에게 자유도를 주고 API 의존도를 낮추지만 schema에 관해서는 철저하게 지켜져야하는 느낌이다.

변수를 동적으로 할당하여 쿼리의 구조와 형태를 동적으로 변경하는 방법도 있다.

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

// @include(if: Boolean) : 인자가 true 인 경우에만 이 필드를 결과에 포함
// @skip(if: Boolean) : 인자가 true 이면 이 필드를 건너뜀

Mutation

데이터를 생성, 수정, 삭제 하는 요청으로 REST 방식의 POST, PATCH, PUT, DELETE 요청을 합친 요청이다.

Query와 기본적인 형태는 똑같다.

mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    stars
    commentary
  }
}

Mutation에서는 mutation 요청임을 반드시 명시해야하며, 변수의 사용법과 return 받을 필드를 입력하는 방식은 동일하다.




TypeDefs


Typescript와 마찬가지로 사용할 객체를 정의해줘야 하는데 이를 위해 TypeDef를 작성해준다. 그리고 정의한 type을 사용하는 QueryMutation들을 선언하는데 Java의 추상메서드와 비슷하다. 이렇게 typeQuery/Mutation을 선언한 것을 TypeDefs라고 한다.

type Character {
  name: String!
  appearsIn: [Episode]!
}

위와 같이 type을 정의해주면 Character 타입의 JSON Data를 파라미터로 받거나 return해줄 수 있게된다. 여기서도 마찬가지로 !(느낌표)가 붙은 필드는 해당 타입에서 Null이 되면 안되는 값이고 대괄호[] 사이에 둠으로써 배열임을 나타낸다.

그리고 위에서 정의한 Character 타입을 사용한 QueryMutation을 아래와 같이 정의한다.

type Query {
	character(id: Int!): Character
    allCharacters: [Character]
}

type Mutation {
	createCharacter(name: String!, appearsIn: [ID!]!): Character
}

이런식으로 작성해주면 된다.

참고로 정의된 Type이 아닌 Int나 String, ID 와 같은 기본적으로 제공되는 변수 타입들이 있는데 이를 Scalar라고 한다. Int, Boolean, ID, String, Float 가 기본 스칼라로 제공되는데 Date와 DateTime이 없어서 얘네는 custom scalar로 만들어야한다. 공식 문서

참고로 GraphQL의 엄청난 점중 하나는 Documentation기능인데

"""
Character 관련 Query
"""
type Query {
  	"""
  	단일 Character 조회
  	"""
	character(id: Int!): Character

    """
  	모든 Character 목록 조회
  	"""
    allCharacters: [Character]
}

이런 식으로 작성해주면 graphql Playground에서 내용을 쉽게 확인 가능하다. 설명 포스트 참조




Resolver


TypeDef에서 선언한 QueryMutation을 구현한 구현체를 Resolver라고 한다.

Query: {
  character: async (_, { id }, context) => {
    return await Character.findOne({ where: { id } })        
  },
  allCharacters: async (_, __, context) => {
    return await Character.findAll()        
  }, 
},
Mutation: {
  createCharacter: async (_, { id, appearsIn }, context) => {
    return await Character.create({ 
    	id,
      	appearsIn
    })
  }
}

이렇게 TypeDef에 선언된 것들을 정의해주면 된다.




결론


최대한 간단하게 정리해봤는데 사용이 편리하고 효율적이라는게 느껴지긴한다. 하지만 모든 기술이 그렇듯 실제로 GraphQL을 사용해서 "잘" 개발하기는 어려울 것 같다. 기존의 REST 방식과는 정말 많이 다르고 코드 구현방식 부터해서 Back-End - Front-End 협업 방식까지 많이 다를것 같다. 대표적으로 Apollo 라이브러리를 함께 활용하는 듯 한데, GraphQL과 Apollo 자체가 언어의 제약없이 사용이 가능하다고는 하지만 Express 서버와 연동한 apollo-server-express가 가장 많이 사용되는 것으로 보인다. (참고할 자료가 가장 많다.) Apollo 공식 문서에서도 server 쪽은 주로 Typescript로 예시가 작성되어 있다.

profile
개발공부를해보자

0개의 댓글

관련 채용 정보