본문으로 건너뛰기

graphql 장점

· 약 12분
Jake Son

이번 글에서는 Node.js 환경에서 REST API 와 GraphQL 을 사용해보면서 느낀점과 GraphQL 을 선호하는 이유를 작성하려고 한다.
먼저 나의 이야기를 해보자면 개발자로 전직 후 첫 직장에서는 GraphQL 을 사용하다가 REST 를 사용하는 곳으로 이직하였다.
GraphQL 사용 경험이 좋았기에 다시 REST 를 한다는 점에는 아쉬웠고 실제로 여러 불편한점을 겪었다.
그때마다 GraphQL 을 사용했다면 어떻게 불편한점이 해결되었을까 생각하곤 했었고 이번에 그 내용을 적어보려고 한다.

기술에 대한 선호는 사람마다 다르기에 이번 글을 주관적인 내용을 담고있습니다.

API Spec 정하기

보통 REST 환경에서는 어떤 동작을 수행하거나 데이터를 요청하려면 다음 항목에 대한 설계가 필요하다.

  • HTTP method
  • path
  • body 또는 querystring 형태

경험상 다음과 같은 상황에서 개발자마다 서로 다른 생각을 가지는 경우가 많았다.

  • 언제 PATCH 를 사용하고 PUT 을 사용할지
  • resource 표현을 단수로 할지 복수로 할지
  • id 에 대한 값을 path, querystring, body 중 어디에 넣을지

물론 검색해보면 여러 가이드 문서가 존재하지만 이는 말 그대로 가이드일뿐 지켜야 할 강제성이 없다.
하지만 GraphQL 에서는 위와같은 선택의 문제가 발생하지 않는다.

  • 모든 요청은 GET 또는 POST 로 이루어진다. (대부분 POST 로 요청하며 FE 개발 시 둘 중 어느것을 선택해야 하는지 신경쓰지 않고 코드를 작성한다)
  • path 도 단일 endpoint 로 이루어진다. (보통 /graphql)
  • 모든 parameter 데이터는 body 에 들어간다.
  • 조회 API 는 Query 필드 아래에 수정 API 는 Mutation 필드 아래에 들어간다.

물론 GraphQL 에서도 필드명을 어떻게 가져가야 할지에 대한 논의가 필요하지만 REST 에 비해서 선택해야 하는 가짓수가 훨씬 적다.
특히 필드명의 경우 FE 에서 다른 이름으로 바꾸길 원하는 경우 GraphQL 에서는 BE 의 작업없이 alias 를 활용할 수 있다는 장점이 있다.

query getPosts {
# posts 대신 myPosts 로 변경
myPosts: posts(id: 1) {
postId: id # id 대신 postId 로 변경
createdAt
}
}

문서화

처음 정한 API 스팩은 개발중에 언제든 변경될 가능성이 존재하며 운영 배포 이후에도 새로운 요구사항으로 인해 변경될 수 있다.
API 명세서를 문서로 관리하는 경우 모든 문서가 그렇듯 시간이 지나면 최신화가 제대로 되지않는 현상이 발생한다.
특히 BE 에서 변경한 사항을 FE 에게 전달하는 것을 빠트린 경우 이를 알아차리는 시기가 QA 나 심각한 경우 운영 배포 이후가 될 수 있다.

GraphQL 은 API 스팩을 하나의 schema 파일로 유지하고 스팩이 변경되면 해당 파일은 자동으로 동기화가 이루어지기에 최신화의 문제에서 자유롭다.
특히 graphql-code-generator 를 활용하면 FE 에서 schema 의 변경이 기존 작성한 코드에 영향을 주는지 검증할 수 있기에 더 안전한 개발이 가능하다.

GraphQL 스키마에는 주석을 넣을 수 있고 @depreacted 같은 directive 를 통해 특정 필드에 대한 설명을 넣거나 폐기여부를 표시할 수 있다.
이는 API 명세를 개발자가 IDE 내에서 확인할 수 있게 만들어준다.
예를들면 다음과 같은 schema 가 있다고 가정해보자.

"""
여기에 주석
"""
type User {
id: ID!
username: String!
email: String! @deprecated(reason: "Use `socialEmail`.") # deprecated 표시
socialEmail: String!
}

generate 후에는 다음과 같은 타입을 만들어주며 IDE 에서 User 필드에 대한 설명을 볼 수 있고 email 필드 사용 시 취소선으로 표시해준다.

/** 여기에 주석 */
export type User = {
__typename?: "User";
id: Scalars["ID"];
username: Scalars["String"];
/** @deprecated Use `socialEmail`. */
email: Scalars["String"];
socialEmail: Scalars["String"];
};

