Hướng dẫn schema
Hướng dẫn schemaBài 19: Lấy dữ liệu từ một API bên ngoài

Bài 19: Lấy dữ liệu từ một API bên ngoài

Extension HTTP Client cho phép chúng ta thực thi các yêu cầu HTTP đến một máy chủ web.

Bài học hướng dẫn này minh họa cách lấy dữ liệu từ một API bên ngoài, bằng cách:

  • Lấy danh sách thành viên của một danh sách email từ API REST của Mailchimp, trích xuất địa chỉ email của họ và thực hiện thao tác với dữ liệu này
  • Lấy các repository từ API GraphQL của GitHub

Thực thi một yêu cầu HTTP

Tài liệu của API Mailchimp giải thích rằng chúng ta phải gửi một yêu cầu GET đến REST API để lấy dữ liệu thành viên của một danh sách email:

curl --request GET \
  --url 'https://us7.api.mailchimp.com/3.0/lists/{LIST_ID}/members' \
  --user 'username:password'

Hãy tái tạo điều này trong Gato GraphQL.

Chúng ta thực thi một yêu cầu HTTP thông qua trường toàn cục _sendHTTPRequest (được cung cấp bởi extension HTTP Client):

query {
  _sendHTTPRequest(input: {
    url: "https://us7.api.mailchimp.com/3.0/lists/{LIST_ID}/members",
    method: GET,
    options: {
      auth: {
        username: "{USER}",
        password: "{API_TOKEN}"
      }
    }
  }) {
    body
    contentType
    statusCode
    headers
    serverHeader: header(name: "Server")
  }
}

Trường _sendHTTPRequest trả về một đối tượng kiểu HTTPResponse. Sau khi thực thi query, hãy chú ý rằng trường body (kiểu String) chứa nội dung thô của phản hồi:

{
  "data": {
    "_sendHTTPRequest": {
      "body": "{\"members\":[{\"id\":\"mSjGOg5qSb3dKTxPU9lhRZCxHGug8Mrt\",\"email_address\":\"vinesh@yahoo.com\",\"unique_email_id\":\"KObAXbEO3X\",\"contact_id\":\"JiCdz5EY67m3PKugW3bRE9VI1WjiBbjq\",\"full_name\":\"Vinesh Munak\",\"web_id\":443344389,\"email_type\":\"html\",\"status\":\"subscribed\",\"consents_to_one_to_one_messaging\":true,\"merge_fields\":{\"FNAME\":\"Vinesh\",\"LNAME\":\"Munak\",\"ADDRESS\":{\"addr1\":\"\",\"addr2\":\"\",\"city\":\"\",\"state\":\"\",\"zip\":\"\",\"country\":\"IN\"},\"PHONE\":\"\",\"BIRTHDAY\":\"\"},\"stats\":{\"avg_open_rate\":0.8,\"avg_click_rate\":0.6},\"ip_signup\":\"\",\"timestamp_signup\":\"\",\"ip_opt\":\"218.115.112.129\",\"timestamp_opt\":\"2020-12-31T06:55:17+00:00\",\"member_rating\":4,\"last_changed\":\"2020-12-31T06:55:17+00:00\",\"language\":\"\",\"vip\":false,\"email_client\":\"\",\"location\":{\"latitude\":2.18,\"longitude\":99.47,\"gmtoff\":8,\"dstoff\":8,\"country_code\":\"MY\",\"timezone\":\"asia/kuala_lumpur\",\"region\":\"10\"},\"source\":\"Admin Add\",\"tags_count\":0,\"tags\":[],\"list_id\":\"9nrwpfj0ou\",\"_links\":[{...}]},{...}],\"total_items\":4927,\"_links\":[{...}]}",
      "contentType": "application/json; charset=utf-8",
      "statusCode": 200,
      "headers": {
        "Server": "openresty",
        "Content-Type": "application/json; charset=utf-8",
        "Vary": "Accept-Encoding",
        "X-Request-Id": "177551d0-82e9-3d61-a664-177f61b91f80",
        "Link": "<https://us7.api.mailchimp.com/schema/3.0/Lists/Members/Collection.json>; rel=\"describedBy\"",
        "Date": "Thu, 13 Jul 2023 04:57:42 GMT",
        "Transfer-Encoding": "chunked",
        "Connection": "keep-alive,Transfer-Encoding"
      },
      "serverHeader": "openresty"
    }
  }
}

