Memanipulasi urutan resolusi field
Tujuan dari direktif @export yang disediakan oleh Multiple Query Execution adalah untuk mengekspor nilai sebuah field (atau sekumpulan field) ke dalam sebuah variabel, yang kemudian digunakan di tempat lain dalam query.
Direktif ini tidak akan berfungsi jika pembacaan variabel terjadi sebelum nilai diekspor ke dalam variabel tersebut. Oleh karena itu, engine perlu menyediakan cara untuk mengontrol urutan eksekusi field.
Gato GraphQL menyediakan cara untuk memanipulasi urutan eksekusi field melalui query itu sendiri. Engine memuat data dalam iterasi untuk setiap tipe, pertama-tama menyelesaikan semua field dari tipe pertama yang ditemukan dalam query, kemudian menyelesaikan semua field dari tipe kedua yang ditemukan, dan seterusnya hingga tidak ada lagi tipe yang perlu diproses.
Misalnya, query berikut yang melibatkan objek bertipe Director, Film, dan Actor:
{
directors {
name
films {
title
actors {
name
}
}
}
}...diselesaikan oleh engine GraphQL dalam urutan ini:

Jika setelah diproses, sebuah tipe direferensikan kembali dalam query untuk mengambil data yang belum dimuat (misalnya: dari objek tambahan, atau field tambahan dari objek yang sudah dimuat), maka tipe tersebut ditambahkan kembali di akhir daftar iterasi.
Misalnya, jika kita juga melakukan query pada field preferredDirector milik Actor (yang mengembalikan objek bertipe Director) seperti ini:
{
directors {
name
films {
title
actors {
name
preferredDirector {
name
}
}
}
}
}...maka engine GraphQL memproses query dalam urutan ini:

Mari kita lihat bagaimana ini berlaku untuk mengeksekusi @export dalam satu query. Pada percobaan pertama, kita membuat query seperti biasanya, tanpa memikirkan urutan eksekusi field:
query GetPostsAuthorNames {
user(by: { id: 1 }) {
name @export(as: "authorName")
}
posts(filter: { search: $authorName }) {
id
title
}
}Saat menjalankan query, respons yang dihasilkan adalah:

...yang mengandung error berikut:
{
"errors": [
{
"message": "Expression 'authorName' is undefined",
}
]
}Error ini berarti bahwa pada saat variabel $authorName dibaca, nilainya belum diset; masih undefined.
Mari kita lihat mengapa ini terjadi. Pertama, kita analisis tipe apa saja yang muncul dalam query, ditambahkan sebagai komentar di bawah ini:
# Type: Root
query GetPostsAuthorNames {
# Type: User
user(by: {id: 1}) {
# Type: String
name @export(as: "authorName")
}
# Type: Post
posts(filter: { search: $authorName }) {
# Type: ID
id
# Type: String
title
}
}Untuk memproses tipe-tipe dan memuat datanya, engine data-loading menambahkan tipe query Root ke dalam daftar FIFO (First-In, First-Out), sehingga [Root] menjadi daftar awal yang diteruskan ke algoritma, kemudian melakukan iterasi atas tipe-tipe secara berurutan, seperti ini:
| # | Operasi | Daftar |
|---|---|---|
| 0 | Siapkan daftar FIFO | [Root] |
| 1a | Ambil tipe pertama dari daftar (Root) | [] |
| 1b | Proses semua field yang di-query dari tipe Root:→ user(by: {id: 1})→ posts(filter: { search: $authorName })Tambahkan tipenya ( User dan Post) ke daftar | [User, Post] |
| 2a | Ambil tipe pertama dari daftar (User) | [Post] |
| 2b | Proses field yang di-query dari tipe User:→ name @export(as: "authorName")Karena ini adalah tipe skalar ( String), tidak perlu ditambahkan ke daftar | [Post] |
| 3a | Ambil tipe pertama dari daftar (Post) | [] |
| 3b | Proses semua field yang di-query dari tipe Post:→ id→ titleKarena ini adalah tipe skalar ( ID dan String), tidak perlu ditambahkan ke daftar | [] |
| 4 | Daftar kosong, iterasi selesai. | Â |
Di sini kita dapat melihat masalahnya: @export dieksekusi pada langkah 2b, tetapi dibaca pada langkah 1b.
Di sinilah kita perlu mengontrol alur eksekusi field. Solusi yang diimplementasikan adalah menunda saat variabel yang diekspor dibaca, yang dicapai dengan melakukan query secara artifisial pada field self dari tipe Root.
Field self, sesuai namanya, mengembalikan objek yang sama; diterapkan pada objek Root, ia mengembalikan objek Root yang sama. Anda mungkin bertanya-tanya: "jika saya sudah memiliki objek root, mengapa saya perlu mengambilnya lagi?". Karena kemudian algoritma engine perlu menambahkan referensi baru ke Root ini di akhir daftar FIFO, dan kita dapat dengan sengaja mendistribusikan field-field yang di-query sebelum atau sesudah setiap iterasi tersebut.
Itulah mengapa field posts(filter:{ search: $authorName }) ditempatkan di dalam field self pada query di atas, dan menjalankan query menghasilkan respons yang diharapkan:
query GetPostsAuthorNames {
user(by: {id: 1}) {
name @export(as: "authorName")
}
self {
posts(filter: { search: $authorName }) {
id
title
}
}
}
Mari kita eksplorasi urutan pemrosesan tipe untuk query ini, untuk memahami mengapa ini berjalan dengan baik:
| # | Operasi | Daftar |
|---|---|---|
| 0 | Siapkan daftar FIFO | [Root] |
| 1a | Ambil tipe pertama dari daftar (Root) | [] |
| 1b | Proses semua field yang di-query dari tipe Root:→ user(by: {id: 1})→ selfTambahkan tipenya ( User dan Root) ke daftar | [User, Root] |
| 2a | Ambil tipe pertama dari daftar (User) | [Root] |
| 2b | Proses field yang di-query dari tipe User:→ name @export(as: "authorName")Karena ini adalah tipe skalar ( String), tidak perlu ditambahkan ke daftar | [Root] |
| 3a | Ambil tipe pertama dari daftar (Root) | [] |
| 3b | Proses field yang di-query dari tipe Root:→ posts(filter:{ search: $authorName })Tambahkan tipenya ( Post) ke daftar | [Post] |
| 4a | Ambil tipe pertama dari daftar (Post) | [] |
| 4b | Proses semua field yang di-query dari tipe Post:→ id→ titleKarena ini adalah tipe skalar ( ID dan String), tidak perlu ditambahkan ke daftar | [] |
| 5 | Daftar kosong, iterasi selesai. | Â |
Sekarang kita dapat melihat bahwa masalah telah teratasi: @export dieksekusi pada langkah 2b, dan dibaca pada langkah 3b.
Multiple Query Execution melakukan hal ini persis saat memisahkan query: ia mengkonversi dokumen GraphQL dengan menambahkan field-field self, sehingga field-field dalam setiap operasi hanya dieksekusi setelah semua field dalam semua operasi sebelumnya telah diselesaikan.