GraphQL#4 - Schema module 분리

이강원·2020년 3월 1일
1

GraphQL

목록 보기
4/4
post-thumbnail

GraphQL Schema(Type, Resolver) 모듈을 분리해서 관리해보자.

이 포스트 역시 지난 포스트와 연결하여 작성하는 포스트임을 미리 밝힌다.

개요

앞서 GraphQL관련 포스트에서 선언했었던 Type과 Resolver등은 전부 index.ts 파일에서 관리하고 실행했었다. 근데 이상태에서 Depth를 계속 추가하게 되면 Query호출할때도 불필요한 Depth가 추가되고 선언해줘야될 Type들이 늘어나게 되면 관리 포인트가 어려워 진다.
이런 부분을 대비하기 위해 Schema(Type, Resovler)를 전부 분리해서 module화 시켜보자.

폴더 구조 분리

  1. 일단 src밑에 schema라는 폴더를 하나 만들자.
  2. 지금까지 team에 관한 정보라는 컨셉으로 예시를 들어왔으니 team이라는 폴더를 schema 폴더 하위에 만들자.
  3. team 폴더안에 각각 type.ts / resolver.ts라는 파일을 생성하자.

현재 이 포스트는 Typescript로 작성되었기 때문에 .ts파일로 작성을 했지만, .graphql 또는 .gql파일로 만들어도 상관은없다.

index.ts파일 수정

index.ts 파일에서 type과 resolver를 분리 할 예정이기 때문에 index파일에서 선언해두었던 typeDefsResolver 삭제 및 소스를 새로 생성한 파일쪽으로 옮길 것이다.

// 변경전
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.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!
  }
`;
  • type 정의를 위해 gql module은 type파일에서 import 해준다.
  • QueryMutationextend로 확장을 시켜주자.
    (extend로 확장을 시켜준다는 의미는 Apollo Server 기능중 하나로서 root 기준으로 확장을 시켜줄수 있다. 지금 당장은 이런 의미구나 라고만 생각하고 뒤에 추가 설명하겠다.)

resolver.ts

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

이번에는 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를 설치할 필요가 없는 범위의 포스트이다.

makeExecutableSchema 원리

지금까지 team에 관한 모듈들처럼 예를들어 event에 관한 type/schema등의 한 SET를 추가한다면 아래와 같이 추가만 하면 된다.

export const schema = makeExecutableSchema({
  typeDefs: [RootSchema, teamSchema, eventSchema],
  resolvers: [RootResolver, teamResolver, eventResolver],
});

원리는 생각보다 간단했다. node_modulesapollo-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을 이용한 후기를 포스팅 할 예정이다.

profile
양산현 포스트를 쓰고싶진 않다. 공식예제를 그대로 가져다 붙이기도 싫다.그냥 내가 경험했던걸 토대로 기록하고자 한다. 내 글의 헤비 커스토머는 내 자신이다.

0개의 댓글