Vì content-type của phản hồi là application/json, chúng ta có thể chuyển đổi nội dung body thô từ String sang JSONObject thông qua trường _strDecodeJSONObject (từ extension PHP Functions Via Schema):

query {
  _sendHTTPRequest(input: {
    url: "https://us7.api.mailchimp.com/3.0/lists/{LIST_ID}/members",
    method: GET,
    options: {
      auth: {
        username: "{USER}",
        password: "{API_TOKEN}"
      }
    }
  }) {
    body @remove
    bodyJSONObject: _strDecodeJSONObject(string: $__body)
  }
}

Body giờ đây có thể truy cập dưới dạng một đối tượng JSON:

{
  "data": {
    "_sendHTTPRequest": {
      "bodyJSONObject": {
        "members": [
          {
            "id": "mSjGOg5qSb3dKTxPU9lhRZCxHGug8Mrt",
            "email_address": "vinesh@yahoo.com",
            "unique_email_id": "KObAXbEO3X",
            "contact_id": "JiCdz5EY67m3PKugW3bRE9VI1WjiBbjq",
            "full_name": "Vinesh Munak",
            "web_id": 443344389,
            "email_type": "html",
            "status": "subscribed",
            "consents_to_one_to_one_messaging": true,
            "merge_fields": {
              "FNAME": "Vinesh",
              "LNAME": "Munak",
              "ADDRESS": {
                "addr1": "",
                "addr2": "",
                "city": "",
                "state": "",
                "zip": "",
                "country": "IN"
              },
              "PHONE": "",
              "BIRTHDAY": ""
            },
            "stats": {
              "avg_open_rate": 0.8,
              "avg_click_rate": 0.6
            },
            "ip_signup": "",
            "timestamp_signup": "",
            "ip_opt": "218.115.112.129",
            "timestamp_opt": "2020-12-31T06:55:17+00:00",
            "member_rating": 4,
            "last_changed": "2020-12-31T06:55:17+00:00",
            "language": "",
            "vip": false,
            "email_client": "",
            "location": {
              "latitude": 2.18,
              "longitude": 99.47,
              "gmtoff": 8,
              "dstoff": 8,
              "country_code": "MY",
              "timezone": "asia/kuala_lumpur",
              "region": "10"
            },
            "source": "Admin Add",
            "tags_count": 0,
            "tags": [],
            "list_id": "9nrwpfj0ou",
            "_links": [
              {
                // ...
              },
              // ...
            ]
          },
          {
            // ...
          }
        ],
        "list_id": "9nrwpfj0ou",
        "total_items": 4927,
        "_links": [
          {
            // ...
          },
          // ...
        ]
      }
    }
  }
}

Kết nối đến một REST API

HTTP Client cũng cung cấp các trường hàm đã xử lý sẵn các phản hồi có content-type application/json, khiến chúng phù hợp để kết nối với REST API:

  • _sendJSONObjectItemHTTPRequest: Khi nội dung liên quan đến một đối tượng JSON đơn lẻ
  • _sendJSONObjectCollectionHTTPRequest: Khi nội dung liên quan đến một tập hợp các đối tượng JSON

Các trường này đã chuyển đổi phản hồi sang JSONObject hoặc [JSONObject].

Các trường này mong đợi mã trạng thái của phản hồi thành công (tức là trong phạm vi 200-299, chẳng hạn như 200, 201 hoặc 202), vì điều này cho phép chúng trả về ngay một JSONObject chứa body của phản hồi đã được giải mã dưới dạng JSON.

Khi không phải như vậy, phản hồi GraphQL sẽ chứa một lỗi tương ứng.

Ví dụ, khi lấy một bài đăng không tồn tại từ endpoint /wp-json/wp/v2/posts/{postId}/ của WP REST API, phản hồi sẽ là:

