• 이번에도 TIL
    궁금했는데 GraphQL 도 사용중이어서 간단하게만
    기존에 나는 Rest API 를 사용했으니까 얘랑 비교하면서 파악해보았다
    • 새로운 기술을 기존에 사용하던거랑 연관지어서 보니까 빠르게 이해되어 좋았음


REST vs GraphQL :: 응답 구조의 주도권

Query : 시스템에 대해 무엇을 얻고싶은지를 선언하는 요청명세

  • ex) 특정 조건을 만족하는 데이터를 줘

=> Rest API, GraphQL 둘다 쿼리방식임
차이는? 누가 응답구조를 정하는가


  • Rest 는 이 조건을 만족하는 데이터를 서버가 미리 정의한 응답구조 그대로 받음
  • GraphQL 은 그 데이터 중에 이 필드들만(서버가 미리 정의한 데이터 중에 원하는 데이터만) 이 구조(어떤 필드를 어떤 객체안에 어떤 계층으로 담을지 정해서)로 줘 로 요청함

SQL처럼 데이터를 어떻게 가져올지 서술 할 수 있어서 쿼리 언어라고 부름
REST도 쿼리를 하긴 하지만 쿼리 언어는 아님
(이 차이가 over-fetching/under-fetching 문제 해결의 핵심)



GraphQL :: 객체 그래프 기반 데이터 조회 방식

GraphQL : 객체 그래프 탐색언어

  • 시작점도 객체 (Query, Mutation)
  • 결과도 객체여야

=> 쿼리 실행 = 객체 그래프 순회



GraphQL Engine

쿼리 해석기 + 실행기
(쿼리 파싱 -> 스키마로 검증 -> 필드별 resolver 매핑 -> resolver 순회실행)

SQL Engine 처럼 요청해석, 실행계획 만들기, 결과조립 하는데 대상이 DB가 아니라 서버의 객체 그래프


GraphQL Engine
  ㄴ Resolver    
    ㄴ Service / Client    
      ㄴ Repository(JPA)    
        ㄴ MySQL    
  • REST에서는 Controller → Service → Repository → DB
  • GraphQL에서는 Query/Mutation Resolver → Service → Repository → DB

Resolver가 Controller 역할을 대신하되, 필드 단위로 세밀하게 동작한다는 차이가 있음



schema 정의

Rest 는 api 명세서가 필수가 아니었고 따로 관리해야했는데(버전 안맞으면 문제) GraphQL 은 필수이고 서버가 실시간으로 스키마 정보를 제공함
=> GraphQL Engine 이 schema 보고 검증함


주요 타입 정의

  • input : request dto 느낌
  • type : 반환 가능한 객체타입 정의
    • type Query : 조회쿼리 진입점 (api 목록 정리된, controller 역할)
    • type Mutation : 변경쿼리 진입점
    • type Custom객체 : 중첩된 객체 타입 정의


=> Query, Mutation 도 반환가능한 객체타입인 이유?


객체 그래프 순회 과정

  • ex)

    query {
      user(id:1){
        name
        team{
          name
        }
      }
    }
    
    • 엔진의 실행 순서
      • Query 객체에서 user 필드 탐색
      • user 필드의 resolver 실행 -> User 객체 획득(반환)
      • User 객체에서 name 필드 resolver 실행
      • User 객체에서 team 필드 resolver 실행 -> Team 객체 획득
      • Team 객체에서 name 필드 resolver 실행
      • 최종 Json 조립

    = 엔진이 쿼리를 트리구조로 해석해서 필드단위로 내려가며(재귀적으로) resolver 를 순차 실행하여 최종 Json 만듬
    이 과정에서 Query/Mutation도 하나의 객체로 취급되어야 일관된 순회가 가능



Operation Name (쿼리 함수화)

실무에서는 하드코딩된 값 (ex) id:1) 안쓰고 동적으로 값을 넣다보니 Operation Name 쿼리를 사용함
variables 라는 parameter에 원하는 값을 넣어주면 됨

  • 익명 쿼리 (간단할 때)

    {
      user(id: 1) {
        name
      }
    }
    


  • Operation Name 쿼리 : 쿼리용 함수 (DB의 Procedure처럼 쿼리를 함수처럼 사용)

    query GetUser($userId: ID!) {
      user(id: $userId) {
        name
      }
    }
    
    • 변수 사용 가능해서 동적으로 값 전달 가능 - 프론트엔드 코드에서 variables: { userId: "123" } 로 전달
    • 클라이언트가 작성/관리 (REST는 백엔드가 API 만들어줌)


Resolver의 4가지 인자

Query: {
  user: async (parent, args, context, info) => {
    // parent: 부모 resolver가 리턴한 객체
    // args: 쿼리에서 입력한 인자 { userId: "123" }
    // context: 모든 resolver에 전달되는 공통 정보 (로그인 정보, 권한 등)
    // info: 스키마 정보 (잘 안씀)
    return await User.findById(args.userId)
  }
}



💡

매번 API 버전관리가 비효율적(deprecated 처리해두고 추후 삭제 등)이라고 생각했는데, 백엔드 배포없이 프론트엔드쪽에서 필요한 필드를 추가, 제거 할 수 있어 API 버전 관리의 부담이 줄어들것 같다
모바일이랑 웹에서 데이터가 다르게 필요하거나 할때도 유용할것 같고
근데 한번 배포된 스키마는 하위 호환성 유지해야해서 본질적인 제약은 여전히 존재함,,


지금은 admin tool 에 graphQL 이 도입되어 있는데 admin tool 특성상 웹만 있기도 하고 백엔드개발자가 화면부터 api 까지 다 관리하다보니까 장점이 크게 체감되지는 않았다


GraphQL의 장점은

  • 프론트/백엔드 팀이 분리되어 있고
  • 같은 데이터를 여러 클라이언트(웹/모바일/파트너사)가 소비하는 환경

에서 잘 느껴질것 같다
다만 객체 그래프 탐색 방식 특성상 또 N+1 주의가 필요할듯함


업데이트: