Schemas

หัวข้อ ที่มักถูกหยิบยกขึ้นมาพูดคุยครั้งแล้วครั้งเล่าคือเราควรใช้ schema เพื่อกำหนดว่า endpoint ของเราเปิดเผยอะไรและรับอะไรหรือไม่ Schema มาในหลายรูปแบบ และการเลือก serialization format มักจะกำหนดว่าคุณสามารถใช้ schema technology ตัวไหนได้ ถ้าคุณทำงานกับ raw XML คุณจะใช้ XML Schema Definition (XSD) ถ้าคุณทำงานกับ raw JSON คุณจะใช้ JSON Schema ตัวเลือกเทคโนโลยีบางตัวที่เราแตะไป (โดยเฉพาะ ตัวเลือก RPC ส่วนใหญ่) ต้องการการใช้ schema ที่ชัดเจน ดังนั้นถ้าคุณเลือกเทคโนโลยีเหล่านั้น คุณก็จะต้องใช้ schema SOAP ทำงานผ่านการใช้ WSDL ในขณะที่ gRPC ต้องการการใช้ protocol buffer specification ตัวเลือกเทคโนโลยีอื่นๆ ที่เราสำรวจไปทำให้การใช้ schema เป็นทางเลือก และตรงนี้แหละที่เรื่องมัน น่าสนใจขึ้น

อย่างที่ผมคุยไปแล้ว ผมสนับสนุนให้มี schema ที่ชัดเจนสำหรับ endpoint ของไมโครเซอร์วิส ด้วยเหตุผลหลักสองข้อ อย่างแรก มันช่วยได้มากในการเป็น representation ที่ชัดเจนว่าไมโครเซอร์วิส endpoint เปิดเผยอะไรและรับอะไรได้บ้าง สิ่งนี้ทำให้ชีวิตง่ายขึ้นทั้งสำหรับ นักพัฒนาที่ทำงานกับไมโครเซอร์วิสและสำหรับผู้เรียกใช้ Schema อาจไม่ได้แทนที่ความจำเป็นในการมีเอกสารที่ดี แต่มันสามารถช่วยลดปริมาณเอกสาร ที่จำเป็นได้อย่างแน่นอน

อีกเหตุผลหนึ่งที่ผมชอบ schema ที่ชัดเจน อย่างไรก็ตาม คือวิธีที่มันช่วยในแง่ของการจับ breakage โดยไม่ตั้งใจ

Structural Versus Semantic Contract Breakages (Contract Breakage แบบ Structural กับแบบ Semantic)

โดย กว้างๆ แล้ว เราสามารถแบ่ง contract breakage ออกเป็นสองประเภท—breakage แบบ structural และ breakage แบบ semantic Structural breakage คือสถานการณ์ที่โครงสร้างของ endpoint เปลี่ยนแปลงในลักษณะที่ทำให้ผู้เรียกใช้ไม่เข้ากันได้แล้ว—นี่อาจหมายถึง ฟิลด์หรือ method ถูกลบออก หรือฟิลด์ที่จำเป็นใหม่ถูกเพิ่มเข้ามา Semantic breakage หมายถึงสถานการณ์ที่โครงสร้างของ endpoint ไมโครเซอร์วิสยังคงเดิม แต่พฤติกรรมเปลี่ยนแปลงในลักษณะที่ทำลายความคาดหวังของผู้เรียกใช้

มาดูตัวอย่างง่ายๆ กัน สมมติคุณมีไมโครเซอร์วิส Hard Calculations ที่ซับซ้อนมาก ซึ่งเปิดเผย method calculate บน endpoint ของมัน method calculate นี้รับ integer สองตัว ทั้งคู่เป็นฟิลด์ที่จำเป็น ถ้าคุณเปลี่ยน Hard Calculations ให้ method calculate รับ integer เพียงตัวเดียวแทน ผู้เรียกใช้ก็จะพัง—พวกเขาจะส่ง request ที่มี integer สองตัวซึ่งไมโครเซอร์วิส Hard Calculations จะปฏิเสธ นี่คือตัวอย่างของการเปลี่ยนแปลงแบบ structural และโดยทั่วไปแล้วการเปลี่ยนแปลงแบบนี้จะสังเกตเห็นได้ง่ายกว่า

การเปลี่ยนแปลงแบบ semantic มีปัญหามากกว่า นี่คือกรณีที่โครงสร้างของ endpoint ไม่เปลี่ยนแปลง แต่พฤติกรรมของ endpoint เปลี่ยนไป กลับมาที่ method calculate ของเรา ลองนึกภาพว่าใน version แรก integer สองตัวที่ให้มาถูกบวกกันและคืนผลลัพธ์กลับไป จนถึงตรงนี้ก็ยังดี ตอนนี้เราเปลี่ยน Hard Calculations ให้ method calculate คูณ integer เหล่านั้นเข้าด้วยกันแล้วคืนผลลัพธ์แทน semantic ของ method calculate เปลี่ยนไปในลักษณะที่อาจทำลายความคาดหวังของผู้เรียกใช้ได้

