Рубрики
Без рубрики

Декларативный график: напишите меньше кода и получите больше с помощью GraphQL-Tools

Теперь я работаю с Graphql в течение нескольких месяцев, но только недавно начал использовать библиотеку GraphQL-Tools Apollo. После изучения нескольких идиом я могу быстро поднять функциональную API. Это во многом связано с его низким кодом, декларативным подходом к определениям типа. Начиная с их

Автор оригинала: 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/”