Lezione 30: Distribuire contenuto da un upstream a più siti downstream
Immaginiamo che un'azienda media disponga di una rete di siti WordPress per diverse regioni, con ogni articolo pubblicato su un sito o meno solo se è adatto a quella regione.
Per questa situazione, ha senso implementare un'architettura in cui:
- Tutto il contenuto viene pubblicato (e modificato) su un unico sito WordPress upstream, che funge da unica fonte di verità per il contenuto
- Il contenuto appropriato viene distribuito (ma non modificato) a ciascuno dei siti WordPress downstream regionali
Questa lezione del tutorial dimostrerà come implementare questa architettura, con il sito WordPress upstream che deve avere le estensioni Gato GraphQL pertinenti attive, mentre i siti downstream hanno bisogno solo del plugin Gato GraphQL gratuito.
Query GraphQL per sincronizzare il contenuto dall'upstream ai siti downstream
(Solo per i siti downstream) Affinché questa query GraphQL funzioni, la Configurazione dello schema applicata all'endpoint deve avere le Mutazioni annidate abilitate
La query GraphQL seguente viene eseguita sul sito WordPress upstream, per sincronizzare il contenuto dell'articolo aggiornato verso i siti downstream pertinenti, utilizzando lo slug dell'articolo come identificatore comune tra i siti.
(La query può essere adattata per sincronizzare anche le altre proprietà — tag, categorie, autore e immagine in evidenza —, come spiegato nella lezione precedente del tutorial.)
La query include una logica transazionale, in modo tale che quando l'aggiornamento fallisce su un sito downstream, sia perché la richiesta HTTP ha fallito (come quando il server è inattivo) sia perché la query GraphQL ha prodotto errori (ad esempio se non esiste alcun articolo con lo slug fornito), la mutazione viene quindi annullata su tutti i siti downstream.
Per annullare lo stato, la variabile $previousPostContent deve essere fornita. Possiamo passare questo valore agganciandoci all'azione WordPress post_updated, al momento della quale la query GraphQL viene eseguita (come spiegato in una lezione precedente del tutorial).
La query esegue le seguenti operazioni:
- Riceve lo slug dell'articolo aggiornato, insieme al suo nuovo contenuto e al contenuto precedente
- Recupera la proprietà meta
"downstream_domains"dell'articolo, che contiene un array con i domini dei siti downstream verso cui l'articolo deve essere distribuito - Se la proprietà meta non esiste (ovvero ha valore
null), recupera l'opzione"downstream_domains"dalla tabellawp_options, che contiene l'elenco di tutti i domini downstream - Autentica l'utente su ciascuno dei siti downstream (usando lo stesso
$usernamee$userPassword, per semplicità) ed esegue la mutazione per aggiornare il contenuto dell'articolo - Se un sito downstream produce un errore, la mutazione viene annullata su tutti i siti 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
}Nella query GraphQL sopra, un articolo non verrà distribuito a nessun sito downstream quando la sua proprietà meta "downstream_domains" è definita con un array vuoto come valore.
Questo è possibile grazie alla differenza tra i campi funzione _notNull e _notEmpty (forniti dall'estensione PHP Functions via Schema):
- Se la proprietà meta
"downstream_domains"non è definita, il suo valore ènull, e sia_notNullche_notEmptyrestituisconofalse - Se la proprietà meta
"downstream_domains"è definita come array vuoto, il suo valore è[], e solo_notEmptyrestituiscefalse