Перейти к основному содержимому

Serverless GraphQL Part II: ходим за данными в Yandex Database

· 3 мин. чтения

В первой части мы закончили на том, что запустили 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о Содержит информацию о ходе выполнения операции (нужен исключительно в сложных случаях)

resolvers.ts
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

Полный код примера можно найти тут.