Should You Use Schemas? (คุณควรใช้ Schema ไหม?)

ด้วยการใช้ schema และเปรียบเทียบ schema version ที่แตกต่างกัน เราสามารถจับ structural breakage ได้ การจับ semantic breakage ต้องการการใช้ testing ถ้าคุณไม่มี schema หรือถ้าคุณมี schema แต่ตัดสินใจที่จะไม่เปรียบเทียบการเปลี่ยนแปลง schema เพื่อดู compatibility ภาระในการจับ structural breakage ก่อนที่คุณจะไปถึง production ก็จะตกอยู่กับ testing เช่นกัน อาจกล่าวได้ว่าสถานการณ์ นี้คล้ายคลึงกับ static เทียบกับ dynamic typing ในภาษาโปรแกรม กับภาษาแบบ statically typed type จะถูกกำหนดตายตัวตอน compile time— ถ้า code ของคุณทำอะไรกับ instance ของ type ที่ไม่ได้รับอนุญาต (เช่น เรียก method ที่ไม่มีอยู่) compiler ก็สามารถจับความผิดพลาดนั้นได้ สิ่งนี้ทำให้คุณสามารถโฟกัสความพยายามในการ testing ไปที่ปัญหาประเภทอื่นได้ แต่กับภาษาแบบ dynamically typed testing บางส่วนของคุณ จะต้องจับความผิดพลาดที่ compiler จะจับได้สำหรับภาษาแบบ statically typed แทน

ตอนนี้ ผมค่อนข้างสบายๆ กับเรื่อง static เทียบกับ dynamically typed language และผมพบว่าตัวเองมีประสิทธิภาพมาก (ค่อนข้างพูดได้) ในทั้งสองแบบ แน่นอนว่าภาษาแบบ dynamically typed ให้ประโยชน์สำคัญบางอย่างที่สำหรับหลายคนก็คุ้มค่าที่จะยอมสละ compile-time safety อย่างไรก็ตาม ในความเห็นส่วนตัว ถ้าเรานำการสนทนากลับมาที่ microservice interaction ผมยังไม่พบว่ามี trade-off ที่สมดุลคล้ายกันนี้ เมื่อพูดถึง schema เทียบกับการสื่อสารแบบ "schemaless" พูดง่ายๆ ผมคิดว่าการมี schema ที่ชัดเจนนั้นชดเชยประโยชน์ที่รับรู้ได้ใดๆ ของการสื่อสารแบบ schemaless มากกว่าเยอะ

ที่จริงแล้ว คำถามไม่ได้อยู่ที่ว่าคุณมี schema หรือไม่—แต่อยู่ที่ว่า schema นั้น ชัดเจน หรือไม่ ถ้าคุณกำลังเรียกใช้ข้อมูลจาก API แบบ schemaless คุณก็ยังมีความคาดหวังว่าข้อมูลอะไรควรอยู่ในนั้นและข้อมูลนั้นควรมีโครงสร้าง อย่างไร code ของคุณที่จะจัดการข้อมูลนั้นจะถูกเขียนขึ้นโดยคำนึงถึงชุดสมมติฐานว่าข้อมูลนั้นมีโครงสร้างอย่างไร ในกรณีนี้ ผมขอโต้แย้งว่า คุณมี schema จริงๆ แค่มันเป็นแบบ implicit ทั้งหมดแทนที่จะเป็น explicit 1 ความปรารถนาส่วนใหญ่ของผมสำหรับ schema ที่ชัดเจนถูกขับเคลื่อนโดยความจริงที่ว่าผมคิดว่ามันสำคัญที่จะชัดเจนที่สุดเท่าที่จะทำได้ว่า ไมโครเซอร์วิสเปิดเผย (หรือไม่เปิดเผย) อะไร

ข้อโต้แย้งหลักสำหรับ schemaless endpoint ดูเหมือนจะเป็นว่า schema ต้องการงานเพิ่มเติมและไม่ให้คุณค่าเพียงพอ นี่ในความเห็นอันต่ำต้อย ของผม ส่วนหนึ่งเป็นความล้มเหลวของจินตนาการ และส่วนหนึ่งเป็นความล้มเหลวของ tooling ที่ดีในการช่วยให้ schema มีคุณค่ามากขึ้นเมื่อพูดถึง การใช้มันเพื่อจับ structural breakage

ท้ายที่สุดแล้ว สิ่งที่ schema ให้เป็นส่วนใหญ่คือ representation ที่ชัดเจนของส่วนหนึ่งของ structure contract ระหว่าง client และ server มันช่วยทำให้สิ่งต่างๆ ชัดเจนขึ้นและสามารถช่วยการสื่อสารระหว่างทีมได้อย่างมาก รวมถึงทำหน้าที่เป็น safety net ในสถานการณ์ที่ต้นทุนของการเปลี่ยนแปลงลดลง—ตัวอย่างเช่น เมื่อทั้ง client และ server เป็นของทีมเดียวกัน—ผมจะสบายใจมากขึ้นกับการไม่มี schema