Leçon 30 : Distribuer le contenu depuis un upstream vers plusieurs sites downstream
Imaginons qu'une entreprise de médias dispose d'un réseau de sites WordPress pour différentes régions, chaque article étant publié sur un site ou non, uniquement s'il convient à cette région.
Pour cette situation, il est logique de mettre en place une architecture où :
- Tout le contenu est publié (et modifié) sur un unique site WordPress upstream, qui constitue la source de vérité pour le contenu
- Le contenu approprié est distribué (mais pas modifié) vers chacun des sites WordPress downstream régionaux
Cette leçon du tutoriel montrera comment implémenter cette architecture, le site WordPress upstream ayant besoin des extensions Gato GraphQL pertinentes activées, tandis que les sites downstream n'ont besoin que du plugin Gato GraphQL gratuit.
Requête GraphQL pour synchroniser le contenu depuis l'upstream vers les sites downstream
(Pour les sites downstream uniquement) Pour que cette requête GraphQL fonctionne, la Configuration du schéma appliquée au point de terminaison doit avoir les Mutations imbriquées activées
La requête GraphQL ci-dessous est exécutée sur le site WordPress upstream, pour synchroniser le contenu de l'article mis à jour vers les sites downstream concernés, en utilisant le slug de l'article comme identifiant commun entre les sites.
(La requête peut être adaptée pour synchroniser également les autres propriétés — étiquettes, catégories, auteur et image mise en avant —, comme expliqué dans la leçon précédente du tutoriel.)
La requête inclut une logique transactionnelle, de sorte que lorsque la mise à jour échoue sur un site downstream, que ce soit parce que la requête HTTP a échoué (comme lorsque le serveur est hors ligne) ou parce que la requête GraphQL a produit des erreurs (par exemple s'il n'existe pas d'article avec le slug fourni), la mutation est alors annulée sur tous les sites downstream.
Pour annuler l'état, la variable $previousPostContent doit être fournie. Nous pouvons passer cette valeur en s'accrochant à l'action WordPress post_updated, à laquelle la requête GraphQL est exécutée (comme expliqué dans une leçon précédente du tutoriel).
La requête effectue les opérations suivantes :
- Elle reçoit le slug de l'article mis à jour, ainsi que son nouveau contenu et son contenu précédent
- Elle récupère la propriété meta
"downstream_domains"de l'article, qui contient un tableau avec les domaines des sites downstream vers lesquels l'article doit être distribué - Si la propriété meta n'existe pas (c'est-à-dire qu'elle a la valeur
null), elle récupère alors l'option"downstream_domains"depuis la tablewp_options, qui contient la liste de tous les domaines downstream - Elle connecte l'utilisateur à chacun des sites downstream (en utilisant le même
$usernameet$userPassword, par souci de simplicité) et exécute la mutation pour mettre à jour le contenu de l'article - Si un site downstream produit une erreur, la mutation est annulée sur tous les sites 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
}Dans la requête GraphQL ci-dessus, un article ne sera distribué à aucun site downstream lorsque sa propriété meta "downstream_domains" est définie avec un tableau vide comme valeur.
Cela est possible grâce à la différence entre les champs fonction _notNull et _notEmpty (fournis par l'extension PHP Functions via Schema) :
- Si la propriété meta
"downstream_domains"n'est pas définie, sa valeur estnull, et_notNullcomme_notEmptyévaluent àfalse - Si la propriété meta
"downstream_domains"est définie comme un tableau vide, sa valeur est[], et seul_notEmptyévalue àfalse