현재 크로키는 API를 GraphQL로 만들고 있습니다. 아직 많은 부분에 대해서 연구 중이어서 현재 상황만 간단하게 정리해 보겠습니다.
Thrift를 1년 정도 쓴 시점부터 여러 가지 불편함을 느끼고 대안을 찾다가 GraphQL을 선택했습니다. GraphQL 생태계가 Node.js를 중심으로 발전하고 있어서 크게 망설이지 않고 정할 수 있었던 것 같습니다.
외부에 영향을 주지 않는 기능 중 N+1 문제를 가지고 있는 기능을 하나 선택해 변환을 해봤습니다. 조금 생각은 해야 하지만 충분히 기존 API에 성능이 떨어지지 않고 사용은 더 편하게 할 수 있는 것을 확인하고 조금씩 확대해나갔습니다.
1년 이상 진행하면서 정리된 API 스펙은 스타일 가이드 저장소에서 공개하고 있습니다.
처음에는 GraphQLObjectType을 써서 정의했습니다.
import { GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLString } from 'graphql';
const type = new GraphQLObjectType({
name: 'Shop',
fields: {
id: {
type: new GraphQLNonNull(GraphQLInt),
},
name: {
type: new GraphQLNonNull(GraphQLString),
},
url: {
type: new GraphQLNonNull(GraphQLString),
},
},
});
그러다가 타입이 많아지면 보기가 힘들어서 스키마 문자열을 통해 정의하는 것으로 변경해 봤습니다.
import { makeExecutableSchema } from 'graphql-tools';
const typeDefs = `
type Shop {
id: ID!
name: String!
url: String!
}
`;
const resolvers = {
Query: {
shop: ShopService.shop,
},
};
const schema = makeExecutableSchema({
typeDefs: [typeDefs],
resolvers: [resolvers],
});
현재는 resolver 구현에도 타입 체킹이 되고, DB 모델과 통일시키기 위해 type-graphql을 적용했습니다. 다만 현재 퍼포먼스 이슈 때문에 수정한 버전을 사용하고 있습니다.
import { Field, ID, ObjectType } from 'type-graphql';
@ObjectType()
export class Shop {
@Field((type) => ID, { nullable: false })
id: string;
@Field((type) => String, { nullable: false })
name: string;
@Field((type) => String, { nullable: false })
url: string;
}
클라어인트에서는 apollo 툴을 사용해 코드를 생성해 호출하고 있습니다.
안드로이드
LoginInput input = LoginInput.builder().email(email).password(password).build();
apolloClient.mutate(
LoginMutation.builder().input(input).build()
).enqueue(new ApolloCall.Callback<LoginMutation.Data>() {
...
});
iOS
let input = LoginInput(email: email, password: password)
apolloClient.perform(mutation: LoginMutation(input: input)) { (result, error) in
...
}
웹
// api.ts (apollo-tooling 위에 자체 스크립트로 생성한 파일)
import * as types from './types';
export async function login(variable: types.LoginVariables, options?: GqlRequestOptions) {
const query = 'mutation Login($input: LoginInput!) { login(input: $input) }';
return await request<types.Login, types.LoginVariables>(query, variable, options);
}
// LoginView.ts
async login() {
await api.login({
input: { email, password },
});
}
전환을 시작한 이후로 많은 GraphQL API를 만들었지만, 기존 API는 손대지 못하고 거의 그대로 사용하고 있습니다. 그걸 보면서 진작에 GraphQL로 전환했으면 두 번 작업하는 일이 줄어들었을 터라는 생각이 듭니다. 하지만 초반에 적용했으면 아직 안정화되지 않은 생태계로 인해 고생했을 것 같습니다.
웹 기술을 TypeScript로, GraphQL로, React로 전환할 때마다, 이 방향이 맞는지, 이 시기가 적당한지 고민이 됩니다. 서비스를 이어가는 와중에 기술 부채를 털어낼 적절한 시기를 놓치지 않도록 계속 노력하고 있습니다.
차후에도 GraphQL을 사용하면서 의미가 있는 부분이 있으면 공개하도록 하겠습니다.