{
  "errors": [
    {
      "message": "Client error: `GET https://newapi.getpop.org/wp-json/wp/v2/posts/88888/` resulted in a `404 Not Found` response:\n{\"code\":\"rest_post_invalid_id\",\"message\":\"Invalid post ID.\",\"data\":{\"status\":404}}\n",
      "locations": [
        {
          "line": 3,
          "column": 17
        }
      ],
      "extensions": {
        "path": [
          "externalData: _sendJSONObjectItemHTTPRequest(input: {url: \"https://newapi.getpop.org/wp-json/wp/v2/posts/88888/\"}) @export(as: \"externalData\")",
          "query ConnectToAPI { ... }"
        ],
        "type": "QueryRoot",
        "field": "externalData: _sendJSONObjectItemHTTPRequest(input: {url: \"https://newapi.getpop.org/wp-json/wp/v2/posts/88888/\"}) @export(as: \"externalData\")",
        "id": "root",
        "code": "PoP/ComponentModel@e1"
      }
    }
  ],
  "data": {
    "externalData": null
  }
}

Nếu chúng ta không muốn coi bất kỳ mã trạng thái nào không phải 200s (chẳng hạn như 302, 404 hoặc 500) là lỗi, chúng ta phải sử dụng trường _sendHTTPRequest.

Điều chỉnh query trước đó:

query {
  _sendJSONObjectItemHTTPRequest(input: {
    url: "https://us7.api.mailchimp.com/3.0/lists/{LIST_ID}/members",
    method: GET,
    options: {
      auth: {
        username: "{USER}",
        password: "{API_TOKEN}"
      }
    }
  })
}

...tạo ra phản hồi này:

{
  "data": {
    "_sendJSONObjectItemHTTPRequest": {
      "members": [
        {
          "id": "mSjGOg5qSb3dKTxPU9lhRZCxHGug8Mrt",
          "email_address": "vinesh@yahoo.com",
          "unique_email_id": "KObAXbEO3X",
          "contact_id": "JiCdz5EY67m3PKugW3bRE9VI1WjiBbjq",
          "full_name": "Vinesh Munak",
          "web_id": 443344389,
          "email_type": "html",
          "status": "subscribed",
          "consents_to_one_to_one_messaging": true,
          "merge_fields": {
            "FNAME": "Vinesh",
            "LNAME": "Munak",
            "ADDRESS": {
              "addr1": "",
              "addr2": "",
              "city": "",
              "state": "",
              "zip": "",
              "country": "IN"
            },
            "PHONE": "",
            "BIRTHDAY": ""
          },
          "stats": {
            "avg_open_rate": 0.8,
            "avg_click_rate": 0.6
          },
          "ip_signup": "",
          "timestamp_signup": "",
          "ip_opt": "218.115.112.129",
          "timestamp_opt": "2020-12-31T06:55:17+00:00",
          "member_rating": 4,
          "last_changed": "2020-12-31T06:55:17+00:00",
          "language": "",
          "vip": false,
          "email_client": "",
          "location": {
            "latitude": 2.18,
            "longitude": 99.47,
            "gmtoff": 8,
            "dstoff": 8,
            "country_code": "MY",
            "timezone": "asia/kuala_lumpur",
            "region": "10"
          },
          "source": "Admin Add",
          "tags_count": 0,
          "tags": [],
          "list_id": "9nrwpfj0ou",
          "_links": [
            {
              // ...
            },
            // ...
          ]
        },
        {
          // ...
        }
      ],
      "list_id": "9nrwpfj0ou",
      "total_items": 4927,
      "_links": [
        {
          // ...
        },
        // ...
      ]
    }
  }
}

Kết nối đến WP REST API, dù từ một máy chủ bên ngoài hay từ chính trang web này, đều tuân theo cùng một quy trình.

Ví dụ, query GraphQL này kết nối đến WP REST API từ trang web cục bộ với chế độ ?context=edit (trong đó nó phải cung cấp thông tin xác thực application password):

query GetPostEditingDataFromRESTAPI(
  $postId: ID!,
  $username: String!,
  $applicationPassword: String!
) {
  siteURL: optionValue(name: "siteurl")
    @remove
 
  endpoint: _sprintf(
    string: "%s/wp-json/wp/v2/posts/%d/?context=edit",
    values: [
      $__siteURL,
      $postId,
    ]
  )
    @remove
 
  _sendJSONObjectItemHTTPRequest(input: {
    url: $__endpoint,
    method: GET,
    options: {
      auth: {
        username: $username,
        password: $applicationPassword
      }
    }
  })
}

Truyền các biến này:

{
  "postId": 1,
  "username": "{username}",
  "applicationPassword": "{application password}"
}

...phản hồi là:

{
  "data": {
    "_sendJSONObjectItemHTTPRequest": {
      "id": 1,
      "date": "2020-04-17T13:06:58",
      "date_gmt": "2020-04-17T13:06:58",
      "guid": {
        "rendered": "https://mysite.com/?p=1",
        "raw": "https://mysite.com/?p=1"
      },
      "modified": "2020-04-17T13:06:58",
      "modified_gmt": "2020-04-17T13:06:58",
      "password": "",
      "slug": "hello-world",
      "status": "publish",
      "type": "post",
      "link": "https://mysite.com/hello-world/",
      "title": {
        "raw": "Hello world!",
        "rendered": "Hello world!"
      },
      "content": {
        "raw": "<!-- wp:paragraph -->\n<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>\n<!-- /wp:paragraph -->",
        "rendered": "\n<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>\n",
        "protected": false,
        "block_version": 1
      },
      "excerpt": {
        "raw": "",
        "rendered": "<p>Welcome to WordPress. This is your first post. Edit or delete it, then start writing!</p>\n",
        "protected": false
      },
      "author": 2,
      "featured_media": 0,
      "comment_status": "open",
      "ping_status": "open",
      "sticky": false,
      "template": "",
      "format": "standard",
      "meta": [],
      "categories": [
        1
      ],
      "tags": [],
      "permalink_template": "https://mysite.com/%postname%/",
      "generated_slug": "hello-world",
      "_links": {
        // ...
      }
    }
  }
}

Kết nối đến một GraphQL API

HTTP Client cũng cung cấp một trường hàm để kết nối thuận tiện đến các GraphQL API.

Trường _sendGraphQLHTTPRequest chấp nhận các đầu vào mà GraphQL yêu cầu (query, biến và tên thao tác), thực thi GraphQL query đối với endpoint được cung cấp và chuyển đổi phản hồi sang JSONObject.

Query này kết nối đến API GraphQL của GitHub và lấy danh sách các repo của chủ sở hữu được chỉ định:

query FetchGitHubRepositories(
  $authorizationToken: String!
  $login: String!
  $numberRepos: Int! = 3
) {
  _sendGraphQLHTTPRequest(input:{
    endpoint: "https://api.github.com/graphql",
    query: """
    
query GetRepositoriesByOwner($login: String!, $numberRepos: Int!) {
  repositoryOwner(login: $login) {
    repositories(first: $numberRepos) {
      nodes {
        id
        name
        description
      }
    }
  }
}
 
    """,
    variables: [
      {
        name: "login",
        value: $login
      },
      {
        name: "numberRepos",
        value: $numberRepos
      }
    ],
    options: {
      auth: {
        password: $authorizationToken
      }
    }
  })
}

Truyền các variables này:

{
  "authorizationToken": "{ GITHUB ACCESS TOKEN }",
  "login": "leoloso"
}

...tạo ra phản hồi này:

{
  "data": {
    "_sendGraphQLHTTPRequest": {
      "data": {
        "repositoryOwner": {
          "repositories": {
            "nodes": [
              {
                "id": "MDEwOlJlcG9zaXRvcnk2NjcyMTIyNw==",
                "name": "PoP",
                "description": "Monorepo of the PoP project, including: a server-side component model in PHP, a GraphQL server, a GraphQL API plugin for WordPress, and a website builder"
              },
              {
                "id": "MDEwOlJlcG9zaXRvcnkxODQ1MzE5NzA=",
                "name": "PoP-API-WP",
                "description": "Bootstrap a PoP API for WordPress"
              },
              {
                "id": "MDEwOlJlcG9zaXRvcnkxOTYwOTk0MzQ=",
                "name": "leoloso.com",
                "description": "My personal site, based on Hylia (https://hylia.website)"
              }
            ]
          }
        }
      }
    }
  }
}

Nếu chúng ta phải thực thi cùng một yêu cầu HTTP nhiều lần, chúng ta có thể sử dụng directive @cache (được cung cấp bởi Field Resolution Caching) để lưu kết quả vào đĩa trong một khoảng thời gian yêu cầu, do đó tăng tốc độ phân giải query.

Khi thực thi query này hai lần trong khoảng 10 giây (như được chỉ định qua đối số @cache(time:)), lần thứ hai sẽ lấy kết quả đã được lưu trong cache; điều này sẽ giúp nó nhanh hơn, vì nó sẽ không kết nối đến máy chủ bên ngoài:

query ConnectToGitHub($authorizationToken: String!)
{
  _sendGraphQLHTTPRequest(input:{
    endpoint: "https://api.github.com/graphql",
    query: """    
{
  repositoryOwner(login: "leoloso") {
    url
  }
}
    """,
    options: {
      auth: {
        password: $authorizationToken
      }
    }
  })
    # Cache the response to disk, indicating for how many seconds
    @cache(time: 10)
}

Directive @cache:

  • Hoạt động với bất kỳ trường nào trả về phản hồi JSON, bao gồm _sendJSONObjectItemHTTPRequest_sendGraphQLHTTPRequest
  • Độc lập (tức là nó không quan tâm đến logic của các trường mà nó được áp dụng), do đó nó hoạt động dù phương thức yêu cầu HTTP là GET hay POST
  • Không hoạt động với _sendHTTPRequest, vì đối tượng HTTPResponse mà nó trả về là một đối tượng "tạm thời" (tức là nó không được lưu trong cơ sở dữ liệu WordPress), chỉ tồn tại trong suốt yêu cầu hiện tại

Lấy dữ liệu từ nhiều URL

Chúng ta có thể gửi các yêu cầu HTTP đến nhiều URL, lấy dữ liệu từ tất cả chúng cùng một lúc.

Mỗi trường yêu cầu HTTP được khám phá ở trên đều có một trường "nhiều" tương ứng:

  • _sendHTTPRequests
  • _sendJSONObjectItemHTTPRequests
  • _sendJSONObjectCollectionHTTPRequests
  • _sendGraphQLHTTPRequests

Tất cả các trường này đều có đối số async, để chỉ định xem có thực thi nhiều yêu cầu HTTP một cách bất đồng bộ hay đồng bộ:

  • Bất đồng bộ: Các yêu cầu HTTP được thực thi cùng nhau, song song
  • Đồng bộ: Mỗi yêu cầu HTTP chỉ được gửi đi sau khi yêu cầu trước đó hoàn thành

Query GraphQL này lấy dữ liệu dự báo thời tiết cho nhiều khu vực:

query {
  _sendJSONObjectItemHTTPRequests(inputs: [
    {
      url: "https://api.weather.gov/gridpoints/TOP/31,80/forecast"
    },
    {
      url: "https://api.weather.gov/gridpoints/TOP/41,55/forecast"
    }
  ])
}

...tạo ra:

