Lompat ke konten utama

The Graph: Memperbaiki kueri data Web3

Solidity
kontrak pintar
kueri
the graph
react
Menengah
Markus Waas
6 September 2020
8 menit baca

Kali ini kita akan melihat lebih dekat The Graph yang pada dasarnya menjadi bagian dari tumpukan standar untuk mengembangkan dapps pada tahun lalu. Mari kita lihat terlebih dahulu bagaimana kita akan melakukan berbagai hal dengan cara tradisional...

Tanpa The Graph...

Jadi mari kita gunakan contoh sederhana untuk tujuan ilustrasi. Kita semua menyukai permainan, jadi bayangkan sebuah permainan sederhana dengan pengguna yang memasang taruhan:

1pragma solidity 0.7.1;
2
3contract Game {
4 uint256 totalGamesPlayerWon = 0;
5 uint256 totalGamesPlayerLost = 0;
6 event BetPlaced(address player, uint256 value, bool hasWon);
7
8 function placeBet() external payable {
9 bool hasWon = evaluateBetForPlayer(msg.sender);
10
11 if (hasWon) {
12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');
13 require(success, "Transfer failed");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
Tampilkan semua

Sekarang katakanlah di dapp kita, kita ingin menampilkan total taruhan, total permainan yang kalah/menang dan juga memperbaruinya setiap kali seseorang bermain lagi. Pendekatannya adalah:

  1. Mengambil totalGamesPlayerWon.
  2. Mengambil totalGamesPlayerLost.
  3. Berlangganan ke event BetPlaced.

Kita dapat mendengarkan event di Web3 (opens in a new tab) seperti yang ditunjukkan di sebelah kanan, tetapi ini memerlukan penanganan beberapa kasus.

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // event fired // event dipicu
6})
7.on('changed', function(event) {
8 // event was removed again // event dihapus lagi
9})
10.on('error', function(error, receipt) {
11 // tx rejected // tx ditolak
12});
Tampilkan semua

Sekarang ini masih cukup baik untuk contoh sederhana kita. Tetapi katakanlah kita sekarang ingin menampilkan jumlah taruhan yang kalah/menang hanya untuk pemain saat ini. Sayangnya kita kurang beruntung, Anda lebih baik menerapkan kontrak baru yang menyimpan nilai-nilai tersebut dan mengambilnya. Dan sekarang bayangkan kontrak pintar dan dapp yang jauh lebih rumit, segalanya bisa menjadi berantakan dengan cepat.

Seseorang Tidak Bisa Begitu Saja Mengkueri

Anda dapat melihat bagaimana ini tidak optimal:

  • Tidak berfungsi untuk kontrak yang sudah diterapkan.
  • Biaya gas tambahan untuk menyimpan nilai-nilai tersebut.
  • Memerlukan panggilan lain untuk mengambil data untuk sebuah node Ethereum.

Itu tidak cukup baik

Sekarang mari kita lihat solusi yang lebih baik.

Izinkan saya memperkenalkan Anda pada GraphQL

Pertama mari kita bicara tentang GraphQL, yang awalnya dirancang dan diimplementasikan oleh Facebook. Anda mungkin akrab dengan model REST API tradisional. Sekarang bayangkan sebagai gantinya Anda dapat menulis kueri untuk data yang tepat seperti yang Anda inginkan:

GraphQL API vs. REST API

Demonstrasi animasi kueri GraphQL di The Graph playground

Kedua gambar tersebut cukup menangkap esensi dari GraphQL. Dengan kueri di sebelah kanan kita dapat menentukan dengan tepat data apa yang kita inginkan, jadi di sana kita mendapatkan semuanya dalam satu permintaan dan tidak lebih dari apa yang kita butuhkan. Server GraphQL menangani pengambilan semua data yang diperlukan, sehingga sangat mudah digunakan oleh sisi konsumen frontend. Ini adalah penjelasan yang bagus (opens in a new tab) tentang bagaimana tepatnya server menangani kueri jika Anda tertarik.

Sekarang dengan pengetahuan itu, mari kita akhirnya melompat ke ruang blockchain dan The Graph.

Apa itu The Graph?

Sebuah blockchain adalah basis data yang terdesentralisasi, tetapi berbeda dengan apa yang biasanya terjadi, kita tidak memiliki bahasa kueri untuk basis data ini. Solusi untuk mengambil data sangat menyulitkan atau sama sekali tidak mungkin. The Graph adalah protokol terdesentralisasi untuk mengindeks dan mengkueri data blockchain. Dan Anda mungkin sudah bisa menebaknya, ia menggunakan GraphQL sebagai bahasa kueri.

The Graph

Contoh selalu menjadi yang terbaik untuk memahami sesuatu, jadi mari kita gunakan The Graph untuk contoh GameContract kita.

Cara membuat Subgraph

Definisi tentang cara mengindeks data disebut subgraph. Ini membutuhkan tiga komponen:

  1. Manifes (subgraph.yaml)
  2. Skema (schema.graphql)
  3. Pemetaan (mapping.ts)

Manifes (subgraph.yaml)

Manifes adalah file konfigurasi kita dan mendefinisikan:

  • kontrak pintar mana yang akan diindeks (alamat, jaringan, ABI...)
  • event mana yang akan didengarkan
  • hal lain yang akan didengarkan seperti panggilan fungsi atau blok
  • fungsi pemetaan yang dipanggil (lihat mapping.ts di bawah)

Anda dapat mendefinisikan beberapa kontrak dan penangan (handler) di sini. Pengaturan tipikal akan memiliki folder subgraph di dalam proyek Hardhat dengan repositorinya sendiri. Kemudian Anda dapat dengan mudah mereferensikan ABI.

Untuk alasan kenyamanan, Anda mungkin juga ingin menggunakan alat templat seperti mustache. Kemudian Anda membuat subgraph.template.yaml dan memasukkan alamat berdasarkan penerapan terbaru. Untuk contoh pengaturan yang lebih canggih, lihat misalnya repo subgraph Aave (opens in a new tab).

Dan dokumentasi lengkapnya dapat dilihat di sini (opens in a new tab).

1specVersion: 0.0.1
2description: Placing Bets on Ethereum
3repository: - GitHub link -
4schema:
5 file: ./schema.graphql
6dataSources:
7 - kind: ethereum/contract
8 name: GameContract
9 network: mainnet
10 source:
11 address: '0x2E6454...cf77eC'
12 abi: GameContract
13 startBlock: 6175244
14 mapping:
15 kind: ethereum/events
16 apiVersion: 0.0.1
17 language: wasm/assemblyscript
18 entities:
19 - GameContract
20 abis:
21 - name: GameContract
22 file: ../build/contracts/GameContract.json
23 eventHandlers:
24 - event: PlacedBet(address,uint256,bool)
25 handler: handleNewBet
26 file: ./src/mapping.ts
Tampilkan semua

Skema (schema.graphql)

Skema adalah definisi data GraphQL. Ini akan memungkinkan Anda untuk mendefinisikan entitas mana yang ada dan tipenya. Tipe yang didukung dari The Graph adalah

  • Bytes
  • ID
  • String
  • Boolean
  • Int
  • BigInt
  • BigDecimal

Anda juga dapat menggunakan entitas sebagai tipe untuk mendefinisikan hubungan. Dalam contoh kita, kita mendefinisikan hubungan 1-ke-banyak dari pemain ke taruhan. Tanda ! berarti nilainya tidak boleh kosong. Dokumentasi lengkapnya dapat dilihat di sini (opens in a new tab).

1type Bet @entity {
2 id: ID!
3 player: Player!
4 playerHasWon: Boolean!
5 time: Int!
6}
7
8type Player @entity {
9 id: ID!
10 totalPlayedCount: Int
11 hasWonCount: Int
12 hasLostCount: Int
13 bets: [Bet]!
14}
Tampilkan semua

Pemetaan (mapping.ts)

File pemetaan di The Graph mendefinisikan fungsi kita yang mengubah event yang masuk menjadi entitas. Ini ditulis dalam AssemblyScript, bagian dari Typescript. Ini berarti ia dapat dikompilasi menjadi WASM (WebAssembly) untuk eksekusi pemetaan yang lebih efisien dan portabel.

Anda perlu mendefinisikan setiap fungsi yang dinamai dalam file subgraph.yaml, jadi dalam kasus kita, kita hanya membutuhkan satu: handleNewBet. Kita pertama-tama mencoba memuat entitas Player dari alamat pengirim sebagai id. Jika tidak ada, kita membuat entitas baru dan mengisinya dengan nilai awal.

Kemudian kita membuat entitas Bet baru. Id untuk ini adalah event.transaction.hash.toHex() + "-" + event.logIndex.toString() yang memastikan nilainya selalu unik. Hanya menggunakan hash tidak cukup karena seseorang mungkin memanggil fungsi placeBet beberapa kali dalam satu transaksi melalui kontrak pintar.

Terakhir kita dapat memperbarui entitas Player dengan semua data. Array tidak dapat didorong (push) secara langsung, tetapi perlu diperbarui seperti yang ditunjukkan di sini. Kita menggunakan id untuk mereferensikan taruhan. Dan .save() diperlukan di akhir untuk menyimpan entitas.

Dokumentasi lengkapnya dapat dilihat di sini: https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings (opens in a new tab). Anda juga dapat menambahkan keluaran pencatatan (logging) ke file pemetaan, lihat di sini (opens in a new tab).

1import { Bet, Player } from "../generated/schema"
2import { PlacedBet } from "../generated/GameContract/GameContract"
3
4export function handleNewBet(event: PlacedBet): void {
5 let player = Player.load(event.transaction.from.toHex())
6
7 if (player == null) {
8 // create if doesn't exist yet // buat jika belum ada
9 player = new Player(event.transaction.from.toHex())
10 player.bets = new Array<string>(0)
11 player.totalPlayedCount = 0
12 player.hasWonCount = 0
13 player.hasLostCount = 0
14 }
15
16 let bet = new Bet(
17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()
18 )
19 bet.player = player.id
20 bet.playerHasWon = event.params.hasWon
21 bet.time = event.block.timestamp
22 bet.save()
23
24 player.totalPlayedCount++
25 if (event.params.hasWon) {
26 player.hasWonCount++
27 } else {
28 player.hasLostCount++
29 }
30
31 // update array like this // perbarui array seperti ini
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
Tampilkan semua

Menggunakannya di Frontend

Menggunakan sesuatu seperti Apollo Boost, Anda dapat dengan mudah mengintegrasikan The Graph di dapp React Anda (atau Apollo-Vue). Terutama saat menggunakan React hooks dan Apollo, mengambil data semudah menulis satu kueri GraphQL di komponen Anda. Pengaturan tipikal mungkin terlihat seperti ini:

1// See all subgraphs: https://thegraph.com/explorer/ // Lihat semua subgraph: https://thegraph.com/explorer/
2const client = new ApolloClient({
3 uri: "{{ subgraphUrl }}",
4})
5
6ReactDOM.render(
7 <ApolloProvider client={client}>
8 <App />
9 </ApolloProvider>,
10 document.getElementById("root")
11)
Tampilkan semua

Dan sekarang kita dapat menulis misalnya kueri seperti ini. Ini akan mengambilkan kita

  • berapa kali pengguna saat ini telah menang
  • berapa kali pengguna saat ini telah kalah
  • daftar stempel waktu dengan semua taruhan sebelumnya

Semuanya dalam satu permintaan tunggal ke server GraphQL.

1const myGraphQlQuery = gql`
2 players(where: { id: $currentUser }) {
3 totalPlayedCount
4 hasWonCount
5 hasLostCount
6 bets {
7 time
8 }
9 }
10`
11
12const { loading, error, data } = useQuery(myGraphQlQuery)
13
14React.useEffect(() => {
15 if (!loading && !error && data) {
16 console.log({ data })
17 }
18}, [loading, error, data])
Tampilkan semua

Sihir

Tetapi kita kehilangan satu bagian terakhir dari teka-teki dan itu adalah server. Anda dapat menjalankannya sendiri atau menggunakan layanan yang di-host.

Server The Graph

Graph Explorer: Layanan yang di-host

Cara termudah adalah menggunakan layanan yang di-host. Ikuti instruksi di sini (opens in a new tab) untuk menerapkan subgraph. Untuk banyak proyek, Anda sebenarnya dapat menemukan subgraph yang ada di penjelajah (opens in a new tab).

The Graph-Explorer

Menjalankan node Anda sendiri

Sebagai alternatif, Anda dapat menjalankan node Anda sendiri. Dokumen di sini (opens in a new tab). Salah satu alasan untuk melakukan ini mungkin karena menggunakan jaringan yang tidak didukung oleh layanan yang di-host. Jaringan yang saat ini didukung dapat ditemukan di sini (opens in a new tab).

Masa depan yang terdesentralisasi

GraphQL juga mendukung aliran (stream) untuk event yang baru masuk. Ini didukung pada grafik melalui Substreams (opens in a new tab) yang saat ini dalam versi beta terbuka.

Pada tahun 2021 (opens in a new tab) The Graph memulai transisinya ke jaringan pengindeksan yang terdesentralisasi. Anda dapat membaca lebih lanjut tentang arsitektur jaringan pengindeksan yang terdesentralisasi ini di sini (opens in a new tab).

Dua aspek utama adalah:

  1. Pengguna membayar pengindeks untuk kueri.
  2. Pengindeks melakukan stake Graph Tokens (GRT).

Pembaruan terakhir halaman: 26 Februari 2026

Apakah tutorial ini membantu?