The Graph: إصلاح الاستعلام عن بيانات Web3
هذه المرة سنلقي نظرة فاحصة على The Graph والذي أصبح بشكل أساسي جزءًا من الحزمة القياسية لتطوير التطبيقات اللامركزية (dapps) في العام الماضي. دعونا نرى أولاً كيف كنا سنفعل الأشياء بالطريقة التقليدية...
بدون The Graph...
لذا دعونا نأخذ مثالاً بسيطاً لأغراض التوضيح. كلنا نحب الألعاب، لذا تخيل لعبة بسيطة يضع فيها المستخدمون الرهانات:
pragma solidity 0.7.1;
contract Game {
uint256 totalGamesPlayerWon = 0;
uint256 totalGamesPlayerLost = 0;
event BetPlaced(address player, uint256 value, bool hasWon);
function placeBet() external payable {
bool hasWon = evaluateBetForPlayer(msg.sender);
if (hasWon) {
(bool success, ) = msg.sender.call{ value: msg.value * 2 }('');
require(success, "Transfer failed");
totalGamesPlayerWon++;
} else {
totalGamesPlayerLost++;
}
emit BetPlaced(msg.sender, msg.value, hasWon);
}
}
الآن لنفترض أننا في تطبيقنا اللامركزي (dapp)، نريد عرض إجمالي الرهانات، وإجمالي الألعاب التي خسرها/فاز بها، وتحديثها أيضاً كلما لعب شخص ما مرة أخرى. سيكون النهج كالتالي:
- جلب
totalGamesPlayerWon. - جلب
totalGamesPlayerLost. - الاشتراك في أحداث
BetPlaced.
يمكننا الاستماع إلى الحدث في Web3 (opens in a new tab) كما هو موضح على اليمين، ولكنه يتطلب التعامل مع عدد غير قليل من الحالات.
GameContract.events.BetPlaced({
fromBlock: 0
}, function(error, event) { console.log(event); })
.on('data', function(event) {
// تم إطلاق الحدث
})
.on('changed', function(event) {
// تمت إزالة الحدث مرة أخرى
})
.on('error', function(error, receipt) {
// تم رفض المعاملة
});
الآن لا يزال هذا جيداً إلى حد ما لمثالنا البسيط. ولكن لنفترض أننا نريد الآن عرض مبالغ الرهانات التي خسرها/فاز بها اللاعب الحالي فقط. حسناً، لم يحالفنا الحظ، من الأفضل لك نشر عقد جديد يخزن تلك القيم وجلبها. والآن تخيل عقداً ذكياً وتطبيقاً لامركزياً (dapp) أكثر تعقيداً، يمكن أن تصبح الأمور فوضوية بسرعة.
يمكنك أن ترى كيف أن هذا ليس مثالياً:
- لا يعمل مع العقود المنشورة بالفعل.
- تكاليف غاز إضافية لتخزين تلك القيم.
- يتطلب استدعاءً آخر لجلب البيانات لعقدة إيثيريوم.
الآن دعونا نلقي نظرة على حل أفضل.
دعني أقدم لك GraphQL
أولاً لنتحدث عن GraphQL، الذي تم تصميمه وتنفيذه في الأصل بواسطة فيسبوك. قد تكون على دراية بنموذج REST API التقليدي. الآن تخيل بدلاً من ذلك أنه يمكنك كتابة استعلام للحصول على البيانات التي تريدها بالضبط:
تلتقط الصورتان إلى حد كبير جوهر GraphQL. باستخدام الاستعلام الموجود على اليمين، يمكننا تحديد البيانات التي نريدها بالضبط، لذلك نحصل على كل شيء في طلب واحد ولا شيء أكثر مما نحتاجه بالضبط. يتعامل خادم GraphQL مع جلب جميع البيانات المطلوبة، لذلك من السهل جداً على جانب المستهلك في الواجهة الأمامية استخدامه. هذا شرح رائع (opens in a new tab) لكيفية تعامل الخادم بالضبط مع الاستعلام إذا كنت مهتماً.
الآن مع هذه المعرفة، دعونا نقفز أخيراً إلى مساحة سلسلة الكتل و The Graph.
ما هو The Graph؟
سلسلة الكتل هي قاعدة بيانات لامركزية، ولكن على عكس ما هو معتاد، ليس لدينا لغة استعلام لقاعدة البيانات هذه. حلول استرداد البيانات مؤلمة أو مستحيلة تماماً. The Graph هو بروتوكول لامركزي لفهرسة بيانات سلسلة الكتل والاستعلام عنها. وكما خمنت على الأرجح، فإنه يستخدم GraphQL كلغة استعلام.
الأمثلة هي دائماً أفضل طريقة لفهم شيء ما، لذا دعونا نستخدم The Graph لمثال GameContract الخاص بنا.
كيفية إنشاء رسم بياني فرعي
يُطلق على تعريف كيفية فهرسة البيانات اسم رسم بياني فرعي. ويتطلب ثلاثة مكونات:
- البيان (
subgraph.yaml) - المخطط (
schema.graphql) - التعيين (
mapping.ts)
البيان (subgraph.yaml)
البيان هو ملف التكوين الخاص بنا ويحدد:
- العقود الذكية التي يجب فهرستها (العنوان، الشبكة،
ABI...) - الأحداث التي يجب الاستماع إليها
- أشياء أخرى يجب الاستماع إليها مثل استدعاءات الدوال أو الكتل
- دوال التعيين التي يتم استدعاؤها (انظر
mapping.tsأدناه)
يمكنك تحديد عقود ومعالجات متعددة هنا. سيحتوي الإعداد النموذجي على مجلد رسم بياني فرعي داخل مشروع Hardhat مع مستودعه الخاص. ثم يمكنك بسهولة الإشارة إلى ABI.
لأسباب تتعلق بالراحة، قد ترغب أيضاً في استخدام أداة قوالب مثل mustache. ثم تقوم بإنشاء subgraph.template.yaml وإدراج العناوين بناءً على أحدث عمليات النشر. للحصول على مثال إعداد أكثر تقدماً، راجع على سبيل المثال مستودع الرسم البياني الفرعي لآفي (opens in a new tab).
ويمكن الاطلاع على الوثائق الكاملة هنا (opens in a new tab).
specVersion: 0.0.1
description: Placing Bets on Ethereum
repository: - GitHub link -
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: GameContract
network: mainnet
source:
address: '0x2E6454...cf77eC'
abi: GameContract
startBlock: 6175244
mapping:
kind: ethereum/events
apiVersion: 0.0.1
language: wasm/assemblyscript
entities:
- GameContract
abis:
- name: GameContract
file: ../build/contracts/GameContract.json
eventHandlers:
- event: PlacedBet(address,uint256,bool)
handler: handleNewBet
file: ./src/mapping.ts
المخطط (schema.graphql)
المخطط هو تعريف بيانات GraphQL. سيسمح لك بتحديد الكيانات الموجودة وأنواعها. الأنواع المدعومة من The Graph هي
BytesIDStringBooleanIntBigIntBigDecimal
يمكنك أيضاً استخدام الكيانات كنوع لتحديد العلاقات. في مثالنا، نحدد علاقة واحد إلى متعدد (1-to-many) من اللاعب إلى الرهانات. تعني علامة ! أن القيمة لا يمكن أن تكون فارغة. يمكن الاطلاع على الوثائق الكاملة هنا (opens in a new tab).
type Bet @entity {
id: ID!
player: Player!
playerHasWon: Boolean!
time: Int!
}
type Player @entity {
id: ID!
totalPlayedCount: Int
hasWonCount: Int
hasLostCount: Int
bets: [Bet]!
}
التعيين (mapping.ts)
يحدد ملف التعيين في The Graph دوالنا التي تحول الأحداث الواردة إلى كيانات. تمت كتابته بلغة AssemblyScript، وهي مجموعة فرعية من TypeScript. هذا يعني أنه يمكن تجميعه في WASM (WebAssembly) لتنفيذ التعيين بشكل أكثر كفاءة وقابلية للنقل.
ستحتاج إلى تحديد كل دالة مسماة في ملف subgraph.yaml، لذلك في حالتنا نحتاج إلى دالة واحدة فقط: handleNewBet. نحاول أولاً تحميل كيان اللاعب (Player) من عنوان المرسل كمعرف (id). إذا لم يكن موجوداً، نقوم بإنشاء كيان جديد ونملأه بقيم البداية.
ثم نقوم بإنشاء كيان رهان (Bet) جديد. سيكون المعرف (id) لهذا هو event.transaction.hash.toHex() + "-" + event.logIndex.toString() مما يضمن دائماً قيمة فريدة. استخدام التجزئة فقط ليس كافياً لأن شخصاً ما قد يستدعي دالة placeBet عدة مرات في معاملة واحدة عبر عقد ذكي.
أخيراً يمكننا تحديث كيان اللاعب (Player) بجميع البيانات. لا يمكن الدفع إلى المصفوفات مباشرة، ولكن يجب تحديثها كما هو موضح هنا. نستخدم المعرف (id) للإشارة إلى الرهان. و .save() مطلوب في النهاية لتخزين كيان.
يمكن الاطلاع على الوثائق الكاملة هنا: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings (opens in a new tab). يمكنك أيضاً إضافة مخرجات التسجيل إلى ملف التعيين، انظر هنا (opens in a new tab).
import { Bet, Player } from "../generated/schema"
import { PlacedBet } from "../generated/GameContract/GameContract"
export function handleNewBet(event: PlacedBet): void {
let player = Player.load(event.transaction.from.toHex())
if (player == null) {
// إنشاء إذا لم يكن موجوداً بعد
player = new Player(event.transaction.from.toHex())
player.bets = new Array<string>(0)
player.totalPlayedCount = 0
player.hasWonCount = 0
player.hasLostCount = 0
}
let bet = new Bet(
event.transaction.hash.toHex() + "-" + event.logIndex.toString()
)
bet.player = player.id
bet.playerHasWon = event.params.hasWon
bet.time = event.block.timestamp
bet.save()
player.totalPlayedCount++
if (event.params.hasWon) {
player.hasWonCount++
} else {
player.hasLostCount++
}
// تحديث المصفوفة بهذا الشكل
let bets = player.bets
bets.push(bet.id)
player.bets = bets
player.save()
}
استخدامه في الواجهة الأمامية
باستخدام شيء مثل Apollo Boost، يمكنك بسهولة دمج The Graph في تطبيق React اللامركزي الخاص بك (أو Apollo-Vue). خاصة عند استخدام خطافات React و Apollo، فإن جلب البيانات بسيط مثل كتابة استعلام GraphQL واحد في المكون الخاص بك. قد يبدو الإعداد النموذجي هكذا:
// عرض جميع الرسوم البيانية الفرعية: https://thegraph.com/explorer/
const client = new ApolloClient({
uri: "{{ subgraphUrl }}",
})
ReactDOM.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>,
document.getElementById("root")
)
والآن يمكننا كتابة استعلام مثل هذا على سبيل المثال. هذا سيجلب لنا
- كم مرة فاز المستخدم الحالي
- كم مرة خسر المستخدم الحالي
- قائمة بالطوابع الزمنية مع جميع رهاناته السابقة
كل ذلك في طلب واحد لخادم GraphQL.
const myGraphQlQuery = gql`
players(where: { id: $currentUser }) {
totalPlayedCount
hasWonCount
hasLostCount
bets {
time
}
}
`
const { loading, error, data } = useQuery(myGraphQlQuery)
React.useEffect(() => {
if (!loading && !error && data) {
console.log({ data })
}
}, [loading, error, data])
لكننا نفتقد قطعة أخيرة من اللغز وهي الخادم. يمكنك إما تشغيله بنفسك أو استخدام الخدمة المستضافة.
خادم The Graph
مستكشف Graph: الخدمة المستضافة
أسهل طريقة هي استخدام الخدمة المستضافة. اتبع التعليمات هنا (opens in a new tab) لنشر رسم بياني فرعي. بالنسبة للعديد من المشاريع، يمكنك في الواقع العثور على رسوم بيانية فرعية موجودة في المستكشف (opens in a new tab).
تشغيل العقدة الخاصة بك
بدلاً من ذلك، يمكنك تشغيل العقدة الخاصة بك. الوثائق هنا (opens in a new tab). أحد أسباب القيام بذلك قد يكون استخدام شبكة غير مدعومة من قبل الخدمة المستضافة. الشبكات المدعومة حالياً يمكن العثور عليها هنا (opens in a new tab).
المستقبل اللامركزي
يدعم GraphQL التدفقات أيضاً للأحداث الواردة حديثاً. هذه مدعومة على The Graph من خلال التدفقات الفرعية (Substreams) (opens in a new tab) والتي هي حالياً في مرحلة تجريبية مفتوحة.
في عام 2021 (opens in a new tab) بدأ The Graph انتقاله إلى شبكة فهرسة لامركزية. يمكنك قراءة المزيد عن بنية شبكة الفهرسة اللامركزية هذه هنا (opens in a new tab).
هناك جانبان رئيسيان هما:
- يدفع المستخدمون للمفهرسين مقابل الاستعلامات.
- يقوم المفهرسون بتخزين حصة من رموز
Graph(GRT).






