Автор оригинала: Jeff M Lowery.
Теперь я работаю с Graphql в течение нескольких месяцев, но только недавно начал использовать библиотеку GraphQL-Tools Apollo. После изучения нескольких идиом я могу быстро поднять функциональную API. Это во многом связано с его низким кодом, декларативным подходом к определениям типа.
Начиная с их примера
Аполлон имеет интерактивный LaunchPad Веб-сайт, как те, которые покрыты в моем Swagger Series Отказ Есть несколько примерных схем, которые вы можете использовать, и для этой статьи я буду использовать их Сообщение и авторы Схема . Вы можете скачать или вилить код.
Я буду перестаивать папки проекта. Для этого поста я скачаю и сохраню его в Github, поэтому я могу вешать и изменять код через каждый шаг. По пути я свяжу филиалы к этому посту.
Основы
- Объявление типов схемы
В LaunchPad вы увидите Typedefs шаблон литерал:
const typeDefs = `
type Author {
id: Int!
firstName: String
lastName: String
posts: [Post] # the list of Posts by this author
}
type Post {
id: Int!
title: String
author: Author
votes: Int
}
# the schema allows the following query:
type Query {
posts: [Post]
author(id: Int!): Author
}
# this schema allows the following mutation:
type Mutation {
upvotePost (
postId: Int!
): Post
}
`;Есть два Предприятия Определяется, Автор и Пост Отказ Кроме того, есть две «магические» типы : Запрос и Мутация Отказ Тип запроса определяет корню аксессуары . В этом случае есть аксессуар для получения всех Сообщения и другой, чтобы получить один Автор По Я БЫ .
Примечание. Нет никакого способа напрямую запросить список авторов или для одного поста. Можно добавлять такие запросы позже.
- Объявляя резольвертелей
Резольверы предоставляют необходимую логику для поддержки схемы. Они записываются как объект JavaScript с ключами, которые соответствуют типам, определенным в схеме. Resolver Показано ниже, работает против статических данных, которые я буду охватить в данный момент.
const resolvers = {
Query: {
posts: () => posts,
author: (_, { id }) => find(authors, { id: id }),
},
Mutation: {
upvotePost: (_, { postId }) => {
const post = find(posts, { id: postId });
if (!post) {
throw new Error(`Couldn't find post with id ${postId}`);
}
post.votes += 1;
return post;
},
},
Author: {
posts: (author) => filter(posts, { authorId: author.id }),
},
Post: {
author: (post) => find(authors, { id: post.authorId }),
},
};Ссылаться схема и Resolver Вместе мы создадим исполняемый экземпляр схемы:
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
});- Источник данных
Для этого простого примера данные поступают из двух массивов объектов, определенных в качестве постоянных: Авторы и Сообщения :
const authors = [
{ id: 1, firstName: 'Tom', lastName: 'Coleman' },
{ id: 2, firstName: 'Sashko', lastName: 'Stubailo' },
{ id: 3, firstName: 'Mikhail', lastName: 'Novikov' },
];
const posts = [
{ id: 1, authorId: 1, title: 'Introduction to GraphQL', votes: 2 },
{ id: 2, authorId: 2, title: 'Welcome to Meteor', votes: 3 },
{ id: 3, authorId: 2, title: 'Advanced GraphQL', votes: 1 },
{ id: 4, authorId: 3, title: 'Launchpad is Cool', votes: 7 },
];- сервер
Вы можете служить исполняемой схеме через graphql_express , apollo_graphql_express или graphql-server-express. Мы видим это в этом примере.
Важные биты:
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express';
import { schema, rootValue, context } from './schema';
const PORT = 3000;
const server = express();
server.use('/graphql', bodyParser.json(), graphqlExpress(request => ({
schema,
rootValue,
context: context(request.headers, process.env),
})));
server.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}));
server.listen(PORT, () => {
console.log(`GraphQL Server is now running on
http://localhost:${PORT}/graphql`);
console.log(`View GraphiQL at
http://localhost:${PORT}/graphiql`);
});Обратите внимание, что используются две кусочки промежуточного программного обеспечения GraphQL:
- graphqlexpress Сервер GraphQL, который обрабатывает запросы и ответы
- graphiqlexpress Интернет-сервис Interactive GraphQL, который позволяет интерактивным запросам через HTML UI
Реорганизация
Установка каждого типа компонента в свой собственный файл имеет смысл. Я пойду лучше и поставлю каждый набор компонентов в свой собственный «домен» папку.
Почему домены?
Домены – это удобный способ разделить большую систему в области эксплуатации. В каждом домене могут быть субдомены. В целом, субдомены имеют ограниченный контекст. В пределах ограниченного контекста имена объектов, свойства и процессы имеют точное значение.
Я нахожу ограниченные контексты, которые будут полезны во время анализа, особенно при разговоре с экспертами домена.
Летание в мази заключается в том, что типы GraphQl занимают одно пространство имен, поэтому могут существовать конфликты именования. Больше на этом позже.
Я назову этот домен авторпостки и поставьте связанные компоненты в авторпостки Папка Отказ В этом я создам файл каждый для DataSource , Резольверы и схема. Давайте также бросим в index.js Файл для упрощения импорта. Оригинальные файлы схемы и серверов останутся в корневой папке, но schema.js код будет скелетом. Найти и Фильтр Методы, импортируемые из Лоташ будет удален в пользу синонимичных методов на родных ES6. Полученный источник – здесь Отказ
Основной файл схемы стал проще. Он обеспечивает скелетную структуру для дальнейшего расширения схемами в наших доменах.
import {
makeExecutableSchema
} from 'graphql-tools';
import {
schema as authorpostsSchema,
resolvers as authorpostsResolvers
} from './authorposts';
const baseSchema = [
`
type Query {
domain: String
}
type Mutation {
domain: String
}
schema {
query: Query,
mutation: Mutation
}`
]
// Put schema together into one array of schema strings and one map of resolvers, like makeExecutableSchema expects
const schema = [...baseSchema, ...authorpostsSchema]
const options = {
typeDefs: schema,
resolvers: {...authorPostResolvers}
}
const executableSchema = makeExecutableSchema(options);
export default executableSchema;А домен Схема импортируется на линии 7-8, а база Схема на линии 11-23. Вы отмечете, что есть домен имущество. Это произвольно, но graphql или graphql-инструменты, настаивают на том, что одно свойство будет определено.
Полная схема построена на линии 26, а Executableschema Экземпляр создан, учитывая схема и Резольверы определяется до сих пор на линии 28-33. Это то, что импортируется server.js код, который во многом без изменений от оригинала.
Таким образом, есть трюк, чтобы разделить схему. Давайте взглянем:
import {
authors,
posts
} from './dataSource';
const rootResolvers = {
Query: {
posts: () => posts,
author: (_, {
id
}) => authors.find(a => a.id === id)
},
Mutation: {
upvotePost: (_, {
postId
}) => {
const post = posts.find(p => p.id === postId);
if (!post) {
throw new Error(`Couldn't find post with id ${postId}`);
}
post.votes += 1;
return post;
}
},
Author: {
posts: (author) => posts.filter(p => p.authorId === author.id)
},
Post: {
author: (post) => authors.find(a => a.id === post.authorId)
}
};
export default rootResolvers;const typeDefs = [
`
type Author {
id: Int!
firstName: String
lastName: String
posts: [Post] # the list of Posts by this author
}
type Post {
id: Int!
title: String
author: Author
votes: Int
}
# the schema allows the following query:
extend type Query {
posts: [Post]
author(id: Int!): Author
}
# this schema allows the following mutation:
extend type Mutation {
upvotePost (
postId: Int!
): Post
}
`
];
export default typeDefs;Первый список, автореспортеры.js , в значительной степени вырезать и вставить работу от оригинала schema.js Источник из примера Аполлона. И все же в authorpostschema.js код, мы простираться Запрос а также Мутатор Определения, которые объявлены в базовой схеме. Если вы не используете простираться Ключевое слово, исполняемый строитель схемы будет жаловаться на два Запрос Определения.
Продолжение …
Это хорошее начало для организации нескольких схем, по одному для каждого домена интереса (до тех пор, пока вы помните о глобальном пространстве имен для типов), но полная схема, даже для одного домена, может стать огромным. К счастью, вы можете сломать каждую схему еще дальше, прямо до Уровень объекта , если необходимо.
Вот модифицированная структура каталогов и списки нового содержимого:
export default `
type Author {
id: Int!
firstName: String
lastName: String
posts: [Post] # the list of Posts by this author
}`export default `
type Post {
id: Int!
title: String
author: Author
votes: Int
}`import Author from './components/author'
import Post from './components/post'
const typeDefs =
`
# the schema allows the following query:
extend type Query {
posts: [Post]
author(id: Int!): Author
}
# this schema allows the following mutation:
extend type Mutation {
upvotePost (
postId: Int!
): Post
}
`;
export default [typeDefs, Author, Post];Мы можем достичь гранулярности, определяя два компонентных файла, а затем импортируя их в доменную схему.
Вам не нужно делать один компонент на файл. Но вы хотите быть уверены, что схема экспортирует эти компоненты вместе с самой схемы, как показано на линии 20 schema.js Отказ В противном случае вы, вероятно, вы можете пропустить зависимость дальше вниз по цепочке включения.
Несколько схем и резольверов
Добавление новой схемы для нового домена проста. Создайте новую папку домена и добавьте файлы DataSource, Resolvers, Schema и index.js. Вы также можете добавить дополнительную папку компонентов с определениями типа компонентов.
const myLittleTypes = [{
id: 1,
description: 'This is good',
}, {
id: 2,
description: 'This is better',
}, {
id: 3,
description: 'This is the best!',
}];
export {
myLittleTypes
};export default `
type MyLittleType {
id: Int!
description: String
}`import {
myLittleTypes
} from './dataSource';
const rootResolvers = {
Query: {
myLittleType: (_, {
id
}) => myLittleTypes.find(t => t.id === id)
},
};
export default rootResolvers;import MyLittleType from './components/myLittleType'
const typeDefs =
`
# the schema allows the following query:
extend type Query {
myLittleType(id: Int!): MyLittleType
}
`;
export default [typeDefs, MyLittleType];Наконец, файл root Schema.js должен сочетать схемы и резольвенторы из обоих доменов:
//...
import {
schema as myLittleTypoSchema,
resolvers as myLittleTypeResolvers
} from './myLittleDomain';
import {
merge
} from 'lodash';
//...
const schema = [...baseSchema, ...authorpostsSchema, ...myLittleTypoSchema]
const options = {
typeDefs: schema,
resolvers: merge(authorpostsResolvers, myLittleTypeResolvers)
}Обратите внимание, что мне пришлось включить лоташ слияние здесь из-за необходимости глубокого слижения двух резольвенторы импорт.
Работа с столкновениями имен
Если вы находитесь в большом проекте, вы встретите столкновения типа типа. Вы можете подумать, что учетная запись в одном домене будет означать то же самое, что и в другой. Тем не менее, даже если они означают более или менее подобные вещи, шансы – это свойства и отношения будут разными. Так технически они не тот же тип.
Во время этого письма GraphQL использует одно пространство имен для типов.
Как работать вокруг этого? Facebook, по-видимому, использует Соглашение об именах Для их 10000 типов. Как то неловко, как это кажется, это работает для них.
Степ Apollo GraphQl-Tools, по-видимому, уводит дублирования имени типа. Так что вы должны быть хорошими там.
Есть постоянное обсуждение на будь то Чтобы включить пространства имен в GraphQL. Это не простое решение. Я помню сложности, вызванные введением 20 пространств имен XML 10 лет назад.
Куда пойти отсюда?
Этот пост только царапает поверхность того, как можно организовать большой набор схем GraphQL. Следующим постом будет о насмещении резольсеров GraphQL, и как можно смешивать как реальные, так и издеваемые значения в ответах на запрос.
Оригинал: “https://www.freecodecamp.org/news/declarative-graphql-with-graphql-tools-cd1645f94fc/”