Type safe

위에서 소개한 graphql-code-generator 를 활용하면 FE 에서 TypeScript 환경에서 개발 시 API 스팩에 대한 타입 선언과 서버로 요청 시 필요한 파라미터들에 대한 선언을 자동 생성할 수 있다는 장점이 있다.
이는 API 와 타입 선언간의 동기화 작업같은 단순한 작업은 컴퓨터에게 맡기고 오직 로직구현에만 집중할 수 있게 만들어준다.
또한 Enum 타입을 지원하기에 어떤 열거형 값을 string 으로 선언해서 사용하는 것보다 리팩토링이 편하고 사용하는 곳 추적이 용이하다.
BE 에서는 요청값의 검증로직을 작성할 때 타입에 대해서는 신경쓰지 않아도 되는 장점이 있다.
예를들어 class-transformer 를 사용한다면 @IsString, @IsInt 같은 타입검증 데코레이터는 넣어줄 필요가 없으며 null 확인 또한 타입 자체를 required 로 선언만 하면 된다.

@ObjectType()
export class Author {
@Field((type) => Int) // Int 타입이며 required
id: number;

@Field({ nullable: true }) // string 타입이며 nullable
name?: string;
}

이는 타입검증에 대한 로직은 graphql 서버에게 위임하고 테스트 코드 작성시 입력값 검증에 대해서 REST 보다 더 적은 케이스만 고려해 작성해도 된다는 장점이 있다.
GraphQL 은 입력값 뿐만 아니라 응답값도 검증을 하는데 예를들어 String 타입으로 선언된 필드가 런타임에서 null 이나 number 를 반환하는 경우 에러가 발생한다.

@ObjectType()
export class Response {
@Field()
get name(): string {
return 123 as any; // error
}
}

반면 REST 에서는 런타임에서 전혀 다른 타입을 반환해도 에러가 발생하지 않는다.
선언된 타입과 다른 타입을 반환하는 경우가 많이 발생하지 않지만 not null 로 기대하는 필드가 런타임에서 null 로 오는 경우는 자주 발생한다.
GraphQL 에서는 not null 로 선언된 타입은 런타임에서도 절대 null 로 오지 않음을 보장하므로 FE 는 API 스팩을 믿고 로직을 작성할 수 있다.

Versioning

REST 에서는 버전이 크게 바뀌는 경우에 대비해 주로 path 에 버전정보를 넣어 하위 호환성을 대응한다.
새로운 버전을 만드는 경우 기존 버전의 파일을 복사해 수정하게 되고 상황에 따라 많은 코드를 중복으로 생성하게 된다.
시간이 지나 기존 버전을 더이상 사용하지 않는 상황이 와도 기존 코드를 삭제하는 것을 깜박하는 경우가 많고 죽은 코드로 남게되는 문제가 발생하게 된다.
GraphQL 은 클라이언트가 요청하는 필드에 대해서만 응답을 내려주는 방식이므로 버저닝에 대한 필요가 크게 발생하지 않는다. BE 에서는 기존 필드에 deprecated 표시를 하고 새로운 필드만 추가하면 되며 FE 에서는 요청 body 에 기존 필드는 지우고 새로운 필드만 지정하면 된다.
기존 필드명을 그대로 사용하고 싶다면 alias 를 활용할 수 있다.

단점

GraphQL 은 대부분의 경우 REST 보다 장점이 있지만 아래와 같은 단점도 존재한다.

모니터링

모든 api 가 하나의 endpoint 를 가지는 상황이 모니터링 관점에서는 단점이 될 수 있다.
api 를 구분하려면 body 를 활용해야 하며 회사에서 사용하는 APM 솔루션이 GraphQL 을 지원하지 않는다면 문제가 될 수 있다.
다만 apollo server 를 사용한다면 apollo studio 를 활용할 수 있고 Datadog 와 연동도 가능하다.

파일 업로드

공식적인 파일 업로드에 대한 스팩이 없어 직접 multipart form 지원을 구현하거나 라이브러리를 사용해야 한다.
node.js 에서는 graphql-upload 가 많이 사용되고 문서도 많아서 큰 문제가 없지만 다른 언어에서는 라이브러리가 없는 경우가 있다.
최근 graphql-kotlin 으로 파일 업로드를 구현해야 하는 상황이 있었는데 문서가 없어 업로드만 REST API 로 구현하였다.

마무리

개인적으로 typesafe 한 개발을 선호하기에 GraphQL 을 사용하다가 REST API 를 쓰는 경험이 마치 TypeScript 를 사용하다가 다시 JavaScript 로 돌아가는 느낌을 받았다.