В данной статье я расскажу вам как исходя из своего опыта я написал небольшой npm-модуль, который помог мне и, думаю, может помочь вам сэкономить приличное количество времени и сократить код практически в два раза.
Все начал с того что я решил написать изоморфный CMS для одного из моих проектов с использованием следующих технологий:
- React — для постройки UI
- Express — в качестве сервера
- MongoDb + Mongoose — noSQL база данных
- graphQL — основной API для взаимодействия с базой данных
- Apollo-Client — коннектор для удобного вызова запросов и мутаций через graphQL
- webpack — для сборки проекта и разделения клиентского и серверного кода
Упрощенная архитектура выглядит следующим образом:
__root
1 |__client
2 |__public
3 |__middleware
4 |__server
- React-компоненты
- Бандл клиентского кода и остальные публичные файлы
- Mongoose: схемы, дополнительные методы и graphQL: типы, класс с запросами, класс с мутациями, схема
- Бандл серверного кода вместе с express сервером
Соответственно, для каждой сущности в базе данных мы должны создать Mongoose-схему, чтобы иметь возможность манипулировать данными. Выглядит это примерно следующим образом:
let couponSchema = mongoose.Schema({
couponCode: Array,
description: String,
discountAmount: String,
minimumAmount: String,
singleUseOnly: Boolean,
createdAt: mongoose.Schema.Types.Date,
updatedAt: mongoose.Schema.Types.Date,
expirationDate: mongoose.Schema.Types.Date
});
Теперь если мы используем graphQL мы должны создать тип для каждой сущности в базе данных, причем тип каждого свойства должен быть идентичен тому же, что и в Mongoose схеме. Без graphQL-типов мы просто не сможем сгенерировать graphQL-схему. Тип выглядит следующим образом:
let couponType = new GraphQLObjectType({
name: 'couponType',
description: 'single use coupon',
fields: {
_id: {type: GraphQLString},
couponCode: {type: new GraphQLList(GraphQLString)},
description: {type: GraphQLString},
discountAmount: {type: GraphQLString},
minimumAmount: {type: GraphQLString},
singleUseOnly: {type: GraphQLBoolean},
createdAt: {type: GraphQLString},
updatedAt: {type: GraphQLString},
expirationDate: {type: GraphQLString}
}
});
Думаю, схожесть очевидна? А теперь представьте, что вы имеете несколько десятков сущностей и для каждой из них вам нужно практически дублировать одну и ту же логику. А ведь типы могут содержать массивы других типов и множество других вложенных элементов.
Собственно, для разрешения этой проблемы я написал небольшой модуль (всего 3kb), который поможет избежать дублирования логически идентичного кода и практически сократит вдвое ваш код, в моем случае я сократил код более чем на 2000 строк.
Для начала установим модуль:
npm i mongoose-schema-to-graphql --save
Модуль содержит одну единственную функцию “MTGQL”, которая принимает один объект с конфигурациями в качестве аргумента. Объект имеет следующую структуру:
let configs = {
name: 'couponType',
// название graphQL типа
description: 'Coupon base schema',
// описание graphQL типа
class: 'GraphQLObjectType',
// graphQL класс который будет использован для сборки
schema: couponSchema,
// Mongoose схема
exclude: ['_id'],
// свойства которые нужно исключить
props: {
price: {type: GraphQLFloat}
}
// дополнительные свойства которые нужно добавить или переписать
}
Пару примеров использования модуля ниже:
dbSchema.js
import mongoose from 'mongoose';
let selectObj = {
value: String,
label: String
};
let answerSchema = mongoose.Schema({
createdAt: mongoose.Schema.Types.Date,
updatedAt: mongoose.Schema.Types.Date,
title: String,
answersImage: String,
recommended: [selectObj],
isPublished: Boolean
});
export let questionSchema = mongoose.Schema({
question: String,
defRecommended: [selectObj],
createdAt: mongoose.Schema.Types.Date,
updatedAt: mongoose.Schema.Types.Date,
isPublished: Boolean,
multipleChoice: Boolean,
answers: [answerSchema]
});
В файле с вашими graphQL типами:
type.js
import MTGQL from 'mongoose-schema-to-graphql';
import {questionSchema} from './dbSchemas';
let config = {
name: 'questionType',
description: 'Question collection's type',
class: 'GraphQLObjectType',
schema: questionSchema,
exclude: ['_id']
};
export let questionType = MTGQL(config);
Собственно всего 10 строк кода, которые равносильны следующему:
import {
GraphQLObjectType,
GraphQLString,
GraphQLBoolean,
GraphQLList,
GraphQLInt
} from 'graphql';
let selectType = new GraphQLObjectType({
name: 'selectType',
fields: {
value: {type: GraphQLString},
label: {type: GraphQLString}
}
});
let answerType = new GraphQLObjectType({
name: 'answerType',
description: 'answer type for question',
fields: {
title: {type: GraphQLString},
answersImage: {type: GraphQLString},
recommended: {type: new GraphQLList(selectType)},
createdAt: {type: GraphQLString},
updatedAt: {type: GraphQLString},
isPublished: {type: GraphQLBoolean}
}
});
export let questionType = new GraphQLObjectType({
name: 'questionType',
description: 'Question collection's type',
fields: {
_id: {type: GraphQLString},
question: {type: GraphQLString},
defRecommended: {type: new GraphQLList(selectType)},
createdAt: {type: GraphQLString},
updatedAt: {type: GraphQLString},
isPublished: {type: GraphQLBoolean},
multipleChoice: {type: GraphQLBoolean},
answers: {type: new GraphQLList(answerType)}
}
});
Думаю, разница очевидна. Модуль не поддерживает напрямую “GraphQLFloat”, потому что в Mongoose схеме вы можете задать только тип «Number», который эквивалентен целому числу. Но эту проблему можно легко решить, передав нужное свойство через конфигурационный объект.
Более подробно о том, как работает модуль, вы можете посмотреть на GitHub.
Надеюсь, статья была вам полезна, если у вас есть какие-то предложения по улучшению, пишите, обязательно учту. Спасибо за внимание.
Оригинал статьи находится на medium.com.
Автор: sarkis-tlt