- 이번에도 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 주의가 필요할듯함