Pelajaran 30: Mendistribusikan konten dari upstream ke beberapa situs downstream
Misalkan sebuah perusahaan media memiliki jaringan situs WordPress untuk berbagai wilayah, di mana setiap artikel berita diterbitkan di suatu situs hanya jika sesuai untuk wilayah tersebut.
Untuk situasi ini, masuk akal untuk mengimplementasikan arsitektur di mana:
- Semua konten diterbitkan (dan diedit) di satu situs WordPress upstream tunggal, yang berfungsi sebagai sumber kebenaran tunggal untuk konten
- Konten yang sesuai didistribusikan (tetapi tidak diedit) ke masing-masing situs WordPress downstream regional
Pelajaran tutorial ini akan mendemonstrasikan cara mengimplementasikan arsitektur ini, dengan situs WordPress upstream perlu memiliki ekstensi Gato GraphQL yang relevan aktif, sementara situs downstream hanya perlu memiliki plugin Gato GraphQL gratis.
Query GraphQL untuk menyinkronkan konten dari upstream ke situs downstream
(Hanya untuk situs downstream) Agar query GraphQL ini berfungsi, Konfigurasi Skema yang diterapkan pada endpoint perlu mengaktifkan Nested Mutations
Query GraphQL di bawah ini dieksekusi pada situs WordPress upstream, untuk menyinkronkan konten postingan yang diperbarui ke situs downstream yang relevan, menggunakan slug postingan sebagai pengenal umum di seluruh situs.
(Query dapat disesuaikan untuk juga menyinkronkan properti lainnya —tag, kategori, penulis, dan gambar unggulan—, sebagaimana dijelaskan dalam pelajaran tutorial sebelumnya.)
Query ini mencakup logika transaksional, sehingga setiap kali pembaruan gagal di situs downstream mana pun, baik karena permintaan HTTP gagal (seperti saat server sedang down) atau karena query GraphQL menghasilkan error (misalnya tidak ada postingan dengan slug yang diberikan), mutasi kemudian dikembalikan di semua situs downstream.
Untuk membalikkan status, variabel $previousPostContent harus disediakan. Kita dapat meneruskan nilai ini dengan mengaitkan pada aksi WordPress post_updated, di mana query GraphQL dieksekusi (sebagaimana dijelaskan dalam pelajaran tutorial sebelumnya).
Query ini melakukan hal-hal berikut:
- Menerima slug postingan yang diperbarui, beserta konten baru dan sebelumnya
- Mengambil properti meta
"downstream_domains"dari postingan, yang berisi array dengan domain situs downstream tempat postingan harus didistribusikan - Jika properti meta tidak ada (yaitu memiliki nilai
null), maka mengambil opsi"downstream_domains"dari tabelwp_options, yang berisi daftar semua domain downstream - Masuk ke akun pengguna di setiap situs downstream (menggunakan
$usernamedan$userPasswordyang sama, untuk kemudahan) dan mengeksekusi mutasi untuk memperbarui konten postingan - Jika situs downstream mana pun menghasilkan error, mutasi dikembalikan di semua situs downstream
query InitializeDynamicVariables
@configureWarningsOnExportingDuplicateVariable(enabled: false)
{
initVariablesWithFalse: _echo(value: false)
@export(as: "requestProducedErrors")
@export(as: "anyErrorProduced")
@export(as: "hasDownstreamDomains")
@remove
}
query GetCustomDownstreamDomains($postSlug: String!)
@depends(on: "InitializeDynamicVariables")
{
post(by: { slug: $postSlug }, status: any)
@fail(
message: "There is no post in the upstream site with the provided slug"
data: {
slug: $postSlug
}
)
{
customDownstreamDomains: metaValues(key: "downstream_domains")
@export(as: "downstreamDomains")
hasDefinedCustomDownstreamDomains: _notNull(value: $__customDownstreamDomains)
@export(as: "hasDefinedCustomDownstreamDomains")
@remove
hasCustomDownstreamDomains: _notEmpty(value: $__customDownstreamDomains)
@export(as: "hasDownstreamDomains")
}
isMissingPostInUpstream: _isNull(value: $__post)
@export(as: "isMissingPostInUpstream")
}
query GetAllDownstreamDomains
@depends(on: "GetCustomDownstreamDomains")
@skip(if: $isMissingPostInUpstream)
@skip(if: $hasDefinedCustomDownstreamDomains)
{
allDownstreamDomains: optionValues(name: "downstream_domains")
@export(as: "downstreamDomains")
hasAllDownstreamDomains: _notEmpty(value: $__allDownstreamDomains)
@export(as: "hasDownstreamDomains")
}
############################################################
# (By default) Append "/graphql" to the domain, to point
# to that site's GraphQL single endpoint
############################################################
query ExportDownstreamGraphQLEndpointsAndQuery(
$endpointPath: String! = "/graphql"
)
@depends(on: "GetAllDownstreamDomains")
@skip(if: $isMissingPostInUpstream)
@include(if: $hasDownstreamDomains)
{
downstreamGraphQLEndpoints: _echo(value: $downstreamDomains)
@underEachArrayItem(
passValueOnwardsAs: "domain"
)
@strAppend(string: $endpointPath)
@export(as: "downstreamGraphQLEndpoints")
query: _echo(value: """
mutation LoginUserAndUpdatePost(
$username: String!
$userPassword: String!
$postSlug: String!
$postContent: String!
) {
loginUser(by: {
credentials: {
usernameOrEmail: $username,
password: $userPassword
}
}) {
userID
}
post(by: {slug: $postSlug})
@fail(
message: "There is no post in the downstream site with the provided slug"
data: {
slug: $postSlug
}
)
{
update(input: {
contentAs: { html: $postContent },
}) {
status
errors {
__typename
...on ErrorPayload {
message
}
}
post {
slug
rawContent
}
}
}
}
"""
)
@export(as: "query")
@remove
}
query ExportSendGraphQLHTTPRequestInputs(
$username: String!
$userPassword: String!
$postSlug: String!
$newPostContent: String!
)
@depends(on: "ExportDownstreamGraphQLEndpointsAndQuery")
@skip(if: $isMissingPostInUpstream)
@include(if: $hasDownstreamDomains)
{
sendGraphQLHTTPRequestInputs: _echo(value: $downstreamGraphQLEndpoints)
@underEachArrayItem(
passValueOnwardsAs: "endpoint"
)
@applyField(
name: "_echo",
arguments: {
value: {
endpoint: $endpoint,
query: $query,
variables: [
{
name: "username",
value: $username
},
{
name: "userPassword",
value: $userPassword
},
{
name: "postSlug",
value: $postSlug
},
{
name: "postContent",
value: $newPostContent
}
]
}
},
setResultInResponse: true
)
@export(as: "sendGraphQLHTTPRequestInputs")
@remove
}
query SendGraphQLHTTPRequests
@depends(on: "ExportSendGraphQLHTTPRequestInputs")
@skip(if: $isMissingPostInUpstream)
@include(if: $hasDownstreamDomains)
{
downstreamGraphQLResponses: _sendGraphQLHTTPRequests(
inputs: $sendGraphQLHTTPRequestInputs
)
@export(as: "downstreamGraphQLResponses")
requestProducedErrors: _isNull(value: $__downstreamGraphQLResponses)
@export(as: "requestProducedErrors")
@export(as: "anyErrorProduced")
@remove
}
query ExportGraphQLResponsesHaveErrors
@depends(on: "SendGraphQLHTTPRequests")
@skip(if: $isMissingPostInUpstream)
@skip(if: $requestProducedErrors)
@include(if: $hasDownstreamDomains)
{
graphQLResponsesHaveErrors: _echo(value: $downstreamGraphQLResponses)
# Check if any GraphQL response has the "errors" entry
@underEachArrayItem(
passValueOnwardsAs: "response"
affectDirectivesUnderPos: [1, 2]
)
@applyField(
name: "_propertyIsSetInJSONObject"
arguments: {
object: $response
by: {
key: "errors"
}
}
setResultInResponse: true
)
@export(as: "graphQLResponsesHaveErrors")
@remove
}
query ValidateGraphQLResponsesHaveErrors
@depends(on: "ExportGraphQLResponsesHaveErrors")
@skip(if: $isMissingPostInUpstream)
@skip(if: $requestProducedErrors)
@include(if: $hasDownstreamDomains)
{
anyGraphQLResponseHasErrors: _or(values: $graphQLResponsesHaveErrors)
@export(as: "anyErrorProduced")
@remove
}
query ExportRevertGraphQLHTTPRequestInputs(
$username: String!
$userPassword: String!
$postSlug: String!
$previousPostContent: String!
)
@depends(on: "ValidateGraphQLResponsesHaveErrors")
@include(if: $hasDownstreamDomains)
@include(if: $anyErrorProduced)
{
revertGraphQLHTTPRequestInputs: _echo(value: $downstreamGraphQLEndpoints)
@underEachArrayItem(
passValueOnwardsAs: "endpoint"
)
@applyField(
name: "_echo",
arguments: {
value: {
endpoint: $endpoint,
query: $query,
variables: [
{
name: "username",
value: $username
},
{
name: "userPassword",
value: $userPassword
},
{
name: "postSlug",
value: $postSlug
},
{
name: "postContent",
value: $previousPostContent
}
]
}
},
setResultInResponse: true
)
@export(as: "revertGraphQLHTTPRequestInputs")
@remove
}
query RevertGraphQLHTTPRequests
@depends(on: "ExportRevertGraphQLHTTPRequestInputs")
@skip(if: $isMissingPostInUpstream)
@include(if: $hasDownstreamDomains)
@include(if: $anyErrorProduced)
{
revertGraphQLResponses: _sendGraphQLHTTPRequests(
inputs: $sendGraphQLHTTPRequestInputs
)
}
query ExecuteAll
@depends(on: "RevertGraphQLHTTPRequests")
{
id @remove
}Dalam query GraphQL di atas, sebuah postingan tidak akan didistribusikan ke situs downstream mana pun apabila properti meta "downstream_domains"-nya didefinisikan dengan array kosong sebagai nilainya.
Hal ini dimungkinkan karena perbedaan antara field fungsi _notNull dan _notEmpty (disediakan oleh ekstensi PHP Functions via Schema):
- Jika properti meta
"downstream_domains"tidak didefinisikan, nilainya adalahnull, dan baik_notNullmaupun_notEmptydievaluasi menjadifalse - Jika properti meta
"downstream_domains"didefinisikan sebagai array kosong, nilainya adalah[], dan hanya_notEmptyyang dievaluasi menjadifalse