Çoklu Query Yürütme
Birden fazla queries'i tek bir query'de birleştirerek aralarında durumu paylaşır ve istenen sırayla yürütür.
Açıklama
Çoklu query yürütme, birden fazla queries'i tek bir query'de birleştirir ve bunların istenen aynı sırayla yürütülmesini sağlar. Operasyonlar, yalnızca bir kez hesaplanan ancak belge boyunca birden fazla kez okunabilen dinamik değişkenler aracılığıyla birbirleriyle durum paylaşabilir.
query SomeQuery {
id @export(as: "rootID")
}
query AnotherQuery
@depends(on: "SomeQuery")
{
_echo(value: $rootID )
}Bu özellik çeşitli avantajlar sunar:
- Performansı artırır: GraphQL sunucusuna karşı bir query yürütmek, yanıtını beklemek ve ardından bu sonucu kullanarak başka bir query yürütmek yerine, queries'i bir araya getirip tek bir istekte yürütebiliriz; böylece birden fazla HTTP bağlantısından kaynaklanan gecikmeyi önlemiş oluruz.
- GraphQL queries'imizi birbirine bağımlı atomik operasyonlar (ya da mantıksal birimler) olarak yönetmemize olanak tanır; bu operasyonlar bir önceki operasyonun sonucuna göre koşullu olarak yürütülebilir.
Çoklu query yürütme, query batching'den farklıdır; query batching'de GraphQL sunucusu da tek bir istekte birden fazla query yürütür, ancak bu queries yalnızca birbiri ardına ve birbirinden bağımsız olarak yürütülür.
Etkinleştirilen direktifler
Çoklu query yürütme etkinleştirildiğinde, aşağıdaki direktifler GraphQL şemasında kullanılabilir hale gelir:
@depends(operasyon direktifi): Bir operasyonun (queryveyamutation) kendisinden önce hangi diğer operasyonların yürütülmesi gerektiğini belirtmesi için@export(alan direktifi): Bir query'deki bir alan değerini dinamik değişken olarak dışa aktarmak ve başka bir query'deki bir alan ya da direktife girdi olarak kullanmak için@exportFrom(alan direktifi):@export'a benzer, ancak kapsamlı dinamik değişkenin değerini dışa aktarmak için kullanılır (@passOnwards(as: "...")veya@applyField(passOnwardsAs: "...")aracılığıyla iletilir)@deferredExport(alan direktifi):@export'a benzer, ancak Multi-Field Directives ile birlikte kullanılmak üzere tasarlanmıştır
Bunlara ek olarak, @include ve @skip direktifleri de operasyon direktifleri olarak kullanılabilir hale gelir (normalde yalnızca alan direktifleridir) ve bir operasyonu belirli bir koşulu sağlaması durumunda koşullu olarak yürütmek için kullanılabilir.
@depends
GraphQL belgesi birden fazla operasyon içerdiğinde, sunucuya hangisini yürüteceğini URL parametresi ?operationName=... aracılığıyla belirtiriz; aksi takdirde son operasyon yürütülür.
Bu başlangıç operasyonundan itibaren sunucu, depends(on: [...]) direktifi eklenerek tanımlanan yürütülecek tüm operasyonları toplar ve bunları bağımlılıklara saygı göstererek karşılık gelen sırayla yürütür.
Direktif argümanı operations, bir operasyon adları dizisi ([String]) alır ya da tek bir operasyon adı (String) da sağlayabiliriz.
Bu query'de ?operationName=Four parametresini geçiyoruz ve yürütülen operasyonlar (query veya mutation olsun) ["One", "Two", "Three", "Four"] olacaktır:
mutation One {
# Do something ...
}
mutation Two {
# Do something ...
}
query Three @depends(on: ["One", "Two"]) {
# Do something ...
}
query Four @depends(on: "Three") {
# Do something ...
}@export
@export direktifi, bir alanın (veya alanlar kümesinin) değerini dinamik bir değişken olarak dışa aktarır; bu değişken başka bir query'deki bir alan ya da query'de girdi olarak kullanılır.
Örneğin, bu query'de oturum açmış kullanıcının adını dışa aktarıyoruz ve bu değeri söz konusu dizeyi içeren gönderileri aramak için kullanıyoruz (lütfen dikkat edin: $loggedInUserName değişkeni dinamik olduğu için FindPosts operasyonunda tanımlanmasına gerek yoktur):
query GetLoggedInUserName {
me {
name @export(as: "loggedInUserName")
}
}
query FindPosts @depends(on: "GetLoggedInUserName") {
posts(filter: { search: $loggedInUserName }) {
id
}
}@exportFrom
@export'a benzerdir, ancak alan değerini dışa aktarmak yerine, @passOnwards(as: "...") veya @applyField(passOnwardsAs: "...") aracılığıyla iletilen kapsamlı dinamik değişkenin değerini dışa aktarır.
Örneğin, bu query'de dizideki öğeleri değiştirmek ve bu yeni değeri kapsamlı dinamik değişken $replaced'e atamak için @applyField kullanıyoruz. Ardından, bu değeri dinamik değişken $replacedList aracılığıyla global olarak erişilebilir kılmak için @exportFrom kullanıyoruz; böylece sonraki bir query'den alınabilir.
query One {
originalList: _echo(value: ["Hello everyone", "How are you?"])
@underEachArrayItem(
passValueOnwardsAs: "value"
affectDirectivesUnderPos: [1, 2]
)
@applyField(
name: "_strReplace"
arguments: {
search: " "
replaceWith: "-"
in: $value
},
passOnwardsAs: "replaced"
)
@exportFrom(
scopedDynamicVariable: $replaced,
as: "replacedList"
)
}
query Two @depends(on: "One") {
transformedList: _echo(value: $replacedList)
}Bu şunu üretecektir:
{
"data": {
"originalList": [
"Hello everyone",
"How are you?"
],
"transformedList": [
"Hello-everyone",
"How-are-you?"
]
}
}@deferredExport
Multi-Field Directives özelliği etkinleştirildiğinde ve birden fazla alanın değerini bir sözlüğe dışa aktardığımızda, alan değerini dışa aktarmadan önce her ilgili alanın tüm direktiflerinin yürütüldüğünü garanti etmek için @export yerine @deferredExport kullanın.
Örneğin, bu query'de birinci alana @strUpperCase direktifi, ikincisine ise @strTitleCase uygulanmaktadır. @deferredExport yürütüldüğünde, dışa aktarılan değer bu direktiflerin uygulanmış halini taşır:
query One {
id @strUpperCase # Will be exported as "ROOT"
again: id @strTitleCase # Will be exported as "Root"
@deferredExport(as: "props", affectAdditionalFieldsUnderPos: [1])
}
query Two @depends(on: "One") {
mirrorProps: _echo(value: $props)
}Şunu üretir:
{
"data": {
"id": "ROOT",
"again": "Root",
"mirrorProps": {
"id": "ROOT",
"again": "Root"
}
}
}@skip ve @include (operasyonlarda)
Çoklu Query Yürütme etkinleştirildiğinde, @include ve @skip direktifleri de operasyon direktifleri olarak kullanılabilir hale gelir ve bir operasyonu belirli bir koşulu sağlaması durumunda koşullu olarak yürütmek için kullanılabilir.
Örneğin, bu query'de CheckIfPostExists operasyonu bir $postExists dinamik değişkenini dışa aktarır ve yalnızca bu değişkenin değeri true ise ExecuteOnlyIfPostExists mutation'ı yürütülür:
query CheckIfPostExists($id: ID!) {
# Initialize the dynamic variable to `false`
postExists: _echo(value: false) @export(as: "postExists")
post(by: { id: $id }) {
# Found the Post => Set dynamic variable to `true`
postExists: _echo(value: true) @export(as: "postExists")
}
}
mutation ExecuteOnlyIfPostExists
@depends(on: "CheckIfPostExists")
@include(if: $postExists)
{
# Do something...
}Dinamik değişken çıktıları
@export şunların kombinasyonuna bağlı olarak 6 farklı çıktı üretebilir:
typeargümanının değeri (SINGLE,LISTveyaDICTIONARY)- Direktifin tek bir alana mı yoksa birden fazla alana mı uygulandığı (Multi-Field Directives modülü aracılığıyla)
6 olası çıktı şunlardır:
SINGLEtürü:- Tek alan
- Çoklu alan
LISTtürü:- Tek alan
- Çoklu alan
DICTIONARYtürü:- Tek alan
- Çoklu alan
SINGLE türü / Tek alan
type: SINGLE parametresi geçildiğinde (varsayılan değer olarak ayarlanmıştır) çıktı tek bir değerdir.
Bu query'de:
query {
post(by: { id: 1 }) {
title @export(as: "postTitle", type: SINGLE)
}
}...$postTitle dinamik değişkeni şu değere sahip olacaktır:
"Hello world!"Lütfen dikkat edin: SINGLE bir varlık dizisi üzerinde uygulanırsa, dışa aktarılan değer son varlığa ait olan değerdir.
Bu query'de:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitle", type: SINGLE)
}
}...$postTitle dinamik değişkeni ID'si 5 olan gönderi için şu değere sahip olacaktır:
"Everything good?"SINGLE türü / Çoklu alan
@export birden fazla alana uygulanırsa (Multi-Field Directives modülünün sağladığı affectAdditionalFieldsUnderPos parametresi eklenerek), dinamik değişkene atanan değer { key: field alias, value: field value } biçiminde bir sözlük olur (JSONObject türünde).
Bu query:
query {
post(by: { id: 1 }) {
title
content
@export(
as: "postData",
type: SINGLE,
affectAdditionalFieldsUnderPos: [1]
)
}
}...$postData dinamik değişkenini şu değerle dışa aktarır:
{
"title": "Hello world!",
"content": "Lorem ipsum."
}LIST türü / Tek alan
type: LIST parametresi geçildiğinde, dinamik değişken tüm sorgulanan varlıklardan (çevreleyen alandan) gelen alan değerini içeren bir dizi barındırır.
Bu query çalıştırıldığında (sorgulanan varlıklar ID'si 1 ve 5 olan gönderilerdir):
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postTitles", type: LIST)
}
}...$postTitles dinamik değişkeni şu değere sahip olacaktır:
[
"Hello world!",
"Everything good?"
]LIST türü / Çoklu alan
Direktifin uygulandığı alanların değerlerini içeren sözlüklerden oluşan bir dizi (JSONObject türünde) elde ederiz.
Bu query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsData",
type: LIST,
affectAdditionalFieldsUnderPos: [1]
)
}
}...$postsData dinamik değişkenini şu değerle dışa aktarır:
[
{
"title": "Hello world!",
"content": "Lorem ipsum."
},
{
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
]DICTIONARY türü / Tek alan
type: DICTIONARY parametresi geçildiğinde, dinamik değişken sorgulanan varlığın ID'sini anahtar, alan değerlerini de değer olarak kullanan bir sözlük (JSONObject türünde) barındırır.
Bu query:
query {
posts(filter: { ids: [1, 5] }) {
title @export(as: "postIDTitles", type: DICTIONARY)
}
}...$postIDTitles dinamik değişkenini şu değerle dışa aktarır:
{
"1": "Hello world!",
"5": "Everything good?"
}DICTIONARY türü / Çoklu alan
Bu kombinasyonda, sözlüklerin sözlüğünü dışa aktarırız: { key: entity ID, value: { key: field alias, value: field value } } (JSONObject türü içinde JSONObject türünde girdiler barındırır).
Bu query:
query {
posts(filter: { ids: [1, 5] }) {
title
content
@export(
as: "postsIDProperties",
type: DICTIONARY,
affectAdditionalFieldsUnderPos: [1]
)
}
}...$postsIDProperties dinamik değişkenini şu değerle dışa aktarır:
{
"1": {
"title": "Hello world!",
"content": "Lorem ipsum."
},
"5": {
"title": "Everything good?",
"content": "Quisque convallis libero in sapien pharetra tincidunt."
}
}Dizi veya JSON nesnesi üzerinde yineleme yaparken değerleri dışa aktarma
@export, çevreleyen herhangi bir meta-direktifin kardinalitesine saygı gösterir.
Özellikle, @export dizi öğeleri veya JSON nesne özellikleri üzerinde yineleme yapan bir meta-direktifin (@underEachArrayItem ve @underEachJSONObjectProperty gibi) altına yerleştirildiğinde, dışa aktarılan değer bir dizi olacaktır.
Bu query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underEachArrayItem
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...$contentAttributes'u şu değerle üretir:
[
"List Block",
"Columns Block",
"Columns inside Columns (nested inner blocks)",
"Life is so rich",
"Life is so dynamic"
]Buna karşılık, tüm öğeler üzerinde yineleme yapmak yerine dizideki belirli bir öğeye erişen aynı query (@underEachArrayItem yerine @underArrayItem(index: 0) kullanarak) tek bir değer dışa aktaracaktır.
Bu query:
{
post(by: { id: 19 }) {
coreContentAttributeBlocks: blockFlattenedDataItems(
filterBy: { include: "core/heading" }
)
@underArrayItem(index: 0)
@underJSONObjectProperty(
by: { path: "attributes.content" },
)
@export(
as: "contentAttributes",
)
}
}...$contentAttributes'u şu değerle üretir:
"List Block"Direktif yürütme sırası
@export'tan önce başka direktifler varsa, dışa aktarılan değer bu önceki direktiflerin yaptığı değişiklikleri yansıtır.
Örneğin, bu query'de @export'un @strUpperCase'den önce mi yoksa sonra mı gerçekleştiğine bağlı olarak sonuç farklı olacaktır:
query One {
id
# First export "root", only then will be converted to "ROOT"
@export(as: "id")
@strUpperCase
again: id
# First convert to "ROOT" and then export this value
@strUpperCase
@export(as: "again")
}
query Two @depends(on: "One") {
mirrorID: _echo(value: $id)
mirrorAgain: _echo(value: $again)
}Şunu üretir:
{
"data": {
"id": "ROOT",
"again": "ROOT",
"mirrorID": "root",
"mirrorAgain": "ROOT"
}
}Persisted Queries'de yürütme
Bir GraphQL query'si, bir Persisted Query içinde birden fazla operasyon içerdiğinde, yürütülecek operasyonun adını ?operationName=... URL parametresiyle ileterek ilgili endpoint'i çağırabiliriz; aksi takdirde son operasyon yürütülür.
Örneğin, /graphql-query/posts-with-user-name/ endpoint'ine sahip bir Persisted Query'de GetPostsContainingString operasyonunu yürütmek için şunu çağırmalıyız:
https://mysite.com/graphql-query/posts-with-user-name/?operationName=GetPostsContainingStringÖrnekler
Harici bir API endpoint'inden içerik içe aktarma:
query FetchDataFromExternalEndpoint
{
_sendJSONObjectItemHTTPRequest(input: { url: "https://site.com/wp-json/wp/posts/1" } )
@export(as: "externalData")
@remove
}
query ManipulateDataIntoInput @depends(on: "FetchDataFromExternalEndpoint")
{
title: _objectProperty(
object: $externalData,
by: {
path: "title.rendered"
}
) @export(as: "postTitle")
excerpt: _objectProperty(
object: $externalData,
by: {
key: "excerpt"
}
) @export(as: "postExcerpt")
}
mutation CreatePost @depends(on: "ManipulateDataIntoInput")
{
createPost(input: {
title: $postTitle
excerpt: $postExcerpt
}) {
id
}
}Bir gönderi için verileri alın, dönüştürün ve yeniden saklayın:
query GetPostData(
$postId: ID!
) {
post(by: {id: $postId}) {
id
title @export(as: "postTitle")
rawContent @export(as: "postContent")
}
}
query AdaptPostData(
$replaceFrom: String!,
$replaceTo: String!
)
@depends(on: "GetPostData")
{
adaptedPostTitle: _strReplace(
search: $replaceFrom
replaceWith: $replaceTo
in: $postTitle
)
@export(as: "adaptedPostTitle")
adaptedPostContent: _strReplace(
search: $replaceFrom
replaceWith: $replaceTo
in: $postContent
)
@export(as: "adaptedPostContent")
}
mutation StoreAdaptedPostData(
$postId: ID!
)
@depends(on: "AdaptPostData")
{
updatePost(input: {
id: $postId,
title: $adaptedPostTitle,
contentAs: { html: $adaptedPostContent },
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
post {
id
title
rawContent
}
}
}Bir gönderi mevcutsa güncelleyin, aksi takdirde hata mesajı gösterin:
query GetPost($id: ID!) {
post(by:{id: $id}) {
id
title
}
_notNull(value: $__post) @export(as: "postExists")
}
query FailIfPostNotExists($id: ID!)
@skip(if: $postExists)
@depends(on: "GetPost")
{
errorMessage: _sprintf(
string: "There is no post with ID '%s'",
values: [$id]
) @remove
_fail(
message: $__errorMessage
data: {
id: $id
}
) @remove
}
mutation UpdatePost($id: ID!, $postTitle: String)
@include(if: $postExists)
@depends(on: "GetPost")
{
updatePost(input: {
id: $id,
title: $postTitle,
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
post {
id
title
rawContent
}
}
}
query MaybeUpdatePost
@depends(on: [
"FailIfPostNotExists",
"UpdatePost"
])
{
id @remove
}Bir mutation yürütmeden önce kullanıcıyı oturum açtırın ve hemen ardından oturumu kapatın:
mutation LogUserIn(
$username: String!
$password: String!
) {
loginUser(by: {
credentials: {
usernameOrEmail: $username,
password: $password
}
}) @remove {
status
user {
id
username
}
}
}
mutation AddComment(
$customPostId: ID!
$commentContent: HTML!
)
@depends(on: "LogUserIn")
{
addCommentToCustomPost(input: {
customPostID: $customPostId,
commentAs: { html: $commentContent }
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
comment {
id
parent {
id
}
content
date
author {
name
email
}
}
}
}
mutation LogUserOut
@depends(on: "AddComment")
{
logoutUser @remove {
status
userID
}
}
query ExecuteAllAddCommentOperations
@depends(on: "LogUserOut")
{
id @remove
}Kimlik bilgileri sağlanmışsa, bir mutation yürütmeden önce kullanıcıyı koşullu olarak oturum açtırın:
query ExportUserLogin(
$username: String
) {
_notNull(value: $username)
@export(as: "hasUsername")
@remove
}
mutation MaybeLogUserIn(
$username: String
$password: String
)
@depends(on: "ExportUserLogin")
@include(if: $hasUsername)
{
loginUser(by: {
credentials: {
usernameOrEmail: $username,
password: $password
}
}) @remove {
status
user {
id
username
}
}
}
mutation AddComment(
$customPostId: ID!
$commentContent: HTML!
)
@depends(on: "MaybeLogUserIn")
{
addCommentToCustomPost(input: {
customPostID: $customPostId,
commentAs: { html: $commentContent }
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
comment {
id
parent {
id
}
content
date
author {
name
email
}
}
}
}
mutation MaybeLogUserOut
@depends(on: "AddComment")
@include(if: $hasUsername)
{
logoutUser @remove {
status
userID
}
}
query ExecuteAllAddCommentOperations
@depends(on: "MaybeLogUserOut")
{
id @remove
}GraphQL spec
Bu işlevsellik şu anda GraphQL spec'in bir parçası değildir, ancak talep edilmiştir: