Phân phối nội dung từ một site upstream đến nhiều site WordPress downstream
Giả sử một công ty truyền thông có một mạng lưới các site WordPress cho nhiều khu vực khác nhau, trong đó mỗi bài báo chỉ được đăng trên một site nếu nó phù hợp với khu vực đó.
Trong tình huống này, việc triển khai một kiến trúc theo mô hình sau là hợp lý:
- Toàn bộ nội dung được đăng (và chỉnh sửa) trên một site WordPress upstream duy nhất, đóng vai trò là nguồn sự thật duy nhất cho nội dung
- Nội dung phù hợp được phân phối đến (nhưng không chỉnh sửa tại) từng site WordPress downstream theo khu vực
Query này đáp ứng kiến trúc đó, với site WordPress upstream cần có các tiện ích mở rộng Gato GraphQL liên quan được kích hoạt, trong khi các site downstream chỉ cần cài đặt plugin Gato GraphQL miễn phí.
Query được thực thi trên site WordPress upstream, để đồng bộ hóa nội dung của bài viết đã cập nhật đến các site downstream liên quan, sử dụng slug của bài viết làm định danh chung giữa các site.
Query bao gồm logic giao dịch, sao cho bất cứ khi nào việc cập nhật thất bại trên bất kỳ site downstream nào — dù là do yêu cầu HTTP thất bại (ví dụ khi máy chủ bị ngừng hoạt động) hay do query GraphQL tạo ra lỗi (ví dụ nếu không có bài viết nào với slug được cung cấp) — thì mutation sẽ được hoàn tác trên tất cả các site downstream.
Lưu ý rằng các tên miền downstream được cung cấp qua thuộc tính meta "downstream_domains" của bài viết, chứa một mảng với các tên miền của các site downstream mà bài viết phải được phân phối đến. Nếu thuộc tính meta này không tồn tại, hệ thống sẽ lấy tùy chọn "downstream_domains" từ bảng wp_options, chứa danh sách tất cả các tên miền downstream.
Sau đó query đăng nhập người dùng vào từng site downstream (sử dụng cùng $username và $userPassword) và thực thi mutation để cập nhật nội dung bài viết. Nếu bất kỳ site downstream nào tạo ra lỗi, mutation sẽ được hoàn tác trên tất cả các site downstream.
Query này yêu cầu endpoint phải bật Nested Mutations.
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 }, status: any)
@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 DistributeContentFromUpstreamToMultipleDownstreamWPSites
@depends(on: "RevertGraphQLHTTPRequests")
{
id @remove
}