แนวคิด ไอเดีย และกลยุทธ์
แนวคิด ไอเดีย และกลยุทธ์การดึงข้อมูลที่มีโครงสร้างแบบไดนามิก

การดึงข้อมูลที่มีโครงสร้างแบบไดนามิก

ใน WordPress เราสามารถดึงข้อมูลแบบซ้อนหลายระดับ กล่าวคือ เอนทิตีที่มีรายการย่อยในประเภทเดียวกัน ตัวอย่างเช่น เมนูหนึ่งมีรายการที่สามารถมีรายการย่อย และรายการย่อยเหล่านั้นก็สามารถมีรายการย่อยของตัวเองได้อีก ซึ่งสามารถดำเนินต่อไปได้หลายระดับ ในทำนองเดียวกัน ความคิดเห็นหนึ่งสามารถมีการตอบกลับ และการตอบกลับเหล่านั้นก็สามารถมีการตอบกลับได้เช่นกัน

มาดูวิธีการทำงานกับเมนูใน GraphQL กัน การดึงข้อมูลเมนูใน GraphQL นั้นเกี่ยวข้องกับการ queries รายการภายในเมนูสำหรับทุกระดับที่แตกต่างกัน ตัวอย่างเช่น ใน queries ด้านล่าง เมนูมี 3 ระดับ และเราใช้ fragment MenuItemProps เพื่อดึงฟิลด์เดียวกัน (id, label และ url) สำหรับรายการเมนูทุกรายการในทุกระดับ:

query GetMenu {
  menu(by: { id: 176 }) {
    id
    items {
      ...MenuItemProps
      children {
        ...MenuItemProps
        children {
          ...MenuItemProps
        }
      }
    }
  }
}
 
fragment MenuItemProps on MenuItem {
  id
  label
  url
}

ดังที่เห็นได้ จำนวนระดับจะสะท้อนอยู่ใน GraphQL queries เนื่องจากเมนูในแอปพลิเคชันมี 3 ระดับ GraphQL queries จึงมีการซ้อน 3 ระดับเช่นกัน

อย่างไรก็ตาม ใน WordPress การสร้างเมนูนั้นไม่ได้ถูกกำหนดไว้ล่วงหน้า แต่ผู้ดูแลระบบของเว็บไซต์เป็นผู้กำหนดค่าผ่านหน้าจอ Menu (นั่นคือ เมื่อไม่ได้ใช้ "block theme") และเก็บไว้ในฐานข้อมูล:

การสร้างเมนูใน WordPress

สิ่งนี้ก่อให้เกิดปัญหา: เมื่อเพิ่มระดับเพิ่มเติมให้กับเมนูผ่านอินเทอร์เฟซผู้ใช้ เราต้องเพิ่มระดับเพิ่มเติมใน GraphQL queries ด้วย มิฉะนั้นระดับใหม่จะไม่แสดงบนเว็บไซต์

มีวิธีจัดการกับปัญหานี้ 2 วิธี วิธีที่ง่ายกว่าคือการสร้าง GraphQL queries ที่ดึงข้อมูลมากกว่าจำนวนระดับที่ต้องการในตอนแรก เพื่อให้มีพื้นที่สำหรับการเพิ่มระดับในภายหลัง ตัวอย่างเช่น หากแอปพลิเคชันต้องการ 3 ระดับ GraphQL queries ก็สามารถดึงข้อมูลสำหรับ 6 (หรือ 10 หรือ 20) ระดับได้ ทำให้มีพื้นที่เพียงพอในการขยายเมนูจนกว่าจะถึงขีดจำกัด:

query GetMenu {
  menu(by: { id: 176 }) {
    id
    items {
      ...MenuItemProps
      children {
        ...MenuItemProps
        children {
          ...MenuItemProps
          children {
            ...MenuItemProps
            children {
              ...MenuItemProps
              children {
                ...MenuItemProps
              }
            }
          }
        }
      }
    }
  }
}
 
fragment MenuItemProps on MenuItem {
  id
  label
  url
}

วิธีที่สองคือการใช้ฟิลด์ Menu.itemDataEntries ซึ่งจะสร้าง JSONObject ที่มีโครงสร้างพร้อมข้อมูลเมนูทั้งหมด รวมถึงทุกระดับและระดับย่อย:

query GetMenu {
  menu(by: { id: 176 }) {
    id
    itemDataEntries
  }
}

การตอบสนองต่อ queries นี้มีลักษณะดังนี้:

