บทช่วยสอน Schema
บทช่วยสอน Schemaบทเรียนที่ 19: การดึงข้อมูลจาก API ภายนอก

บทเรียนที่ 19: การดึงข้อมูลจาก API ภายนอก

ส่วนขยาย HTTP Client ช่วยให้เราสามารถส่ง HTTP request ไปยังเว็บเซิร์ฟเวอร์ได้

บทเรียนนี้สาธิตวิธีการดึงข้อมูลจาก API ภายนอก โดย:

  • ดึงสมาชิกของอีเมลลิสต์จาก REST API ของ Mailchimp แล้วแยกอีเมลออกมาและนำข้อมูลนี้ไปใช้งานต่อ
  • ดึง repository จาก GraphQL API ของ GitHub

การส่ง HTTP request

เอกสารของ API ของ Mailchimp อธิบายว่าเราต้องส่ง request แบบ GET ไปยัง REST API เพื่อดึงข้อมูลสมาชิกของอีเมลลิสต์:

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

มาลองทำสิ่งนี้ด้วย Gato GraphQL กัน

เราส่ง HTTP request ผ่านฟิลด์ global _sendHTTPRequest (ซึ่งจัดเตรียมโดยส่วนขยาย 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")
  }
}

ฟิลด์ _sendHTTPRequest คืนค่าออบเจกต์ของชนิด HTTPResponse หลังจากส่งคิวรีแล้ว สังเกตว่าฟิลด์ body (ชนิด String) มีเนื้อหาดิบของ response อยู่:

{
  "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"
    }
  }
}

เนื่องจาก content-type ของ response คือ application/json เราจึงสามารถแปลงเนื้อหาดิบของ body จาก String เป็น JSONObject ผ่านฟิลด์ _strDecodeJSONObject (จากส่วนขยาย 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 ในรูปแบบออบเจกต์ 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": [
          {
            // ...
          },
          // ...
        ]
      }
    }
  }
}

การเชื่อมต่อกับ REST API

HTTP Client ยังจัดเตรียมฟิลด์ฟังก์ชันที่จัดการ response ที่มี content-type application/json ไว้ให้แล้ว ทำให้เหมาะสำหรับการเชื่อมต่อกับ REST API:

  • _sendJSONObjectItemHTTPRequest: เมื่อเนื้อหาเป็นออบเจกต์ JSON เดียว
  • _sendJSONObjectCollectionHTTPRequest: เมื่อเนื้อหาเป็นคอลเลกชันของออบเจกต์ JSON

ฟิลด์เหล่านี้แปลง response เป็น JSONObject หรือ [JSONObject] ไว้ให้แล้ว

ฟิลด์เหล่านี้คาดหวังว่า status code ของ response จะสำเร็จ (กล่าวคืออยู่ในช่วง 200-299 เช่น 200, 201 หรือ 202) เพราะทำให้สามารถคืนค่า JSONObject ที่มี body ของ response ที่ถอดรหัสเป็น JSON แล้วได้

หากไม่เป็นเช่นนั้น GraphQL response จะมี error ที่เกี่ยวข้องอยู่

ตัวอย่างเช่น เมื่อดึงโพสต์ที่ไม่มีอยู่จริงจากเอนด์พอยต์ /wp-json/wp/v2/posts/{postId}/ ของ WP REST API นั้น response จะเป็น:

{
  "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
  }
}

หากเราไม่ต้องการให้ status code ที่ไม่ใช่ 200s (เช่น 302, 404 หรือ 500) ถูกถือว่าเป็น error เราต้องใช้ฟิลด์ _sendHTTPRequest

เมื่อปรับคิวรีก่อนหน้านี้:

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

...จะได้ response ดังนี้:

{
  "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": [
        {
          // ...
        },
        // ...
      ]
    }
  }
}

การเชื่อมต่อกับ WP REST API ไม่ว่าจะจากเซิร์ฟเวอร์ภายนอกหรือจากไซต์เดียวกันนี้ ใช้ขั้นตอนเดียวกัน

ตัวอย่างเช่น GraphQL คิวรีนี้เชื่อมต่อกับ WP REST API จากไซต์ภายในเครื่องด้วยโหมด ?context=edit (ซึ่งต้องระบุข้อมูลรับรอง 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
      }
    }
  })
}

เมื่อส่งตัวแปรเหล่านี้:

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

...response จะเป็นดังนี้:

{
  "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": {
        // ...
      }
    }
  }
}

การเชื่อมต่อกับ GraphQL API

HTTP Client ยังจัดเตรียมฟิลด์ฟังก์ชันสำหรับการเชื่อมต่อกับ GraphQL API ได้อย่างสะดวกด้วย

ฟิลด์ _sendGraphQLHTTPRequest รับ input ที่ GraphQL คาดหวัง (คิวรี ตัวแปร และชื่อ operation) ส่ง GraphQL คิวรีไปยังเอนด์พอยต์ที่ระบุ และแปลง response เป็น JSONObject

คิวรีนี้เชื่อมต่อกับ GraphQL API ของ GitHub และดึงรายการ repo ของเจ้าของที่ระบุไว้:

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
      }
    }
  })
}

เมื่อส่ง variables เหล่านี้:

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

...จะได้ response ดังนี้:

{
  "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)"
              }
            ]
          }
        }
      }
    }
  }
}

หากเราต้องส่ง HTTP request เดิมซ้ำๆ เราสามารถใช้ directive @cache (จัดเตรียมโดย Field Resolution Caching) เพื่อจัดเก็บผลลัพธ์ลงในดิสก์ตามระยะเวลาที่ร้องขอ ซึ่งช่วยเร่งความเร็วในการ resolve คิวรี

