Lekcja 30: Dystrybucja treści z upstream do wielu stron downstream
Wyobraźmy sobie, że firma medialna posiada sieć stron WordPress dla różnych regionów, gdzie każdy artykuł jest publikowany na danej stronie tylko wtedy, gdy jest odpowiedni dla tego regionu.
W tej sytuacji sensowne jest wdrożenie architektury, w której:
- Cała treść jest publikowana (i edytowana) na jednej stronie WordPress upstream, która pełni rolę jedynego źródła prawdy dla treści
- Odpowiednia treść jest dystrybuowana (ale nie edytowana) do każdej z regionalnych stron WordPress downstream
Ta lekcja tutoriala pokaże, jak zaimplementować tę architekturę, w której strona WordPress upstream musi mieć aktywne odpowiednie rozszerzenia Gato GraphQL, podczas gdy strony downstream potrzebują jedynie darmowej wtyczki Gato GraphQL.
Query GraphQL do synchronizacji treści z upstream do stron downstream
(Tylko dla stron downstream) Aby ta query GraphQL działała, Konfiguracja Schema zastosowana do endpointu musi mieć włączone Zagnieżdżone Mutacje
Poniższa query GraphQL jest wykonywana na stronie WordPress upstream, aby zsynchronizować treść zaktualizowanego posta z odpowiednimi stronami downstream, używając sluga posta jako wspólnego identyfikatora między stronami.
(Query można dostosować, aby również synchronizować inne właściwości — tagi, kategorie, autora i wyróżniony obraz — jak wyjaśniono w poprzedniej lekcji tutoriala.)
Query zawiera logikę transakcyjną, tak że gdy aktualizacja nie powiedzie się na którejkolwiek stronie downstream, czy to dlatego, że żądanie HTTP nie powiodło się (np. gdy serwer jest niedostępny), czy dlatego, że query GraphQL zwróciła błędy (np. gdy nie ma posta o podanym slugu), mutacja jest wówczas cofana na wszystkich stronach downstream.
Aby cofnąć stan, należy podać zmienną $previousPostContent. Możemy przekazać tę wartość, podpinając się pod akcję WordPress post_updated, w momencie gdy wykonywana jest query GraphQL (jak wyjaśniono w poprzedniej lekcji tutoriala).
Query wykonuje następujące czynności:
- Odbiera slug zaktualizowanego posta oraz jego nową i poprzednią treść
- Pobiera właściwość meta
"downstream_domains"z posta, która zawiera tablicę z domenami stron downstream, do których post ma być dystrybuowany - Jeśli właściwość meta nie istnieje (tzn. ma wartość
null), pobiera opcję"downstream_domains"z tabeliwp_options, która zawiera listę wszystkich domen downstream - Loguje użytkownika na każdej ze stron downstream (używając tego samego
$usernamei$userPassword, dla uproszczenia) i wykonuje mutację aktualizującą treść posta - Jeśli którakolwiek strona downstream zwróci błąd, mutacja jest cofana na wszystkich stronach 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
}W powyższej query GraphQL post nie zostanie dystrybuowany do żadnej strony downstream, gdy jego właściwość meta "downstream_domains" jest zdefiniowana z pustą tablicą jako wartość.
Jest to możliwe dzięki różnicy między polami funkcji _notNull i _notEmpty (dostarczanymi przez rozszerzenie PHP Functions via Schema):
- Jeśli właściwość meta
"downstream_domains"nie jest zdefiniowana, jej wartość wynosinull, i zarówno_notNull, jak i_notEmptyzwracająfalse - Jeśli właściwość meta
"downstream_domains"jest zdefiniowana jako pusta tablica, jej wartość wynosi[], i tylko_notEmptyzwracafalse