{
  "data": {
    "_sendJSONObjectItemHTTPRequests": [
      {
        "@context": [
          "https://geojson.org/geojson-ld/geojson-context.jsonld",
          {
            "@version": "1.1",
            "wx": "https://api.weather.gov/ontology#",
            "geo": "http://www.opengis.net/ont/geosparql#",
            "unit": "http://codes.wmo.int/common/unit/",
            "@vocab": "https://api.weather.gov/ontology#"
          }
        ],
        "type": "Feature",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [
                -97.137207,
                39.7444372
              ],
              [
                -97.1367549,
                39.7223799
              ],
              [
                -97.1080809,
                39.7227252
              ],
              [
                -97.10852700000001,
                39.7447825
              ],
              [
                -97.137207,
                39.7444372
              ]
            ]
          ]
        },
        "properties": {
          "updated": "2023-07-13T05:39:07+00:00",
          "units": "us",
          "forecastGenerator": "BaselineForecastGenerator",
          "generatedAt": "2023-07-13T06:44:24+00:00",
          "updateTime": "2023-07-13T05:39:07+00:00",
          "validTimes": "2023-07-12T23:00:00+00:00/P7DT2H",
          "elevation": {
            "unitCode": "wmoUnit:m",
            "value": 456.8952
          },
          "periods": [
            {
              "number": 1,
              "name": "Overnight",
              "startTime": "2023-07-13T01:00:00-05:00",
              "endTime": "2023-07-13T06:00:00-05:00",
              "isDaytime": false,
              "temperature": 68,
              "temperatureUnit": "F",
              "temperatureTrend": null,
              "probabilityOfPrecipitation": {
                "unitCode": "wmoUnit:percent",
                "value": null
              },
              "dewpoint": {
                "unitCode": "wmoUnit:degC",
                "value": 21.666666666666668
              },
              "relativeHumidity": {
                "unitCode": "wmoUnit:percent",
                "value": 100
              },
              "windSpeed": "5 mph",
              "windDirection": "NE",
              "icon": "https://api.weather.gov/icons/land/night/few?size=medium",
              "shortForecast": "Mostly Clear",
              "detailedForecast": "Mostly clear, with a low around 68. Northeast wind around 5 mph."
            },
            {
              "number": 2,
              "name": "Thursday",
              "startTime": "2023-07-13T06:00:00-05:00",
              "endTime": "2023-07-13T18:00:00-05:00",
              "isDaytime": true,
              "temperature": 90,
              "temperatureUnit": "F",
              "temperatureTrend": null,
              "probabilityOfPrecipitation": {
                "unitCode": "wmoUnit:percent",
                "value": null
              },
              "dewpoint": {
                "unitCode": "wmoUnit:degC",
                "value": 21.11111111111111
              },
              "relativeHumidity": {
                "unitCode": "wmoUnit:percent",
                "value": 100
              },
              "windSpeed": "5 to 10 mph",
              "windDirection": "NE",
              "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
              "shortForecast": "Mostly Sunny",
              "detailedForecast": "Mostly sunny, with a high near 90. Northeast wind 5 to 10 mph."
            },
            // ...
          ]
        }
      },
      {
        "@context": [
          "https://geojson.org/geojson-ld/geojson-context.jsonld",
          {
            "@version": "1.1",
            "wx": "https://api.weather.gov/ontology#",
            "geo": "http://www.opengis.net/ont/geosparql#",
            "unit": "http://codes.wmo.int/common/unit/",
            "@vocab": "https://api.weather.gov/ontology#"
          }
        ],
        "type": "Feature",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            [
              [
                -96.8406778,
                39.1956467
              ],
              [
                -96.8402904,
                39.1735282
              ],
              [
                -96.811767,
                39.1738261
              ],
              [
                -96.8121485,
                39.1959446
              ],
              [
                -96.8406778,
                39.1956467
              ]
            ]
          ]
        },
        "properties": {
          "updated": "2023-07-13T05:39:07+00:00",
          "units": "us",
          "forecastGenerator": "BaselineForecastGenerator",
          "generatedAt": "2023-07-13T07:07:02+00:00",
          "updateTime": "2023-07-13T05:39:07+00:00",
          "validTimes": "2023-07-12T23:00:00+00:00/P7DT2H",
          "elevation": {
            "unitCode": "wmoUnit:m",
            "value": 403.86
          },
          "periods": [
            {
              "number": 1,
              "name": "Overnight",
              "startTime": "2023-07-13T02:00:00-05:00",
              "endTime": "2023-07-13T06:00:00-05:00",
              "isDaytime": false,
              "temperature": 69,
              "temperatureUnit": "F",
              "temperatureTrend": null,
              "probabilityOfPrecipitation": {
                "unitCode": "wmoUnit:percent",
                "value": null
              },
              "dewpoint": {
                "unitCode": "wmoUnit:degC",
                "value": 22.22222222222222
              },
              "relativeHumidity": {
                "unitCode": "wmoUnit:percent",
                "value": 97
              },
              "windSpeed": "5 to 10 mph",
              "windDirection": "NE",
              "icon": "https://api.weather.gov/icons/land/night/few?size=medium",
              "shortForecast": "Mostly Clear",
              "detailedForecast": "Mostly clear, with a low around 69. Northeast wind 5 to 10 mph."
            },
            {
              "number": 2,
              "name": "Thursday",
              "startTime": "2023-07-13T06:00:00-05:00",
              "endTime": "2023-07-13T18:00:00-05:00",
              "isDaytime": true,
              "temperature": 93,
              "temperatureUnit": "F",
              "temperatureTrend": null,
              "probabilityOfPrecipitation": {
                "unitCode": "wmoUnit:percent",
                "value": null
              },
              "dewpoint": {
                "unitCode": "wmoUnit:degC",
                "value": 22.22222222222222
              },
              "relativeHumidity": {
                "unitCode": "wmoUnit:percent",
                "value": 100
              },
              "windSpeed": "5 to 10 mph",
              "windDirection": "NE",
              "icon": "https://api.weather.gov/icons/land/day/sct?size=medium",
              "shortForecast": "Mostly Sunny",
              "detailedForecast": "Mostly sunny, with a high near 93. Northeast wind 5 to 10 mph."
            },
            // ...
          ]
        }
      }
    ]
  }
}

