본문 바로가기

# GraphQL/GraphQL.js

타입스크립트 GraphQL Scalar Type 클래스



스칼라 값이란?


순수한 데이터 값을 나타내며,

프리미티브primitive 자료형과 비슷하다고 볼 수 있습니다.

  • 정수 Int

  • 문자열 String

  • 불리언 Boolean

  • 소수형 숫자 Float

  • 아이디 ID


스칼라 값을 반환하는 필드는 

더 이상 쿼리를 뻗어나갈 수 없기 때문에 리프 노드leaf node라고 부릅니다.


필드가 아니라 노드인 이유는, 

GraphQL 쿼리가 내부적으로는 트리구조로 표현되기 때문입니다.





사용자 정의 스칼라 Custom Scalar


프로그래머는 임의의 스칼라 타입을 만들 수 있습니다.

먼저 스키마 정의 언어SDL로 표현하면 다음과 같습니다.


다만, 이대로는 사용할 수 없으며

스칼라 값을 어떻게 표현하고 어떻게 해석할건지에 대한 로직이 필요합니다.

스칼라 타입에 로직을 할당하는 방법은 언어마다 다르기 때문에 공식문서를 참고하세요.



여기서는 예제로 RGB를 다룹니다.

내부적인 표현으로는 number[3] 이고,

외부적인 표현으로는 string을 사용할 것 입니다.


여기서,

내부적인 표현이란, 컴퓨터 안에서 어떻게 그 값을 구현할건지 나타내는 표현이고.

외부적인 표현이란, 사용자에게 어떻게 그 값을 표현할건지 나타내는 표현입니다.


아래 그림을 볼까요,

위의 숫자 3개가 내부적인 표현이고, 아래 문자열이 외부적인 표현입니다.

내부적인 표현을 사용하여 외부적인 표현이 만들어지고 클라이언트에게 반환됩니다.

Attention!

RGB를 문자열로 다루는게 더 쉬운 분들은,

문자열을 내부적인 표현으로 사용해도 상관없습니다.

코드에서 더 다루기 쉽도록 내부적인 표현을 결정하는 것이 좋습니다.



스칼라 타입을 만들기 위해서는 3가지 함수를 구현해야 합니다.

  • serialize (value : any)
    내부적인 표현을 외부적인 표현으로 바꾸는 부분.
    이 함수의 결과가 쿼리 응답에 포함됩니다.

  • parseValue (value : any)
    외부적인 표현을 내부적인 표현으로 바꾸는 부분.

  • parseLiteral (valueAST : any)
    외부적인 표현을 내부적인 표현으로 바꾸는 부분.


serialize 함수는 내부to외부이고,

parseValue와 parseLiteral함수는 외부to내부 입니다.


두 함수의 차이점은 다다음 절에서 다루겠습니다.




필수 로직부터 구현하자


parseValue와 parseLiteral의 차이점이 무엇인지 궁금하시겠지만 본질은 같습니다.

외부 표현을 내부 표현으로 만드는 것 뿐이죠.


일단, 두 표현간의 변환함수를 구현해야 합니다.

먼저 내부 표현을 외부 표현으로 만드는 함수부터 만들어볼까요?

/**
 * RGB의 채널값 3개가 담긴 배열을 전달받아,
 * HEX 문자열로 반환한다.
 */
function arr2str(arr: number[]): string {
    if (arr.length != 3) {
        throw Error(`arr length is must 3, but got ${arr.length}`);
    }
    let hex: string = "#";
    for (let i = 0; i < 3; i++) {
        let channel = arr[i].toString(16);
        if (channel.length == 1) hex += "0" + channel;
        else if (channel.length == 2) hex += channel;
        else throw Error(`${arr[i]} is not 0~255`);
    }
    return hex;
}


외부 표현을 내부 표현으로 만드는 함수도 만들어야 합니다.

/**
 * RGB HEX 문자열을 전달받아,
 * 채널값 3개가 담긴 배열로 반환한다.
 */
function str2arr(str: string): number[] {
    if (!/^#[0-9a-f]{6}/.test(str)) {
        throw Error(`${str} is not hex RGB`);
    }
    let arr: number[] = [];
    arr.push(parseInt(str.substring(1, 3), 16));
    arr.push(parseInt(str.substring(3, 5), 16));
    arr.push(parseInt(str.substring(5, 7), 16));
    return arr;
}


필수로직 구현은 끝났습니다.

이제 아래처럼 적당히 뭉치면 스칼라 타입이 완성됩니다.

/**
 * RGB 값을 대표하는 스칼라 타입.
 *
 * 내부적 표현 : number[3]
 * 외부적 표현 : hex string
 */
let RGB = new GraphQLScalarType({
    name: "RGB",

    serialize: (value: any) => {
        console.log("serialize");
        return arr2str(value);
    },

    parseValue: (value: any) => {
        console.log("parseValue");
        return str2arr(value);
    },

    parseLiteral: (valueAST: any) => {
        console.log("parseLiteral");
        return str2arr(valueAST.value);
    }
});

parseValue는 str2arr에 value를 넘겼지만,

parseLiteral은 str2arr에 valueAST.value를 넘긴것에 주목해야 합니다.


차이점은 바로 다음 절에서 다룹니다.




리터럴과 값


GraphQL에서 value는 QueryVariables에 선언된 것을 value라고 하며,

쿼리에 직접적으로 나온 값을 literal이라고 합니다.


이럼에도 헷갈릴 수 있습니다.

아래 그림을 보면서 이해하면 쉽습니다.


Value : 



Literal :


즉, 변수를 value로 선언했는지 literal로 선언했는지에 따라

호출되는 parse함수가 다릅니다.


value로 선언했다면 옆에 주어진 값이 value로 그대로 전달되며,

literal로 선언했다면 valueAST.value로 전달됩니다.



전체 코드는 아래의 링크에서 볼 수 있습니다.

https://github.com/MyAeroCode/ts-backend-study/tree/master/src/chapter-03-expressGraphql/009-scalarType




참고 링크


https://github.com/graphql/graphql-js/issues/500

https://stackoverflow.com/questions/41510880/whats-the-difference-between-parsevalue-and-parseliteral-in-graphqlscalartype