Graphql Compose 기반 Code Autogeneration

Graphql-Compose 는 프로그래머블한 스키마 구성을 위한 여러 가지 메소드(Resolver)와 형식(Type Schema) 레지스트리를 제공합니다. 이는 형식(Type)들을 커스터마이징 하고 확장할 수 있을 뿐만 아니라 Fields, Interfaces, 변수를 변경할 수 있습니다.


Graphql-compose 는 다양한 plugin 을 제공합니다. 이들은 Graphql-compose 를 기반으로 구현된 선언적 auto conde generation 도구로써, 기 정의된 프레임워크들(예 : MongoDB Schema, Elasticsearch Schema 등의 ORM, 스키마 정의를 가져와서 GraphQL Model 을 만들거나 기존 GraphQL Type을 수정합니다.


대표적인 plugin 들은 다음과 같습니다.



Graphql-compose-mongoose Basic Example

  • UserSchema - Mongoose Schema 입니다.

  • User - Mongoose Model 입니다.

  • UserTC - User 모델을 위한 ObjectType Composer 인스턴스입니다. ObjectTypeComposer에는 UserTC.getType() 메서드를 통해 사용할 수 있는 GraphQLObjectType이 내부에 있습니다.

import mongoose from 'mongoose';
import { composeMongoose } from 'graphql-compose-mongoose';
import { schemaComposer } from 'graphql-compose';

// STEP 1: DEFINE MONGOOSE SCHEMA AND MODEL
const LanguagesSchema = new mongoose.Schema({
  language: String,
  skill: {
    type: String,
    enum: [ 'basic', 'fluent', 'native' ],
  },
});

const UserSchema = new mongoose.Schema({
  name: String, // standard types
  age: {
    type: Number,
    index: true,
  },
  ln: {
    type: [LanguagesSchema], // you may include other schemas (here included as array of embedded documents)
    default: [],
    alias: 'languages', // in schema `ln` will be named as `languages`
  },
  contacts: { // another mongoose way for providing embedded documents
    email: String,
    phones: [String], // array of strings
  },
  gender: { // enum field with values
    type: String,
    enum: ['male', 'female'],
  },
  someMixed: {
    type: mongoose.Schema.Types.Mixed,
    description: 'Can be any mixed type, that will be treated as JSON GraphQL Scalar Type',
  },
});
const User = mongoose.model('User', UserSchema);


// STEP 2: CONVERT MONGOOSE MODEL TO GraphQL PIECES
const customizationOptions = {}; // left it empty for simplicity, described below
const UserTC = composeMongoose(User, customizationOptions);

// STEP 3: Add needed CRUD User operations to the GraphQL Schema
// via graphql-compose it will be much much easier, with less typing
schemaComposer.Query.addFields({
  userById: UserTC.mongooseResolvers.findById(),
  userByIds: UserTC.mongooseResolvers.findByIds(),
  userOne: UserTC.mongooseResolvers.findOne(),
  userMany: UserTC.mongooseResolvers.findMany(),
  userDataLoader: UserTC.mongooseResolvers.dataLoader(),
  userDataLoaderMany: UserTC.mongooseResolvers.dataLoaderMany(),
  userByIdLean: UserTC.mongooseResolvers.findById({ lean: true }),
  userByIdsLean: UserTC.mongooseResolvers.findByIds({ lean: true }),
  userOneLean: UserTC.mongooseResolvers.findOne({ lean: true }),
  userManyLean: UserTC.mongooseResolvers.findMany({ lean: true }),
  userDataLoaderLean: UserTC.mongooseResolvers.dataLoader({ lean: true }),
  userDataLoaderManyLean: UserTC.mongooseResolvers.dataLoaderMany({ lean: true }),
  userCount: UserTC.mongooseResolvers.count(),
  userConnection: UserTC.mongooseResolvers.connection(),
  userPagination: UserTC.mongooseResolvers.pagination(),
});

schemaComposer.Mutation.addFields({
  userCreateOne: UserTC.mongooseResolvers.createOne(),
  userCreateMany: UserTC.mongooseResolvers.createMany(),
  userUpdateById: UserTC.mongooseResolvers.updateById(),
  userUpdateOne: UserTC.mongooseResolvers.updateOne(),
  userUpdateMany: UserTC.mongooseResolvers.updateMany(),
  userRemoveById: UserTC.mongooseResolvers.removeById(),
  userRemoveOne: UserTC.mongooseResolvers.removeOne(),
  userRemoveMany: UserTC.mongooseResolvers.removeMany(),
});

const graphqlSchema = schemaComposer.buildSchema();
export default graphqlSchema;


Graphql-compose-mongoose 기반 Model Linking example

  • Object Type Composer 에 addRelation() 을 추가 하여 Model Linking 을 갖는 Resolver Query 를 구현할 수 있음

  • 아래 예는 Alarm Model 이 rule 이라는 필드를 통해 Rule Model 과 Linking 하는 예제임

  • Alarm Model

const AlarmSchema = new Schema({
 rule: { type: Schema.Types.ObjectId, ref: 'rule' },
 name: String,
 timestamp: Date,
 message: String,
 level: String,
 state: String,
 objectsource: String,
 failover: String,
 data: Object
});

module.exports = {
 AlarmSchema: mongoClients.model("alarm", AlarmSchema),
 AlarmTC: composeWithMongoose(mongoClients.model("alarm", AlarmSchema))
};

rule 은 rule model 로 Linking 되는 relation 을 schema 로 가짐

  • Alarm Schema

const { AlarmTC } = require("../models/alarm");
const { RuleTC } = require("../models/rule");

const AlarmQuery = {
 alarmById: AlarmTC.getResolver("findById"),
 alarmByIds: AlarmTC.getResolver("findByIds"),
 alarmOne: AlarmTC.getResolver("findOne"),
 alarmMany: AlarmTC.getResolver("findMany"),
 alarmCount: AlarmTC.getResolver("count"),
 alarmConnection: AlarmTC.getResolver("connection"),
 alarmPagination: AlarmTC.getResolver("pagination"),
};

const AlarmMutation = {
 alarmCreateOne: AlarmTC.getResolver("createOne"),
 alarmCreateMany: AlarmTC.getResolver("createMany"),
 alarmUpdateById: AlarmTC.getResolver("updateById"),
 alarmUpdateOne: AlarmTC.getResolver("updateOne"),
 alarmUpdateMany: AlarmTC.getResolver("updateMany"),
 alarmRemoveById: AlarmTC.getResolver("removeById"),
 alarmRemoveOne: AlarmTC.getResolver("removeOne"),
 alarmRemoveMany: AlarmTC.getResolver("removeMany")
};

AlarmTC.addRelation(
 'ruleLink',
  {
 resolver: () => RuleTC.getResolver("findById"),
 prepareArgs: { // resolver `findByIds` has `_ids` arg, let provide value to it
 _id: (source) => source.rule,
    },
 projection: { rule: 1 }, // point fields in source object, which should be fetched from DB
  }
);

module.exports = { AlarmQuery: AlarmQuery, AlarmMutation: AlarmMutation };

  • AlarmTC.addRelate() 을 통해 ruleLink 라는 새로운 Resolver 함수가 정의 되었고, 해당 resolver 는 Alarm 의 rule 필드의 값을 _id 로 해서 Rule Model 의 findById resolver 함수를 Call 해서 값을 return 하게 됨


Graphql-compose 기반 자동생성 Schema, Resolver 와 기존 Graphql Raw Schema, Resolver 와의 통합 Example

  • 본 예제는 graphql-compose-plugin 을 통해 자동 생성한 Schema 와 Resolver 와 기존의 graphql-tool 을 통해 구현한 Schema 와 Resolver 를 병합 하기 위한 방법을 설명 함

  • 통상적으로, graphql-compose 의 SchemaComposer 를 사용함

  • graphql-tool 을 통해 구현한 schema 의 경우는 schemaComposer.addTypeDefs() 함수를 사용하여 병합하며, Resolver 의 경우는 schemaComposer.addResolveMethods() 함수를 사용하여 병합함

  • graphql-compose-plugin 의 경우는 schemaComposer.Query.addFields() 와 schemaComposer.Mutation.addFields() 함수를 사용하여 병합함

  • 다음 예는 schema 폴더에 정의된 Mongo 기반 모델의 Schema 와 gql 폴더에 정의된 raw 레벨의 graphql Schema 를 병합하는 예제를 설명함

const { SchemaComposer } = require( 'graphql-compose');
const { fileLoader, mergeResolvers, mergeTypes} = require('merge-graphql-schemas');
const path = require('path');
const schemaComposer = new SchemaComposer();

// Construct a schema, using GraphQL schema language
const allTypes = fileLoader(path.join(__dirname, "/gql/**/*.graphql"));
const allResolvers = fileLoader(path.join(__dirname, "/gql/**/*.js"));

const { ActionQuery, ActionMutation } = require ('./schema/action');
const { MatchQuery, MatchMutation } = require ('./schema/match');
const { RuleQuery, RuleMutation } = require ('./schema/rule');
const { AlarmQuery, AlarmMutation } = require ('./schema/alarm');
const { FailoverQuery, FailoverMutation } = require ('./schema/failover');
const model = require('./models/user')

schemaComposer.addTypeDefs(mergeTypes(allTypes));

allResolvers.forEach(query => {
 schemaComposer.addResolveMethods(query);
});

schemaComposer.Query.addFields({
    ...ActionQuery,
    ...AlarmQuery,
    ...MatchQuery,
    ...RuleQuery,
    ...FailoverQuery
});

schemaComposer.Mutation.addFields({
    ...ActionMutation,
    ...AlarmMutation,
    ...MatchMutation,
    ...RuleMutation,
    ...FailoverMutation
});

module.exports = schemaComposer.buildSchema();