본문 바로가기

# GraphQL/GraphQL.js

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



GraphQL Interface Type


GraphQL Interface Type은 오브젝트 타입object type의 공통적인 필드와 반환형을 알려줍니다.

유니온union과 다른점은 모든 오브젝트 타입이 공통 필드common field를 가지고 있다는 점입니다. 



GraphQL에서 사용하는 스키마 정의 언어schema define language

인터페이스를 표현하려면 아래의 방식을 따릅니다.


캐릭터 인터페이스 타입 :

이 인터페이스 타입을 구현한 객체 타입들은 firstName이라는 필드를 구현해야 합니다.

그 외에는 자유롭고, 자신만의 필드도 가질 수 있습니다.



캐릭터 인터페이스를 구현한 타입 : 

type Human implements Character {
    firstName: String!
    lastName: String!
}

type Elf implements Character {
    firstName: String!
    middleName: String!
    lastName: String!
}

약간 판타지스런 설정을 가져오자면,

엘프 타입만 미들네임을 갖는다고 가정합니다.



캐릭터 인터페이스를 조회하는 쿼리 :

query{
    getAllCharacters {
        # common field
        firstName

        # own field
        ... on Human {
            lastName
        }
        ... on Elf {
            lastName
            middleName
        }
    }
}

getAllCharacters는 캐릭터 인터페이스를 구현한 객체를 반환하지만,

Human을 반환할지 Elf를 반환할지는 누구도 모릅니다.


다만 인터페이스에 있는 필드만큼은 존재한다고 확신할 수 있으므로,

별다른 제약없이 사용할 수 있습니다.


만약 각 타입마다 고유의 특성을 가져오고 싶다면, 

위의 쿼리처럼 인라인 프래그먼트 inline fragments를 사용해야 합니다.





Typescript로 구현하는 Interface


먼저 요약부터 하겠습니다.

  • 인터페이스 타입을 정의할 것.
  • 오브젝트 타입이 인터페이스를 구현할 것.
  • 오브젝트 타입에서 isTypeOf를 구현할 것.
  • 인터페이스에서 resolveType을 구현할 것.
  • 스키마에 구현된 오브젝트 타입을 명시할 것.


예제는 truthly, falsy number 입니다.

숫자 0만 false로 평가되고, 다른 숫자는 true로 평가됩니다.


number 필드에는 평가할 숫자가 들어가야 하고,

result 필드에는 "true" 또는 "false"가 출력되어야 합니다.



인터페이스 타입을 정의할 것 :

const BinaryNumberInterface = new GraphQLInterfaceType({
    name: "BinaryNumberInterface",
    fields: {
        number: { type: GraphQLInt },
        result: { type: GraphQLString }
    },
    resolveType: BinaryNumberTypeResolver
});

resolveType에 대해서는 곧 설명합니다.



오브젝트 타입이 인터페이스를 구현할 것 :

오브젝트 타입에서 isTypeOf를 구현할 것 :

const TruthyNumberType = new GraphQLObjectType({
    name: "TruthyNumberType",
    interfaces: [BinaryNumberInterface],
    fields: {
        number: { type: GraphQLInt },
        result: {
            type: GraphQLString,
            resolve() {
                return "true";
            }
        },
        x: {
            type: GraphQLString,
            resolve() {
                return "truthy object field";
            }
        }
    },
    isTypeOf: data => data.number
});

const FalsyNumberType = new GraphQLObjectType({
    name: "FalsyNumberType",
    interfaces: [BinaryNumberInterface],
    fields: {
        number: { type: GraphQLInt },
        result: {
            type: GraphQLString,
            resolve() {
                return "false";
            }
        },
        y: {
            type: GraphQLString,
            resolve() {
                return "falsy object field";
            }
        }
    },
    isTypeOf: data => !data.number
});

먼저 오브젝트 타입이 어떤 인터페이스 타입을 구현했다고 알려주기 위해,

interfaces에 인터페이스 타입의 배열을 넘겨주어야 합니다.


isTypeOf는 객체의 타입 유효성을 평가하며, (data : any) => boolean 형식입니다.

해당 객체타입이 맞다면 true, 아니라면 false를 반환해주세요.

여기서는 data.number를 기준으로 유효성을 따졌습니다.


TruthyNumberType의 x와 FalsyNumberType의 y는

각 오브젝트 타입의 고유한 필드입니다.



인터페이스에서 resolveType을 구현할 것 :

function BinaryNumberTypeResolver(data: any): GraphQLObjectType {
    return data.number ? TruthyNumberType : FalsyNumberType;
}

GraphQL에서는 그 데이터가 어떤 타입인지 인터페이스가 결정해야 합니다.

이 함수는 인터페이스의 resolveType의 인자로 들어가야 하며,

(data : any) => GraphQLObjectType 형식의 함수입니다.


이럴거면 오브젝트 타입의 isTypeOf는 왜 있는건가 싶은데,

isTypeOf는 진짜 이 타입이 맞아?라는 유효성을 검증하는 역할입니다.


인터페이스의 타입 리졸버가 잘못된 타입을 리턴할 수 있고,

예상치 못한 오류가 발생할 가능성도 있기 때문입니다.



스키마에 구현된 오브젝트 타입을 명시할 것 :

/**
 * types에 구체적인 타입의 이름을 적지 않는다면 오류 발생.
 */
let schema = new GraphQLSchema({
    types: [TruthyNumberType, FalsyNumberType],
    query: query
});

구체화한 오브젝트 타입을 스키마에 명시적으로 넘겨주어야 합니다.

단순히 스키마에 쿼리만 넘겨주면, GraphQL이 구체화된 타입을 인식하지 못합니다.



완성된 예제는 아래에서 확인할 수 있습니다.

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