เมื่อส่งคิวรีนี้สองครั้งภายในช่วงเวลา 10 วินาที (ตามที่ระบุผ่านอาร์กิวเมนต์ @cache(time:)) ครั้งที่สองจะดึงผลลัพธ์ที่แคชไว้ ซึ่งทำให้เร็วขึ้นเพราะจะไม่เชื่อมต่อกับโฮสต์ภายนอก:

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:

  • ทำงานกับฟิลด์ใดๆ ที่คืนค่า response เป็น JSON รวมถึง _sendJSONObjectItemHTTPRequest และ _sendGraphQLHTTPRequest
  • เป็นอิสระ (กล่าวคือไม่สนใจตรรกะของฟิลด์ที่นำไปใช้) ดังนั้นจึงทำงานได้ไม่ว่าเมธอดของ HTTP request จะเป็น GET หรือ POST
  • ไม่ทำงานกับ _sendHTTPRequest เนื่องจากออบเจกต์ HTTPResponse ที่มันคืนค่าเป็นออบเจกต์แบบ "transient" (กล่าวคือไม่ได้จัดเก็บในฐานข้อมูล WordPress) ซึ่งคงอยู่เฉพาะระหว่าง request ปัจจุบันเท่านั้น

การดึงข้อมูลจากหลาย URL

เราสามารถส่ง HTTP request ไปยังหลาย URL เพื่อดึงข้อมูลจากทั้งหมดในเวลาเดียวกันได้

ฟิลด์ HTTP request แต่ละตัวที่กล่าวถึงข้างต้นมีฟิลด์ "พหูพจน์" ที่สอดคล้องกัน:

  • _sendHTTPRequests
  • _sendJSONObjectItemHTTPRequests
  • _sendJSONObjectCollectionHTTPRequests
  • _sendGraphQLHTTPRequests

ฟิลด์เหล่านี้ทั้งหมดมีอาร์กิวเมนต์ async เพื่อระบุว่าจะส่ง HTTP request หลายตัวแบบ asynchronous หรือ synchronous:

  • แบบ Asynchronous: HTTP request ทั้งหมดถูกส่งพร้อมกันแบบขนาน
  • แบบ Synchronous: HTTP request แต่ละตัวจะถูกส่งออกไปหลังจากตัวก่อนหน้าเสร็จสิ้นแล้วเท่านั้น

GraphQL คิวรีนี้ดึงข้อมูลพยากรณ์อากาศสำหรับหลายภูมิภาค:

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

...จะได้:

{
  "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."
            },
            // ...
          ]
        }
      }
    ]
  }
}

การแยกข้อมูลจาก response ของ API

กลับมาที่ API ของ Mailchimp มาลองแยกรายการที่อยู่อีเมลทั้งหมดออกจาก response กัน ข้อมูลเหล่านี้อยู่ภายใต้พร็อพเพอร์ตี้ email_address ของแต่ละไอเทมในลิสต์ members:

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

ส่วนขยาย Field Value Iteration and Manipulation จัดเตรียม composable directives ที่วนซ้ำผ่านองค์ประกอบภายในของอาร์เรย์หรือออบเจกต์ และนำ directive ที่ซ้อนอยู่ไปใช้กับองค์ประกอบเหล่านั้น:

  • @underArrayItem: ดำเนินการกับไอเทมที่ระบุจากอาร์เรย์
  • @underJSONObjectProperty: ดำเนินการกับเอนทรีที่ระบุจากออบเจกต์ JSON
  • @underEachArrayItem: ดำเนินการกับทุกไอเทมจากอาร์เรย์
  • @underEachJSONObjectProperty: ดำเนินการกับทุกเอนทรีจากออบเจกต์ JSON

GraphQL คิวรีนี้นำทางไปยังพร็อพเพอร์ตี้ email_address แต่ละตัว และส่งออกค่าของมันไปยังตัวแปร dynamic $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")
}

เราสามารถดูเอนทรีเหล่านี้ได้โดยการพิมพ์ค่าของตัวแปร dynamic:

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

...จะได้:

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

สังเกตว่าแม้ตัวแปร dynamic $mailchimpListMemberEmails จะเป็นลิสต์ แต่ @export ไม่มีอาร์กิวเมนต์ type: LIST

นี่เป็นเพราะเมื่อใดก็ตามที่ @export ถูกซ้อนอยู่ภายใต้ @underEachArrayItem (หรือ @underEachJSONObjectProperty) แล้ว ค่าที่ส่งออกจะเป็นอาร์เรย์อยู่แล้ว

การรวมข้อมูลจากสมาชิก Mailchimp กับผู้ใช้เว็บไซต์

สมมติว่าสมาชิก Mailchimp ของเราก็มีบัญชีผู้ใช้ในเว็บไซต์ของเราด้วย และที่อยู่อีเมลของพวกเขาเป็น ID ร่วมของทั้งสองแอปพลิเคชัน

เราจึงสามารถใช้ที่อยู่อีเมลที่ดึงมาจาก Mailchimp (ซึ่งตอนนี้อยู่ในตัวแปร dynamic $mailchimpListMemberEmails) เพื่อดึงข้อมูลผู้ใช้ที่เกี่ยวข้องซึ่งจัดเก็บอยู่ในไซต์ของเราได้:

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

response จะเป็นดังนี้:

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

เมื่อดึงผู้ใช้มาแล้ว เราสามารถดำเนินการใดๆ ที่ต้องการกับพวกเขาได้ (ส่ง mutation เพื่ออัปเดตข้อมูล ส่งอีเมล ฯลฯ)