Ваш собственный проект может быть размещен в сети рядом с существующими проектами SubQuery от Acala и Bifrost. Вы также можете сохранить свой проект SubQuery закрытым, не отображая его в проводнике, что идеально, если вы хотите в частном порядке протестировать его с помощью управляемой инфраструктуры SubQuery. Кроме того, SubQuery интегрирован с GitHub, поэтому создание проекта SubQuery в учетной записи организации GitHub автоматически предоставит доступ к нему членам вашей команды.
В предыдущих статьях мы рассказывали о том, что такое SubQuery Projects, посмотрели на приемущества платформы, а также затронули тему того, как создать свой проект. В сегодняшней статье мы посмотрим более подробно на техническую сторону SubQuery Project.
Manifest File
Manifest project.yaml можно рассматривать как точку входа в ваш проект, он определяет большую часть деталей того, как SubQuery будет индексировать и преобразовывать данные блокчейна. Манифест может быть в формате YAML или JSON - в этом документе мы будем использовать YAML во всех примерах.
- specVersion указывает, какая версия этого API используется
- schema указывает на файл схемы GraphQL этого SubQuery (вам не нужно менять это)
- network.endpoint определяет конечную точку wss или ws блокчейна, которая будет индексироваться
- network.types объявляют определенные типы, поддерживаемые этим блокчейном. Разработчики SubQuery поддерживают дополнительные типы, используемые модулями среды Substrate. Поддерживаются typesAlias, typesBundle, typesChain и typesSpec.
- dataSources определяет, какие данные будут отфильтрованы и извлечены, а также расположение обработчика функции сопоставления для применения преобразования данных
- kind пока поддерживает только Substrate/Runtime
- startBlock указывает высоту блока, с которой начинается индексация
- filter будет фильтровать источник данных для выполнения по имени спецификации конечной точки сети, см. сетевые фильтры
- mapping.handlers перечислит все "Mapping functions" и соответствующие им типы обработчиков с дополнительными "Mapping filters".
Что такое "Mapping functions"? Они определяют, как данные цепочки преобразуются в оптимизированные объекты GraphQL, которые разработчики ранее определили в файле schema.graphql. "Mappings" записываются в подмножестве TypeScript, называемом AssemblyScript, который может быть скомпилирован в WASM (WebAssembly).
Что такое "Mapping filters"? Это чрезвычайно полезная функция, позволяющая решить, какой блок, событие или внешний вызов будет запускать обработчик сопоставления. Только входящие данные, которые соответствуют условиям фильтрации, будут обрабатываться "Mapping functions". "Mapping filters" не являются обязательными, но рекомендуются, поскольку они значительно сокращают объем данных, обрабатываемых вашим проектом SubQuery, и улучшают производительность индексации.
Network Filters
Обычно пользователь создает SubQuery и рассчитывает повторно использовать его как для своей тестовой сети, так и для основной сети (например, Polkadot и Kusama). В разных сетях могут быть разные параметры (например, блок запуска индекса). Поэтому SubQuery позволяет пользователям определять разные детали для каждого источника данных, поэтому один проект SubQuery по-прежнему может использоваться в нескольких сетях. Пользователи могут добавить фильтр к источникам данных, чтобы решить, какой источник данных запускать в каждой сети. Ниже приведен пример, показывающий разные источники данных для сетей Polkadot и Kusama.
GraphQL Schema
Файл schema.graphql определяет различные схемы GraphQL. Из-за того, как работает язык SubQuery GraphQL, файл схемы по существу определяет форму ваших данных из SubQuery.
Важно: когда вы вносите какие-либо изменения в файл schema, убедитесь, что вы повторно создали каталог типов с помощью следующей команды yarn codegen
Каждая часть должна определить свой идентификатор обязательных полей с типом идентификатора ID!, который используется в качестве первичного ключа и уникален среди всех частей одного типа.
Поля, не допускающие значения NULL, в части обозначены восклицательным знаком «!». Посмотрим пример ниже:
type Example @entity {
id: ID! # id field is always required and must look like this
name: String! # This is a required field
address: String # This is an optional field
}
Важно: когда вы вносите какие-либо изменения в файл schema, убедитесь, что вы повторно создали каталог типов с помощью следующей команды yarn codegen
Поддерживаемые скаляры и типы
В настоящее время SubQuery поддерживает типы потоковых скаляров:
- ID
- Int
- String
- BigInt
- Date
- Boolean
- <EntityName>
- JSON (может хранить структурированные данные)
Индексирование по Non-primary-key
Чтобы повысить производительность Query, индексируйте поле сущности, просто реализуя аннотацию @index для поля, отличного от первичного ключа.
Вот пример:
type User @entity {
id: ID!
name: String! @index(unique: true) # unique can be set to true or false
title: Title! @index # By default indexes are not unique, index by foreign key field
}type Title @entity {
id: ID!
name: String! @index(unique:true)
}
Предполагая, что мы знаем имя этого пользователя, но не знаем точного значения идентификатора, вместо того, чтобы извлечь всех пользователей, а затем выполнить фильтрацию по имени, мы можем добавить @index позади поля имени. Это значительно ускоряет выполнение Query, и мы можем дополнительно передать unique: true для обеспечения уникальности. Если поле не уникально, максимальный размер набора результатов равен 100.
При запуске генерации кода это автоматически создаст getByName в модели User, а заголовок поля внешнего ключа создаст метод getByTitleId, к которому можно получить прямой доступ в функции сопоставления.
/* Prepare a record for title entity */
INSERT INTO titles (id, name) VALUES ('id_1', 'Captain')
// Handler in mapping function
import {User} from "../types/models/User"
import {Title} from "../types/models/Title"const jack = await User.getByName('Jack Sparrow');
const captainTitle = await Title.getByName('Captain');
const pirateLords = await User.getByTitleId(captainTitle.id); // List of all Captains
Связь сущностей
Сущность часто имеет вложенные отношения с другими сущностями. Установка значения поля для другого имени сущности будет определять взаимно однозначное отношение между этими двумя сущностями по умолчанию. Различные отношения сущностей (один-к-одному, один-ко-многим и многие-ко-многим) можно настроить, используя приведенные ниже примеры.
Связь One-to-One
Отношения один-к-одному устанавливаются по умолчанию, когда только одна сущность сопоставляется с другой. Пример: в паспорте будет только один человек, а у человека только один паспорт.
type Person @entity {
id: ID!
}type Passport @entity {
id: ID!
owner: Person!
}
Связь One-to-Many
Вы можете использовать скобки, чтобы указать, что тип поля включает несколько объектов. Пример: у человека может быть несколько учетных записей.
type Person @entity {
id: ID!
accounts: [Account]
}type Account @entity {
id: ID!
publicAddress: String!
}
Связь Many-to-Many
Связь может быть достигнута путем Mapping реализации для соединения двух других объектов. Пример: каждый человек является частью нескольких групп, а в группах есть несколько разных людей.
type Person @entity {
id: ID!
name: String!
groups: [PersonGroup]
}type PersonGroup @entity {
id: ID!
person: Person!
Group: Group!
}type Group @entity {
id: ID!
name: String!
persons: [PersonGroup]
}
Кроме того, можно создать соединение одной и той же сущности в нескольких полях. Например, у учетной записи может быть несколько переводов, и у каждой передачи есть исходная и конечная учетные записи. Это установит двунаправленную связь между двумя учетными записями (от и до) через таблицу трансфера.
type Account @entity {
id: ID!
publicAddress: String!
}type Transfer @entity {
id: ID!
amount: BigInt
from: Account!
to: Account!
}
Обратный Lookup
Чтобы включить обратный поиск объекта в отношении, присоедините @devedFrom к полю и укажите на его поле обратного просмотра другого объекта. Это создает виртуальное поле на объекте, которое можно запрашивать. Трансфер из учетной записи, доступен путем создания поля переводов:
type Account @entity {
id: ID!
publicAddress: String!
sentTransfers: [Transfer] @derivedFrom(field: "from")
recievedTransfers: [Transfer] @derivedFrom(field: "to")
}type Transfer @entity {
id: ID!
amount: BigInt
from: Account!
to: Account!
}
JSON
SubQuery поддерживает сохранение данных в формате JSON, что является быстрым способом хранения структурированных данных. Разработчики автоматически сгенерируют соответствующие интерфейсы JSON для запроса этих данных и сэкономят ваше время на определение сущностей и управление ими.
Они рекомендуют пользователям использовать тип JSON в следующих случаях:
- Хранение структурированных данных в одном поле более управляемо, чем создание нескольких отдельных сущностей.
- Сохранение произвольных пользовательских настроек ключа/значения (где значение может быть логическим, текстовым или числовым, и вы не хотите иметь отдельные столбцы для разных типов данных)
- Схема непостоянна и часто меняется.
Определите свойство как тип JSON, добавив аннотацию jsonField в сущность, это автоматически сгенерирует интерфейсы для всех объектов JSON в вашем проекте в types/interfaces.ts, и вы сможете получить к ним доступ в своей функции сопоставления. В отличие от объекта, объект директивы jsonField не требует поля id. Объект JSON также может быть вложен в другие объекты JSON. Недостатком использования типов JSON является небольшое влияние на эффективность запроса при фильтрации, поскольку каждый раз выполняется текстовый поиск по всей сущности. Однако в службе запросов влияние по-прежнему приемлемо. Вот пример того, как использовать contains в запросе GraphQL в поле JSON, чтобы найти первые 5 собственных телефонных номеров пользователей, содержащие «0064».
#To find the the first 5 users own phone numbers contains '0064'.
query{
user(
first: 5,
filter: {
contactCard: {
contains: [{ phone: "0064" }]
}
}){
nodes{
id
contactCard
}
}
}
Таким образом мы рассмотрели самые важные технические моменты, на которые обращают внимание пользователей разработчики SubQuery.
Остались вопросы?
Получить дополнительную информацию и задать любые вопросы о SubQuery можно в русскоязычной группе SubQuery в Telegram.