В данной статье я расскажу вам как исходя из своего опыта я написал небольшой npm-модуль, который помог мне и, думаю, может помочь вам сэкономить приличное количество времени и сократить код практически в два раза.
Все начал с того что я решил написать изоморфный CMS для одного из моих проектов с использованием следующих технологий:
Упрощенная архитектура выглядит следующим образом:
Соответственно, для каждой сущности в базе данных мы должны создать Mongoose-схему, чтобы иметь возможность манипулировать данными. Выглядит это примерно следующим образом:
Теперь если мы используем graphQL мы должны создать тип для каждой сущности в базе данных, причем тип каждого свойства должен быть идентичен тому же, что и в Mongoose схеме. Без graphQL-типов мы просто не сможем сгенерировать graphQL-схему. Тип выглядит следующим образом:
Думаю, схожесть очевидна? А теперь представьте, что вы имеете несколько десятков сущностей и для каждой из них вам нужно практически дублировать одну и ту же логику. А ведь типы могут содержать массивы других типов и множество других вложенных элементов.
Собственно, для разрешения этой проблемы я написал небольшой модуль (всего 3kb), который поможет избежать дублирования логически идентичного кода и практически сократит вдвое ваш код, в моем случае я сократил код более чем на 2000 строк.
Для начала установим модуль:
Модуль содержит одну единственную функцию “MTGQL”, которая принимает один объект с конфигурациями в качестве аргумента. Объект имеет следующую структуру:
Пару примеров использования модуля ниже:
dbSchema.js
В файле с вашими graphQL типами:
type.js
Собственно всего 10 строк кода, которые равносильны следующему:
Думаю, разница очевидна. Модуль не поддерживает напрямую “GraphQLFloat”, потому что в Mongoose схеме вы можете задать только тип «Number», который эквивалентен целому числу. Но эту проблему можно легко решить, передав нужное свойство через конфигурационный объект.
Более подробно о том, как работает модуль, вы можете посмотреть на GitHub.
Надеюсь, статья была вам полезна, если у вас есть какие-то предложения по улучшению, пишите, обязательно учту. Спасибо за внимание.
Оригинал статьи находится на medium.com.
Все начал с того что я решил написать изоморфный 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: { 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.