GraphQL Schema(Type, Resolver) 모듈을 분리해서 관리해보자.
이 포스트 역시 지난 포스트와 연결하여 작성하는 포스트임을 미리 밝힌다.
앞서 GraphQL관련 포스트에서 선언했었던 Type과 Resolver등은 전부 index.ts 파일에서 관리하고 실행했었다. 근데 이상태에서 Depth를 계속 추가하게 되면 Query호출할때도 불필요한 Depth가 추가되고 선언해줘야될 Type들이 늘어나게 되면 관리 포인트가 어려워 진다.
이런 부분을 대비하기 위해 Schema(Type, Resovler)를 전부 분리해서 module화 시켜보자.
schema
라는 폴더를 하나 만들자. team
이라는 폴더를 schema 폴더 하위에 만들자. type.ts
/ resolver.ts
라는 파일을 생성하자. 현재 이 포스트는 Typescript로 작성되었기 때문에 .ts파일로 작성을 했지만, .graphql 또는 .gql파일로 만들어도 상관은없다.
index.ts 파일에서 type과 resolver를 분리 할 예정이기 때문에 index파일에서 선언해두었던 typeDefs
와 Resolver
삭제 및 소스를 새로 생성한 파일쪽으로 옮길 것이다.
// 변경전
import { ApolloServer, gql } from 'apollo-server-express';
// 변경후
import { ApolloServer } from 'apollo-server-express';
gql
은 모듈분리를 하게 되면 index.ts파일에서는 더이상 Type에 관한 정의를 할 일이 없기 때문에 현재 파일에서는 더이상 쓰임새가 없게 되어 import 해줄 필요가 없다.
Mockup sample Data로 썼던 teamData.json
파일도 resolver 함수에서 return시켜주기 위한 파일이기 때문에 분리후에는 index.ts에서는 더이상 import 해줄 필요가 없다.
자 이제 분리 해놓은 type.ts에서 다시 type들을 정의 해보자. team에 관한 기존 소스들을 그대로 가져오되 조금만 변경하면 된다.
import { gql } from 'apollo-server-express';
export const teamSchema = gql`
type Member {
name: String
email: String
}
extend type Query {
members: [Member]
}
extend type Mutation {
addMember(name: String!, email: String!): Member!
deleteMember(name: String!): Boolean!
}
`;
Query
와 Mutation
에 extend로 확장을 시켜주자.resolver.ts에도 기존에 index.ts에 있던 resolver로 정의되있던 소스를 그대로 가져오고 export만 해주면 된다. 그리고 index.ts에서 뺐던 mockup data도 Resolver에서 return받아야 될 부분이니 여기에서 재선언 해주면 된다.
아래는 지난번 포스트에 이어서 deleteMember라는 Mutation 예시 소스만 추가해뒀다. Mutation 및 business logic에 관한 얘기는 지난 포스트에서 했기 때문에 따로 설명은 안하겠다.
import teamData from '../../mockup/teamData.json';
export const teamResolver = {
Query: {
members: () => teamData,
},
Mutation: {
addMember: (root, args) => addMemberFn(args),
deleteMember: (root, args) => deleteMemberFn(args),
}
}
const addMemberFn = (args) => {
const { name, email } = args;
const data = {
name,
email,
};
teamData.push(data);
return data;
}
const deleteMemberFn = (args) => {
const { name } = args;
const { getMatchIndex, isMatched } = compareName(name);
isMatched && teamData.splice(getMatchIndex, 1);
return isMatched;
}
const compareName = (name) => {
const getMatchIndex = teamData.findIndex(value => value.name === name);
const isMatched = getMatchIndex > -1;
return {
getMatchIndex,
isMatched,
};
}
이번에는 schema폴더 아래에 index.ts를 추가해보자.
이 파일은 모든 type
/ resolver
의 root가 되는 파일이라고 보면된다.
import { gql, makeExecutableSchema } from 'apollo-server-express';
import { eventSchema } from './event/type';
import { eventResolver } from './event/resolver';
const RootSchema = gql`
type Query {
root: String
}
type Mutation{
root: String
}
`;
const RootResolver = {
Query: {
root: () => 'Root resolver is running!',
},
};
export const schema = makeExecutableSchema({
typeDefs: [RootSchema, teamSchema],
resolvers: [RootResolver, teamResolver],
});
type.ts 파일에서 한번 언급했던 extend
를 쓰는 이유는 root개념의 Query, Mutation을 선언해두고 이 type 및 resolver들에 대해 확장해서 쓰는 개념이라고 보면 된다.
makeExecutableSchema
는 apollo-server-express에서 제공해주는 모듈로서 우리가 분리해놓은 type과 resolver등을 머지 해주는 기능이다. 위와 같이 분리해서 export해놓은 module들을 typeDefs 와 resolvers로 묶어서 schema라는 변수로 export해주고 apollo server를 구동시켜주는 index.ts에서 Apollo server instance config부분만 바꿔주면 모듈 분리는 끝난다.
// src/index.ts
import { schema } from './schema/index';
const server = new ApolloServer({
schema,
playground: true,
});
모듈 분리 방법을 검색해보다 보니 apollo-server-express를 쓰는 포스트중에 makeExecutableSchema 모듈을 graphql-tools를 설치하고 여기에서 import해서 쓰는 경우가 엄청 많이 보였는데 딱히 이해는 잘 안간다. 나는 쓸데없는 Dependency를 굉장히 싫어하기 때문에 이 포스트는 grpahql-tools를 설치할 필요가 없는 범위의 포스트이다.
지금까지 team에 관한 모듈들처럼 예를들어 event에 관한 type/schema등의 한 SET를 추가한다면 아래와 같이 추가만 하면 된다.
export const schema = makeExecutableSchema({
typeDefs: [RootSchema, teamSchema, eventSchema],
resolvers: [RootResolver, teamResolver, eventResolver],
});
원리는 생각보다 간단했다. node_modules
의 apollo-server/packages/apollo-server-core/src/ApolloServer.ts 파일을 보면
const augmentedTypeDefs = Array.isArray(typeDefs) ? typeDefs : [typeDefs];
if (!isDirectiveDefined(augmentedTypeDefs, 'cacheControl')) {
augmentedTypeDefs.push(
...
);
}
ArrayObject에서 Array.isArray 정적 메소드를 사용하여 전달되는 typeDef의 typeOf를 확인후 typeDef를 Apollo
자체에서 어차피 단일로 반환하거나 배열로 만들어서 schema definition language에 push하기 때문이다. resolvers도 같은 원리이다.
이 부분에 대해 찾아보다가 어떤 포스트에는 lodash
의 merge를 쓰는 경우도 있고 merge-graphql-schemas 모듈을 설치해서 쓰는경우도 있었다. 전자의 경우는 역시.. 쓸데없는 dependency같고, 후자의 경우에는 나쁜 방법은 아니지만, fs
모듈로 관련 파일들을 죄다 묶어서 import하는 로직이기때문에 분리해놓은 type,resolver파일이 몇십개가 넘어간다면 쓸만한 방법일듯 하다.
여기까지 관련 소스들은 해당저장소에서 확인 할수 있으며, Front-end입장에서 GraphQL은 이정도면 일단 되지 않을까 싶다. 추후 하나 더 추가하고 싶다면 subscription
포스트를 한번 추가해볼까 한다.
이렇게 Apollo server를 만들어두고 Mockup data까지 만들어놨으니 이걸 두고두고 Framework들이랑 연결해서 이거저거 테스트 및 시도 해보기 딱 좋지 아니한가..
다음 포스트에서는 GraphQL을 사용해서 Svelte Framework을 이용한 후기를 포스팅 할 예정이다.