Serverless GraphQL Part II: ходим за данными в Yandex Database
В первой части мы закончили на том, что запустили Apollo в облаке. Но это слишком простое API. Поэтому сейчас добавим интеграцию с Severless YDB.
Создаем базу
В консоли переходим во вкладку Yandex Database и нажимаем кнопку создать базу.
Выбираем Serverless вариант. Собственно на странице создания вам объяснят все плюсы такого подхода:
YDB автоматически выделяет и освобождает ресурсы исходя из пользовательской нагрузки, то есть исходя из объема хранимых данных и количества и сложности операций выполняемых с данными; в этом случае оплачивается стоимость выполнения операций и хранимых данных. Тарифы и цены.
Далее создаем 2 таблицы authors
и books
. Их мы будем использовать в примере.
Теперь можно переходить к написанию кода.
Пример работы с YDB
YDB может работать с просто YQL запросами. Но предпочтительным является через prepared запросы. Это позволит избежать инъекций. Также если переиспользовать подготовленные запросы, это позволит работать с базой эффективнее, так как не будет тратиться время на парсинг каждого запроса.
import {Driver, getCredentialsFromEnv, getLogger, Logger, withRetries, Ydb} from 'ydb-sdk';
import {Author, Book} from "./data-helpers";
import Long from 'long';
export class Db {
driver: Driver;
tablePathPrefix: string;
logger: Logger;
queries = {
selectBooksByAuthor: (tablePathPrefix) => `
PRAGMA TablePathPrefix("${tablePathPrefix}");
DECLARE $authorId AS Uint64;
SELECT id, author_id, title, info, cast(release_date as string) as release_date
FROM books VIEW books_author_id
WHERE author_id = $authorId
ORDER BY release_date;`,
}
constructor(entryPoint, dbName) {
const logger = getLogger({level: 'debug'});
const authService = getCredentialsFromEnv(entryPoint, dbName, logger);
this.driver = new Driver(entryPoint, dbName, authService);
this.tablePathPrefix = dbName;
this.logger = logger
}
async selectBooksByAuthorId(authorId: number): Promise<Book[]> {
let result: Book[] = [];
this.logger.info({authorId: authorId}, 'author id');
await this.driver.tableClient.withSession(async (session) => {
const select = async () => {
const preparedQuery = await session.prepareQuery(this.queries.selectBooksByAuthor(this.tablePathPrefix));
const {resultSets} = await session.executeQuery(preparedQuery, {
'$authorId': Ydb.TypedValue.create({
type: {typeId: Ydb.Type.PrimitiveTypeId.UINT64},
value: {uint64Value: Long.fromNumber(authorId)}
}),
});
return Book.createNativeObjects(resultSets[0])
}
result = await withRetries(select) as Book[];
});
return result;
}
}
Мы получили данные из базы, осталось описать resolver’ы в Apollo, которые будут обращаться к этим методам в ответ на входящие запросы.
Apollo Resolvers
Опишем GraphQL схему.
type Info {
note: String
}
type Book {
id: Int!
title: String
releaseDate: String
info: Info
}
type Author {
id: Int!
name: String
books: [Book]
}
type Query {
books: [Book]!
book(id: Int!): Book
author(id: Int!): Author
}
Как видно нам нужно описать всего 4 резолвера. Это простые функции которые принимают 4 параметра (parent, args, context, info)=> data
.
parent
— Результат выполнения родительского резолвера. Например author
в нашем случае. Родительский резолвер всегда отрабатывает до резолверов вложенных в него.
args
В этом объекте лежат все аргументы переданные при вызове запроса.
context
Этот объект делят все резолверы, выполняемые в рамках одной операции. Его можно использовать для передачи состояния в рамках одной операции и доступа к datasouce’ам.
infо
Содержит информацию о ходе выполнения операции (нужен исключительно в сложных случаях)
export const resolvers = {
Query: {
books: async (_, __, {dataSources}) => dataSources.bookAPI.selectAll(),
book: async (_, {id}, {dataSources}) => dataSources.bookAPI.selectOne({id}),
author: async (_, {id}, {dataSources}) => dataSources.authorAPI.selectOne({id}),
},
Author: {
books: async (author, __, {dataSources}) => dataSources.bookAPI.selectByAuthor({id: author.id}),
}
};
Подробнее про резолверы можно прочитать тут.
Вот собственно мы и получили работающее GraphQL API читающее данные из YDB.
Примеры запросов в API
Полный код примера можно найти тут.