{
  "data": {
    "menu": {
      "id": 176,
      "itemDataEntries": [
        {
          "id": 735,
          "objectID": "6",
          "parentID": null,
          "label": "About The Tests",
          "url": "https://mywpsite.com/about/",
          "children": [
            {
              "id": 1451,
              "objectID": "1133",
              "parentID": "735",
              "label": "Page Image Alignment",
              "url": "https://mywpsite.com/about/page-image-alignment/",
              "children": []
            },
            {
              "id": 1452,
              "objectID": "1134",
              "parentID": "735",
              "label": "Page Markup And Formatting",
              "url": "https://mywpsite.com/about/page-markup-and-formatting/",
              "children": []
            }
          ]
        },
        {
          "id": 739,
          "objectID": "174",
          "parentID": null,
          "label": "Level 1",
          "url": "https://mywpsite.com/level-1/",
          "children": [
            {
              "id": 740,
              "objectID": "173",
              "parentID": "739",
              "label": "Level 2",
              "url": "https://mywpsite.com/level-1/level-2/",
              "children": [
                {
                  "id": 741,
                  "objectID": "172",
                  "parentID": "740",
                  "label": "Level 3",
                  "url": "https://mywpsite.com/level-1/level-2/level-3/",
                  "children": []
                },
                {
                  "id": 1453,
                  "objectID": "747",
                  "parentID": "740",
                  "label": "Level 3a",
                  "url": "https://mywpsite.com/level-1/level-2/level-3a/",
                  "children": []
                },
                {
                  "id": 1454,
                  "objectID": "748",
                  "parentID": "740",
                  "label": "Level 3b",
                  "url": "https://mywpsite.com/level-1/level-2/level-3b/",
                  "children": []
                }
              ]
            }
          ]
        },
        {
          "id": 742,
          "objectID": "146",
          "parentID": null,
          "label": "Lorem Ipsum",
          "url": "https://mywpsite.com/lorem-ipsum/",
          "children": []
        }
      ]
    }
  }
}

วิธีนี้มีข้อดีตรงที่ข้อมูลที่ดึงมานั้นถูกควบคุมโดยอินเทอร์เฟซผู้ใช้อย่างสมบูรณ์ สะท้อนสิ่งที่เก็บไว้ในฐานข้อมูลตามที่เป็นอยู่ ดังนั้นแอปพลิเคชันจะไม่จำเป็นต้องอัปเดตเมื่อเพิ่มระดับเพิ่มเติมให้กับเมนู ไม่ว่าจะ 2 หรือ 20 ระดับก็ตาม

อย่างไรก็ตาม วิธีนี้มีข้อเสียที่ชัดเจนคือเราสูญเสียการกำหนดประเภทที่เข้มแข็งจาก GraphQL แทนที่จะได้รับรายการเมนูที่มีฟิลด์ที่กำหนดประเภทอย่างเข้มแข็ง เช่น url เป็น URL, label เป็น String, objectID เป็น ID และอื่นๆ เราจะได้รับอ็อบเจกต์ธรรมดาที่เครื่องมือและไคลเอนต์ GraphQL เช่น Apollo client หรือ Relay จะไม่เข้าใจ ด้วยเหตุนี้ เราจะไม่สามารถใช้ประโยชน์จาก GraphQL ได้อย่างเต็มที่

การดึงข้อมูลการตั้งค่า WordPress

อีกปัญหาหนึ่งคือเมื่อเราจำเป็นต้องดึงเอนทิตีที่ถูกควบคุมโดยอินเทอร์เฟซผู้ใช้และเก็บไว้ในฐานข้อมูล นั่นคือกรณีของการตั้งค่าใน WordPress ที่ชื่อของ option ถูกสร้างขึ้นแบบไดนามิกโดย themes และ plugins ดังนั้นเซิร์ฟเวอร์ GraphQL จึงไม่ทราบล่วงหน้า และค่า meta ที่ themes และ plugins อาจกำหนดขึ้นด้วยก็ไม่ได้ถูกแมปกับ GraphQL schema โดยค่าเริ่มต้น

ด้วยเหตุนี้ schema ที่สร้างโดย Gato GraphQL จึงไม่ hardcode ชื่อ option และประเภทของมัน แต่จะเข้าถึงผ่านฟิลด์ optionValue (และ optionValues และ optionObjectValue) ซึ่งรับชื่อของ option และส่งคืนค่าของประเภทในตัวที่เป็นไปได้ (ตามที่แสดงด้วย AnyBuiltInScalar):

type Root {
  optionValue(name: String!): AnyBuiltInScalar
}

เนื่องจากไม่ใช่ทุก option ที่มีจุดประสงค์เพื่อเปิดเผยผ่าน API ผู้ดูแลระบบของเว็บไซต์จึงต้องเพิ่มมันไปยัง allowlist อย่างชัดเจน ไม่ว่าจะเป็นชื่อเต็มหรือ regex ในการตั้งค่า plugin:

การเพิ่ม options เข้า allowlist ในหน้า Settings
การเพิ่ม options เข้า allowlist ในหน้า Settings

ตอนนี้ queries สามารถดึง option ที่อยู่ใน whitelist ได้:

{
  siteURL: optionValue(name: "siteurl")
  siteName: optionValue(name: "blogname")
  siteDescription: optionValue(name: "blogdescription")
}

หากมี option เพิ่มเติมที่แอปพลิเคชันต้องการ ก็สามารถทำให้พร้อมใช้งานกับ API ได้ทันทีเพียงแค่เพิ่มรายการที่เกี่ยวข้องใน allowlist ในหน้า Settings