Trích xuất dữ liệu từ phản hồi API

Quay lại API của Mailchimp, hãy trích xuất danh sách tất cả các địa chỉ email từ phản hồi. Chúng được chứa trong thuộc tính email_address trên mỗi phần tử của danh sách members:

{
  "data": {
    "_sendJSONObjectItemHTTPRequest": {
      "members": [
        {
          "email_address": "vinesh@yahoo.com",
          // ...
        },
        {
          "email_address": "thiago@hotmail.com",
          // ...
        },
        // ...
      ]
    }
  }
}

Extension Field Value Iteration and Manipulation cung cấp các directive kết hợp duyệt qua các phần tử bên trong của mảng hoặc đối tượng, và áp dụng (các) directive lồng nhau của chúng dưới các phần tử đó:

  • @underArrayItem: Thao tác trên một phần tử cụ thể từ mảng
  • @underJSONObjectProperty: Thao tác trên một mục cụ thể từ đối tượng JSON
  • @underEachArrayItem: Thao tác trên tất cả các phần tử từ mảng
  • @underEachJSONObjectProperty: Thao tác trên tất cả các mục từ đối tượng JSON

Query GraphQL này điều hướng đến từng thuộc tính email_address và xuất giá trị của chúng sang biến động $mailchimpListMemberEmails:

query GetDataFromMailchimp {
  mailchimpListMembersJSONObject: _sendJSONObjectItemHTTPRequest(input: {
    url: "https://us7.api.mailchimp.com/3.0/lists/{LIST_ID}/members",
    method: GET,
    options: {
      auth: {
        username: "{USER}",
        password: "{API_TOKEN}"
      }
    }
  })
    @underJSONObjectProperty(by: { key: "members"})
      @underEachArrayItem
        @underJSONObjectProperty(by: { key: "email_address"})
          @export(as: "mailchimpListMemberEmails")
}

Chúng ta có thể trực quan hóa các mục bằng cách in giá trị của biến động:

query PrintMailchimpSubscriberEmails
  @depends(on: "GetDataFromMailchimp")
{
  mailchimpListMemberEmails: _echo(value: $mailchimpListMemberEmails)
}

...tạo ra:

{
  "data": {
    "mailchimpListMembersJSONObject": {
      // ...
    },
    "mailchimpListMemberEmails": [
      "vinesh@yahoo.com",
      "thiago@hotmail.com",
      // ...
    ]
  }
}

Lưu ý rằng, mặc dù biến động $mailchimpListMemberEmails là một danh sách, @export không có đối số type: LIST.

Điều này là do bất cứ khi nào @export được lồng bên dưới @underEachArrayItem (hoặc @underEachJSONObjectProperty), giá trị được xuất đã là một mảng.

Kết hợp dữ liệu từ người đăng ký Mailchimp và người dùng trang web

Giả sử rằng những người đăng ký Mailchimp của chúng ta cũng có tài khoản người dùng trên trang web của chúng ta, và địa chỉ email của họ là ID chung cho cả hai ứng dụng.

Chúng ta có thể sử dụng các địa chỉ email được lấy từ Mailchimp (hiện được đặt dưới biến động $mailchimpListMemberEmails) để lấy dữ liệu người dùng tương ứng được lưu trữ trên trang web của chúng ta:

query GetUsersUsingMailchimpSubscriberEmails
  @depends(on: "GetDataFromMailchimp")
{
  users(filter: { searchBy: { emails: $mailchimpListMemberEmails } } ) {
    id
    name
    email
  }
}

Phản hồi sẽ là:

{
  "data": {
    "mailchimpListMembersJSONObject": {
      // ...
    },
    "users": [
      {
        "id": 88,
        "name": "Vinesh Munak",
        "email": "vinesh@yahoo.com"
      },
      {
        "id": 705,
        "name": "Thiago Barbossa",
        "email": "thiago@hotmail.com"
      }
    ]
  }
}

Sau khi lấy được người dùng, chúng ta có thể áp dụng bất kỳ thao tác mong muốn nào trên họ (thực thi một mutation để cập nhật dữ liệu của họ, gửi email, v.v.).