Lektion 30: Inhalte von einem Upstream auf mehrere Downstream-Sites verteilen
Stellen wir uns vor, ein Medienunternehmen betreibt ein Netzwerk von WordPress-Sites für verschiedene Regionen, wobei jeder Nachrichtenartikel nur dann auf einer Site veröffentlicht wird, wenn er für diese Region geeignet ist.
FĂĽr diese Situation ist es sinnvoll, eine Architektur zu implementieren, bei der:
- Alle Inhalte auf einer einzigen WordPress-Upstream-Site veröffentlicht (und bearbeitet) werden, die als einzige Quelle der Wahrheit für Inhalte dient
- Geeignete Inhalte auf jede der regionalen WordPress-Downstream-Sites verteilt (aber nicht dort bearbeitet) werden
Diese Tutorial-Lektion zeigt, wie du diese Architektur implementierst: Die WordPress-Upstream-Site benötigt die relevanten Gato GraphQL-Erweiterungen, während die Downstream-Sites nur das kostenlose Gato GraphQL-Plugin benötigen.
GraphQL-Query zum Synchronisieren von Inhalten vom Upstream zu den Downstream-Sites
(Nur fĂĽr die Downstream-Sites) Damit diese GraphQL-Query funktioniert, muss die Schema-Konfiguration, die auf den Endpoint angewendet wird, Nested Mutations aktiviert haben
Die folgende GraphQL-Query wird auf der WordPress-Upstream-Site ausgefĂĽhrt, um den Inhalt des aktualisierten Beitrags mit den entsprechenden Downstream-Sites zu synchronisieren, wobei der Post-Slug als gemeinsamer Bezeichner zwischen den Sites verwendet wird.
(Die Query kann angepasst werden, um auch die anderen Eigenschaften zu synchronisieren – Tags, Kategorien, Autor und Beitragsbild –, wie in der vorherigen Tutorial-Lektion erläutert.)
Die Query enthält Transaktionslogik, sodass wenn die Aktualisierung auf einer Downstream-Site fehlschlägt – sei es weil die HTTP-Anfrage fehlgeschlagen ist (z. B. wenn der Server nicht verfügbar ist) oder weil die GraphQL-Query Fehler erzeugt hat (z. B. wenn kein Beitrag mit dem angegebenen Slug vorhanden ist) –, die Mutation auf allen Downstream-Sites rückgängig gemacht wird.
Um den Zustand rückgängig zu machen, muss die Variable $previousPostContent übergeben werden. Wir können diesen Wert übergeben, indem wir uns in die WordPress-Action post_updated einklinken, bei der die GraphQL-Query ausgeführt wird (wie in einer vorherigen Tutorial-Lektion erklärt).
Die Query fĂĽhrt folgende Schritte aus:
- Sie empfängt den Slug des aktualisierten Beitrags sowie seinen neuen und vorherigen Inhalt
- Sie ruft die Meta-Eigenschaft
"downstream_domains"des Beitrags ab, die ein Array mit den Domains der Downstream-Sites enthält, auf die der Beitrag verteilt werden soll - Wenn die Meta-Eigenschaft nicht existiert (d. h. den Wert
nullhat), ruft sie die Option"downstream_domains"aus der Tabellewp_optionsab, die die Liste aller Downstream-Domains enthält - Sie meldet den Benutzer bei jeder der Downstream-Sites an (der Einfachheit halber mit demselben
$usernameund$userPassword) und führt die Mutation aus, um den Beitragsinhalt zu aktualisieren - Wenn eine Downstream-Site einen Fehler erzeugt, wird die Mutation auf allen Downstream-Sites rückgängig gemacht
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
}In der obigen GraphQL-Query wird ein Beitrag nicht auf eine Downstream-Site verteilt, wenn seine Meta-Eigenschaft "downstream_domains" mit einem leeren Array als Wert definiert ist.
Dies ist möglich dank des Unterschieds zwischen den Funktionsfeldern _notNull und _notEmpty (bereitgestellt von der Erweiterung PHP Functions via Schema):
- Wenn die Meta-Eigenschaft
"downstream_domains"nicht definiert ist, ist ihr Wertnull, und sowohl_notNullals auch_notEmptyergebenfalse - Wenn die Meta-Eigenschaft
"downstream_domains"als leeres Array definiert ist, ist ihr Wert[], und nur_notEmptyergibtfalse