- GraphQL พยายามแก้ปัญหาการร้องขอข้อมูลเกินความจำเป็น แต่ ในสภาพแวดล้อมองค์กรส่วนใหญ่ ปัญหานี้ถูกแก้ด้วยวิธีอื่นไปแล้ว
- ในระบบองค์กรที่โครงสร้าง BFF(Backend for Frontend) กลายเป็นเรื่องปกติ ข้อได้เปรียบหลักของ GraphQL ลดลงอย่างมาก
- ด้วย ความซับซ้อนในการติดตั้งใช้งาน, การสังเกตการณ์ที่แย่ลง, ปัญหาแคช, ข้อจำกัดเรื่อง ID, ความไม่สะดวกในการจัดการไฟล์ ทำให้ต้นทุนในสภาพแวดล้อมการใช้งานจริงสูงขึ้น
- REST เรียบง่ายและรวดเร็วกว่า อีกทั้งจัดการข้อผิดพลาดและออนบอร์ดได้ง่าย จึงมีประสิทธิภาพมากกว่าในสภาพแวดล้อมทีมขนาดใหญ่
- โดยสรุป GraphQL มีประโยชน์ในบางสถานการณ์ แต่สำหรับองค์กรส่วนใหญ่ถือเป็นทางเลือกที่มากเกินความจำเป็น
ปัญหาที่ GraphQL พยายามแก้
- เป้าหมายหลักของ GraphQL คือการป้องกัน overfetching (การร้องขอข้อมูลเกินความจำเป็น)
- ไคลเอนต์สามารถขอเฉพาะฟิลด์ที่ต้องการเพื่อลดการส่งข้อมูลที่ไม่จำเป็น
- มีข้อดีคือไม่ต้องแก้ฝั่งแบ็กเอนด์ทุกครั้งที่มีความต้องการใหม่จาก UI
- แต่ในสภาพแวดล้อมจริง โครงสร้างในอุดมคตินี้ ไม่สอดคล้องกับความซับซ้อนของโลกจริง
overfetching ที่ถูกแก้ไปแล้วด้วย BFF
- ฟรอนต์เอนด์ระดับองค์กรส่วนใหญ่ใช้ชั้น BFF(Backend for Frontend) อยู่แล้ว
- ทำหน้าที่ประกอบข้อมูลให้เหมาะกับ UI รวมการเรียกใช้งานดาวน์สตรีมหลายตัว และซ่อนความซับซ้อนของแบ็กเอนด์
- BFF ที่อิงกับ REST สามารถคืนเฉพาะข้อมูลที่ต้องใช้ได้อยู่แล้ว จึงทำให้ ข้อดีของ GraphQL ซ้ำซ้อน
- หากชั้น GraphQL ดึงข้อมูลมาจาก REST API ก็เท่ากับว่า overfetching เพียงแค่เลื่อนลงไปอีกหนึ่งชั้น
- GraphQL อาจมีประโยชน์เมื่อหลายหน้าต้องใช้เอนด์พอยต์เดียวกัน
- แต่ข้อดีนั้นก็อยู่ในระดับของการ ยอมแลกการตั้งค่าและภาระการดูแลรักษาที่มากขึ้น เพื่อประหยัดข้อมูลเพียงไม่กี่กิโลไบต์
ความซับซ้อนในการติดตั้งใช้งานและประสิทธิภาพการทำงานที่ลดลง
- GraphQL ใช้เวลาและมีความซับซ้อนในการติดตั้งมากกว่า REST อย่างมาก
- ต้องมีงานเพิ่ม เช่น การกำหนดสคีมา, type, รีโซลเวอร์ และแหล่งข้อมูล
- ยังมีภาระในการทำให้สคีมากับไคลเอนต์สอดคล้องกันอยู่เสมอ
- GraphQL ปรับให้เหมาะกับ การใช้งานฝั่งผู้บริโภค (ความสะดวกของไคลเอนต์) แต่ต้องแลกกับ การผลิต (ความเร็วในการพัฒนาเซิร์ฟเวอร์)
- ในสภาพแวดล้อมองค์กร ความเร็วในการพัฒนาและความเรียบง่าย สำคัญกว่า
ปัญหาเรื่องการสังเกตการณ์และมอนิเตอร์ริง
- ระบบ HTTP status code ของ GraphQL ไม่สม่ำเสมอ
- แม้จะตอบกลับเป็น 200 ก็อาจมีข้อผิดพลาดอยู่ภายใน ทำให้มอนิเตอร์แล้วแยกความสำเร็จ/ความล้มเหลวได้ยาก
- REST แยกชัดเจนด้วย 2XX/4XX/5XX ทำให้ การกรองบนแดชบอร์ดเข้าใจได้ตรงไปตรงมา
- แม้จะปรับแต่งได้ใน Apollo เป็นต้น แต่สิ่งนี้ก็นำไปสู่ การตั้งค่าเพิ่มเติมและภาระทางความคิด
- เมื่อต้องรับมือกับเหตุขัดข้องระหว่างการใช้งานจริง การระบุปัญหาใน GraphQL ยากและซับซ้อนกว่า REST
ข้อจำกัดของแคชในโลกจริง
- normalized caching ของ Apollo แม้ในทางทฤษฎีจะทรงพลัง แต่ในทางปฏิบัติกลับ เปราะบางและซับซ้อน
- คิวรีที่ต่างกันเพียงฟิลด์เดียวก็อาจถูกมองเป็นคนละรายการ จึงต้องเชื่อมโยงกันเองด้วยมือ
- การดีบักแคชกลายเป็นปัญหาอีกชุดหนึ่งแยกต่างหาก
- ในทางกลับกัน REST สามารถแคชทั้ง response ได้ตรง ๆ จึง เสถียรและดูแลรักษาง่ายกว่า
ปัญหาจากข้อจำกัดของฟิลด์ ID
- Apollo สมมติว่าอ็อบเจ็กต์ทุกตัวต้องมี id หรือ _id field
- แต่ API ระดับองค์กรจำนวนมากไม่มี ID ที่เป็นเอกลักษณ์ หรือไม่ใช่ตัวระบุแบบ global
- เพื่อให้สอดคล้องกับข้อกำหนดนี้ BFF จึงต้อง เพิ่มลอจิกสร้าง local ID
- ผลลัพธ์คือ มีฟิลด์และลอจิกที่ไม่จำเป็นเพิ่มขึ้น จนหักล้างผลดีจากการลด overfetching
ความไม่มีประสิทธิภาพของการอัปโหลดและดาวน์โหลดไฟล์
- GraphQL ไม่เหมาะกับการจัดการข้อมูลไบนารี
- ในทางปฏิบัติมักคืน URL สำหรับดาวน์โหลด แล้วส่งไฟล์ผ่าน REST
- หากรวมข้อมูลขนาดใหญ่ เช่น PDF ไว้ใน response ของ GraphQL ก็จะทำให้ ประสิทธิภาพลดลง
- ด้วยเหตุนี้ อุดมคติเรื่อง “API เดียว” ของ GraphQL จึงพังลง
การออนบอร์ดและเส้นโค้งการเรียนรู้
- นักพัฒนาส่วนใหญ่ คุ้นเคยกับ REST อยู่แล้ว แต่ GraphQL ต้องเรียนรู้เพิ่มเติม
- ต้องทำความเข้าใจแนวคิดใหม่ เช่น สคีมา, รีโซลเวอร์, การประกอบคิวรี, กฎการแคช, การจัดการข้อผิดพลาด
- ส่งผลให้ ความเร็วในการออนบอร์ดของทีมลดลง
- REST เป็นแนวทางที่ “เรียบง่ายจนน่าเบื่อ แต่ขยายต่อได้ดี” จึง เหมาะกับทีมขนาดใหญ่
ความซับซ้อนของการจัดการข้อผิดพลาด
- response ข้อผิดพลาดของ GraphQL มีความซับซ้อนจาก nullable field, partial data, อาร์เรย์
errors, extended status code เป็นต้น
- จำเป็นต้องไล่ดูว่ารีโซลเวอร์ตัวใดล้มเหลว
- ส่วน REST แยกเพียง 400/500 จึง เข้าใจและดีบักได้ง่ายกว่า
บทสรุป: GraphQL เป็นเทคโนโลยีเฉพาะทาง
- GraphQL เป็นเครื่องมือที่ใช้ได้ผลในบางสถานการณ์
- แต่ในสภาพแวดล้อมองค์กรส่วนใหญ่ ปัญหาถูกแก้ไปแล้วด้วย BFF และ REST
- ความท้าทายหลักไม่ใช่ overfetching แต่คือ การสังเกตการณ์ ความน่าเชื่อถือ และความเร็ว
- ผลลัพธ์คือ GraphQL แก้ปัญหาที่แคบ แต่สร้างความซับซ้อนที่กว้างกว่าเดิม
- ข้อสรุปคือ “GraphQL ไม่ได้แย่ แต่ ส่วนใหญ่แล้วไม่จำเป็น”
2 ความคิดเห็น
แม้จะมีทั้งข้อดีและข้อเสีย แต่การออกแบบข้อมูลและการควบคุมการเข้าถึงในระดับสคีมา แล้วทุกครั้งที่จะเพิ่มอะไรสักอย่างก็ต้องไปเพิ่มใน REST API แทนที่จะสุดท้ายต้องคืนค่าทุกอย่างออกมาทั้งหมด ก็ดูดีกว่าอยู่ครับ ข้อเสียมีชัดเจนก็จริง แต่ข้อดีก็ชัดเจนเหมือนกัน ฮ่าๆ
ความคิดเห็นบน Hacker News
ไม่เห็นด้วยกับบทความที่นิยามว่าปัญหาหลักของ GraphQL คือ overfetching
สำหรับผม ข้อดีจริง ๆ คือ (a) มันบังคับใช้ สัญญาที่อิงชนิดข้อมูลอย่างเข้มงวด และ (b) การวิวัฒน์ของสคีมา ทำได้ง่ายกว่ามาก
ด้วยระบบชนิดข้อมูล ทั้ง input และ output จะอยู่ในรูปแบบที่กำหนดไว้เสมอ และถ้าใช้ custom scalar type (เช่น เบอร์โทร อีเมล ฯลฯ) ก็ลดทั้งบั๊กและปัญหาด้านความปลอดภัยได้มาก
อีกทั้งการเพิ่มฟิลด์ใหม่หรือเลิกใช้ฟิลด์เดิมก็เป็นมาตรฐานอยู่แล้ว ทำให้ทั้งฝั่งเซิร์ฟเวอร์และไคลเอนต์มีภาระในการทำความเข้าใจน้อยลง
เหตุผลที่ผมใช้ GraphQL คือ การประกอบ API และการวิวัฒน์ โดยเฉพาะในระบบขนาดใหญ่ที่มีโครงสร้างแบบ M:N โฟลว์แบบ “ไคลเอนต์ระบุสิ่งที่ต้องการ → เซิร์ฟเวอร์ประกอบให้ → โดเมนเซอร์วิสจัดการต่อ” นั้นดูแลง่ายกว่าในระยะยาวมาก
ถ้าจับคู่กับ observability ที่ดี มันจะกลายเป็นฐานที่แข็งแกร่งมากสำหรับการเข้าถึงข้อมูล
อีกอย่างคือ การใช้ resolver ซ้ำและ federation ทำได้ง่าย ใน REST เรื่องนี้ค่อนข้างยุ่งยาก
เรื่องการวิวัฒน์ของสคีมา Protobuf ก็จัดการได้ดีเหมือนกัน
จุดเด่นที่แท้จริงของ GraphQL คือการ ประกอบข้อมูล UI จากชิ้นส่วนเล็ก ๆ
ถ้าใช้ fragment colocation แบบในวิดีโอนี้ ต่อให้แก้ไขคอมโพเนนต์ย่อยก็จะไม่กระทบส่วนอื่น
เพราะ query ถูกสร้างอัตโนมัติโดยอิงจาก fragment ความเสี่ยงที่คอมโพเนนต์อื่นจะพังเมื่อมีการลบฟิลด์ก็ลดลงด้วย
ถ้าระบบไม่ได้ใหญ่หรือความเร็วในการพัฒนาไม่ใช่เรื่องสำคัญมาก GraphQL ก็อาจดูเป็นการลงทุนเกินความจำเป็น
Colocation เป็นแนวคิดที่ปฏิวัติมาก แต่ Apollo แทบไม่พูดถึงเรื่องนี้เลย
เอกสารของ Relay ยังไม่ดีพออยู่ก็จริง แต่แนวคิด Entrypoint นั้นยอดเยี่ยมมาก
แต่ implementation ของ graphql-codegen เรื่องนี้ เข้ากันได้กับปลั๊กอิน ไม่ค่อยดี เราเลยต้องเขียนปลั๊กอินเอง
ระบบนิเวศโดยรวมยังขาดความสม่ำเสมอ
การบอกว่า overfetching คือปัญหาหลักของ GraphQL นั้นพูดเกินจริง
ผมคิดว่า GraphQL เป็นเครื่องมือที่จัดการ impedance mismatch ในระดับไคลเอนต์แบบเดียวกับที่ ORM จัดการ
การใช้โดยไม่มี tooling แบบอิงคอมไพเลอร์ อย่าง Relay ถือเป็น anti-pattern
ช่วงนี้ AI สร้าง data layer ให้อัตโนมัติได้มากขึ้น เลยรู้สึกว่าความจำเป็นของ GraphQL กำลังลดลง
อย่างเช่นถ้าเลือกฟิลด์ที่ไม่มีอยู่ มันจะสร้างให้โดยอัตโนมัติ ทำให้ ประสบการณ์นักพัฒนา (DevEx) ดีมาก
ความช้าของเว็บสมัยใหม่ส่วนใหญ่เกิดจาก การส่งข้อมูลเกินจำเป็น หลายแอปทำงานได้อย่างมีประสิทธิภาพไม่ถึง 0.5% ด้วยซ้ำ
ผมใช้ GraphQL มาตั้งแต่ปี 2016
โดยเนื้อแท้แล้ว GraphQL คือ สเปก RPC มันถูก implement เป็นแผนที่แบบ “Action(Args) → ResultType” บนเซิร์ฟเวอร์
แทนที่จะมีหลาย endpoint แบบ REST GraphQL จะทำงานผ่าน endpoint เดียวคือ
/queryโดยใช้แผนที่ของ resolverสุดท้ายแล้วมันก็เป็นโครงสร้างที่นิยาม input และ output ด้วย type เหมือน OpenAPI หรือ gRPC
Apollo ดีขึ้นเล็กน้อยหลังเพิ่ม fragment masking แต่แนวคิดแบบ Relay ก็ยังสำคัญอยู่ดี
แล้วฝั่งแบ็กเอนด์แปลงมันเป็น SQL query เดียว เพื่อประมวลผล
ควรใช้ resolver เป็นข้อยกเว้นเฉพาะกรณีที่ดึงจาก DB โดยตรงไม่ได้เท่านั้น
ตอนที่เคยเป็นหัวหน้าทีม ฝั่ง FE อยากได้ GraphQL ก็เลยเอามาใช้
สุดท้ายแต่ละหน้ากลายเป็น query ใหญ่ก้อนเดียวที่ดึงทุกอย่างมา และเวลาแก้ไขก็ส่ง JSON blob ทั้งก้อนกลับไปใหม่
แอปก็ใช้งานได้ แต่พอบริษัท pivot โค้ดส่วนนั้นก็หายไปทั้งหมด
ในความเป็นจริง โปรเจกต์ GraphQL จำนวนมากก็ดูจะจบลงแบบนี้ คือ มีแค่รูปแบบแต่ไม่ได้ใช้ประโยชน์จริง
โฟลว์ auth ของ GraphQL เป็นหนึ่งในโจทย์ที่ยากที่สุด
เพราะ resolver สามารถถูกเรียกใช้ได้จากหลายบริบท จึงต้องคำนึงถึงทุกกรณี
ความซับซ้อนและต้นทุนทางความคิดสูงมาก จนสุดท้ายก็ต้องล็อกฟิลด์ไว้
ผมเคยทำgraphql-autharoo เอง แต่ก็ยังไม่เพียงพอ
ฟังก์ชันส่วนใหญ่ทำได้ง่ายกว่าถ้าไม่ใช้ GraphQL
เพราะต้องเพิ่มสิทธิ์อย่างละเอียดให้กับทุก GraphQL resource ทำให้ query ใช้งานไม่ได้จนกว่าจะอัปเดตครบทั้ง resource
ภาระมันมากจนถึงขั้นมีคนพูดว่า “กลับไปทำเป็น REST ยังดีกว่า”
ผมคิดว่าแทนที่จะประกอบเอง ควรใช้ server framework ที่สำเร็จรูปและสมบูรณ์มากกว่า
GraphQL ดูดีในตอนแรก แต่ในความเป็นจริงมันคือ เทคโนโลยีที่ยิ่งเวลาผ่านไปยิ่งดูแลรักษายาก
เหมือนเทคโนโลยีหลายอย่าง มันให้บทเรียนว่า ล้อก็คือล้ออยู่วันยังค่ำ
ข้อดีของ GraphQL คือแค่เพิ่มฟิลด์ในสคีมา ไคลเอนต์ทุกตัวก็ query ได้ทันที
คำขอจาก FE ประเภท “ช่วยเพิ่มฟิลด์นี้ให้หน่อย” ก็หายไป ทำให้การทำงานร่วมกันง่ายขึ้น
อีกทั้งยังทำ schema snapshot เพื่อ ตรวจจับการเปลี่ยนแปลงในการทดสอบแบบรวมระบบ ได้ง่ายด้วย
ผมรู้สึกว่ามันสม่ำเสมอกว่า REST แต่ก็เป็นแค่ประสบการณ์ส่วนตัว
เพราะสามารถไล่เรียกคำขอเป็นทอด ๆ จาก A ไปถึง E ได้ ทำให้ทั้งประสิทธิภาพตกและ โครงสร้างข้อมูลผูกติดกับฝั่งฟรอนต์เอนด์ มากขึ้น
React เองตอนนี้ก็ใช้ SSR แบบ framework-based และ server components เป็นค่าพื้นฐาน
สุดท้ายแนวโน้มก็คือ TypeScript กำลังรวมไคลเอนต์กับเซิร์ฟเวอร์เข้าด้วยกัน
ผมสงสัยว่า GraphQL ป้องกันปัญหา ภาระที่ DB ต้องรับหรือ query ที่ไม่มีประสิทธิภาพ อย่างไร
query ที่เป็นอันตรายอาจทำให้สถานะภายในระเบิดได้ไม่ใช่หรือ?
ผมกำลังลอง GraphQL เพราะ REST มันน่าเบื่อเกินไป
ผมชอบตรงที่เซิร์ฟเวอร์กับไคลเอนต์สามารถ ตกลงเรื่อง request/response กันได้ตั้งแต่ compile time
อยากให้บล็อกต่าง ๆ ไม่ใช่แค่บอกว่า “อันนี้ไม่ดี” แต่ช่วยเสนอ เทคโนโลยีทางเลือก มาด้วย
ทั้งสองเทคโนโลยีต่างก็แก้ปัญหา สัญญาระหว่างไคลเอนต์กับเซิร์ฟเวอร์ ได้ดี