คุณอาจเคยได้ยินเกี่ยวกับเด็กใหม่ในบล็อก: GraphQL ถ้าไม่ใช่ GraphQL เป็นวิธีการใหม่ในการดึงข้อมูล API ซึ่งเป็นอีกทางเลือกหนึ่งของ REST เริ่มต้นเป็นโครงการภายในที่ Facebook และเนื่องจากเป็นโอเพ่นซอร์สจึงได้รับ แรงดึงมาก .
จุดมุ่งหมายของบทความนี้คือการช่วยให้คุณเปลี่ยนจาก REST เป็น GraphQL ได้อย่างง่ายดายไม่ว่าคุณจะตัดสินใจใช้ GraphQL แล้วหรือแค่เต็มใจที่จะลองใช้ ไม่จำเป็นต้องมีความรู้มาก่อนเกี่ยวกับ GraphQL แต่จำเป็นต้องมีความคุ้นเคยกับ REST API เพื่อทำความเข้าใจบทความ
ส่วนแรกของบทความจะเริ่มต้นด้วยการให้เหตุผลสามประการว่าทำไมโดยส่วนตัวแล้วฉันคิดว่า GraphQL เหนือกว่า REST ส่วนที่สองคือบทช่วยสอนเกี่ยวกับวิธีการเพิ่มปลายทาง GraphQL ในส่วนหลังของคุณ
หากคุณยังลังเลว่า GraphQL จะเหมาะกับความต้องการของคุณหรือไม่คุณจะได้รับภาพรวมของ“ REST vs. GraphQL” ที่ค่อนข้างครอบคลุมและชัดเจน ที่นี่ . อย่างไรก็ตามสำหรับเหตุผลสามประการแรกในการใช้ GraphQL โปรดอ่านต่อ
สมมติว่าคุณมีแหล่งข้อมูลผู้ใช้ที่ส่วนหลังพร้อมชื่อนามสกุลอีเมลและช่องอื่น ๆ อีก 10 ช่อง สำหรับลูกค้าโดยทั่วไปคุณต้องการเพียงไม่กี่อย่างเท่านั้น
การเรียก REST บน /users
จุดสิ้นสุดให้คุณย้อนกลับฟิลด์ทั้งหมดของผู้ใช้และไคลเอนต์ใช้เฉพาะฟิลด์ที่จำเป็นเท่านั้น เห็นได้ชัดว่ามีความสิ้นเปลืองในการถ่ายโอนข้อมูลซึ่งอาจต้องพิจารณากับไคลเอนต์มือถือ
โดยค่าเริ่มต้น GraphQL จะดึงข้อมูลที่เล็กที่สุดเท่าที่จะทำได้ หากคุณต้องการเพียงชื่อและนามสกุลของผู้ใช้ของคุณให้ระบุในแบบสอบถามของคุณ
อินเทอร์เฟซด้านล่างนี้เรียกว่า GraphiQL ซึ่งเป็นเหมือน API explorer สำหรับ GraphQL ฉันสร้างโครงการเล็ก ๆ ขึ้นเพื่อจุดประสงค์ของบทความนี้ รหัสถูกโฮสต์ บน GitHub และเราจะเจาะลึกลงไปในส่วนที่สอง
วิธีใช้โหนด js
ในบานหน้าต่างด้านซ้ายของอินเทอร์เฟซคือแบบสอบถาม ที่นี่เรากำลังดึงข้อมูลผู้ใช้ทั้งหมด - เราจะทำ GET /users
กับ REST - และรับเฉพาะชื่อและนามสกุลเท่านั้น
แบบสอบถาม
query { users { firstname lastname } }
ผลลัพธ์
{ 'data': { 'users': [ { 'firstname': 'John', 'lastname': 'Doe' }, { 'firstname': 'Alicia', 'lastname': 'Smith' } ] } }
หากเราต้องการรับอีเมลด้วยการเพิ่มบรรทัด 'อีเมล' ด้านล่าง 'นามสกุล' จะช่วยแก้ปัญหาได้
ส่วนหลังของ REST บางตัวมีตัวเลือกเช่น /users?fields=firstname,lastname
เพื่อส่งคืนทรัพยากรบางส่วน คุ้มค่าคุ้มราคา Google ขอแนะนำ . อย่างไรก็ตามจะไม่ได้ใช้งานตามค่าเริ่มต้นและทำให้คำขอแทบไม่สามารถอ่านได้โดยเฉพาะอย่างยิ่งเมื่อคุณโยนพารามิเตอร์การสืบค้นอื่น ๆ :
&status=active
เพื่อกรองผู้ใช้ที่ใช้งานอยู่&sort=createdAat
เพื่อจัดเรียงผู้ใช้ตามวันที่สร้าง&sortDirection=desc
เพราะคุณต้องการมันอย่างชัดเจน&include=projects
เพื่อรวมโครงการของผู้ใช้พารามิเตอร์คิวรีเหล่านี้เป็นแพตช์ที่เพิ่มใน REST API เพื่อเลียนแบบภาษาคิวรี GraphQL อยู่เหนือภาษาแบบสอบถามซึ่งทำให้คำขอมีความกระชับและแม่นยำตั้งแต่เริ่มต้น
สมมติว่าเราต้องการสร้างเครื่องมือจัดการโครงการง่ายๆ เรามีทรัพยากรสามอย่าง ได้แก่ ผู้ใช้โครงการและงาน เรายังกำหนดความสัมพันธ์ต่อไปนี้ระหว่างทรัพยากร:
ความแตกต่างระหว่างโหนด js และ javascript
นี่คือจุดสิ้นสุดบางส่วนที่เราเปิดเผยต่อโลก:
จุดสิ้นสุด | คำอธิบาย |
---|---|
GET /users | แสดงรายชื่อผู้ใช้ทั้งหมด |
GET /users/:id | รับผู้ใช้คนเดียวด้วย id: id |
GET /users/:id/projects | รับโปรเจ็กต์ทั้งหมดของผู้ใช้คนเดียว |
จุดสิ้นสุดนั้นเรียบง่ายอ่านง่ายและมีการจัดระเบียบที่ดี
สิ่งต่างๆจะยุ่งยากขึ้นเมื่อคำขอของเราซับซ้อนมากขึ้น มาดู GET /users/:id/projects
จุดสิ้นสุด: สมมติว่าฉันต้องการแสดงเฉพาะชื่อโครงการในโฮมเพจ แต่โปรเจ็กต์ + งานบนแดชบอร์ดโดยไม่ต้องโทร REST หลายครั้ง ฉันจะโทร:
GET /users/:id/projects
สำหรับหน้าแรกGET /users/:id/projects?include=tasks
(ตัวอย่าง) บนหน้าแดชบอร์ดเพื่อให้ส่วนหลังต่อท้ายงานที่เกี่ยวข้องทั้งหมดการเพิ่มพารามิเตอร์การค้นหาเป็นเรื่องปกติ ?include=...
เพื่อให้งานนี้ได้รับการแนะนำโดยไฟล์ JSON API ข้อกำหนด พารามิเตอร์การค้นหาเช่น ?include=tasks
ยังอ่านได้ แต่อีกไม่นานเราจะจบลงด้วย ?include=tasks,tasks.owner,tasks.comments,tasks.comments.author
ในกรณีนี้จะเป็นการดีกว่าไหมที่จะสร้าง /projects
จุดสิ้นสุดเพื่อทำสิ่งนี้? บางอย่างเช่น /projects?userId=:id&include=tasks
เนื่องจากเรามีระดับความสัมพันธ์น้อยกว่าที่จะรวม? หรือจริงๆแล้ว a /tasks?userId=:id
จุดสิ้นสุดอาจใช้งานได้เช่นกัน นี่อาจเป็นทางเลือกในการออกแบบที่ยาก ซับซ้อนมากยิ่งขึ้น ถ้าเรามีความสัมพันธ์แบบกลุ่มต่อกลุ่ม
GraphQL ใช้ include
เข้าใกล้ทุกที่ สิ่งนี้ทำให้ไวยากรณ์ในการดึงความสัมพันธ์มีประสิทธิภาพและสอดคล้องกัน
นี่คือตัวอย่างของการดึงโครงการและงานทั้งหมดจากผู้ใช้ด้วยรหัส 1
แบบสอบถาม
{ user(id: 1) { projects { name tasks { description } } } }
ผลลัพธ์
{ 'data': { 'user': { 'projects': [ { 'name': 'Migrate from REST to GraphQL', 'tasks': [ { 'description': 'Read tutorial' }, { 'description': 'Start coding' } ] }, { 'name': 'Create a blog', 'tasks': [ { 'description': 'Write draft of article' }, { 'description': 'Set up blog platform' } ] } ] } } }
ดังที่คุณเห็นไวยากรณ์ของแบบสอบถามสามารถอ่านได้ง่าย หากเราต้องการเจาะลึกและรวมถึงงานความคิดเห็นรูปภาพและผู้เขียนเราจะไม่คิดซ้ำอีกว่าจะจัดระเบียบ API ของเราอย่างไร GraphQL ทำให้ง่ายต่อการดึงวัตถุที่ซับซ้อน
เมื่อสร้างแบ็คเอนด์เรามักจะเริ่มต้นด้วยการพยายามทำให้ API สามารถใช้งานได้อย่างกว้างขวางสำหรับลูกค้าทั้งหมดเท่าที่จะทำได้ แต่ลูกค้ามักต้องการโทรน้อยลงและเรียกข้อมูลมากขึ้น ด้วยการรวมทรัพยากรบางส่วนและการกรองอย่างละเอียดคำขอที่สร้างโดยไคลเอนต์เว็บและมือถืออาจแตกต่างกันมาก
ด้วย REST มีสองวิธีแก้ปัญหา เราสามารถสร้างปลายทางที่กำหนดเอง (เช่นปลายทางนามแฝงเช่น /mobile_user
) การแสดงที่กำหนดเอง (Content-Type: application/vnd.rest-app-example.com+v1+mobile+json
) หรือแม้แต่ API เฉพาะไคลเอ็นต์ (เช่น Netflix เคยทำ ). ทั้งสามคนต้องใช้ความพยายามเป็นพิเศษจากทีมพัฒนาส่วนหลัง
GraphQL ให้อำนาจกับลูกค้ามากขึ้น หากลูกค้าต้องการคำขอที่ซับซ้อนไคลเอ็นต์จะสร้างแบบสอบถามที่เกี่ยวข้องเอง ลูกค้าแต่ละรายสามารถใช้ API เดียวกันได้แตกต่างกัน
ในการถกเถียงกันมากที่สุดเกี่ยวกับ“ GraphQL vs. REST” ในปัจจุบันผู้คนมักคิดว่าพวกเขาต้องเลือกหนึ่งในสองอย่างนั้น นี้เป็นเพียงไม่เป็นความจริง.
การแก้ปัญหาอย่างกะทันหันเรียกว่า _____
โดยทั่วไปแอปพลิเคชันสมัยใหม่จะใช้บริการต่างๆซึ่งจะแสดง API หลายตัว เราอาจคิดว่า GraphQL เป็นเกตเวย์หรือ Wrapper ของบริการเหล่านี้ทั้งหมด ไคลเอนต์ทั้งหมดจะเข้าสู่จุดสิ้นสุด GraphQL และจุดสิ้นสุดนี้จะเข้าสู่ชั้นฐานข้อมูลบริการภายนอกเช่น ElasticSearch หรือ Sendgrid หรือจุดสิ้นสุด REST อื่น ๆ
วิธีที่สองในการใช้ทั้งสองอย่างคือการแยก /graphql
จุดสิ้นสุดบน REST API ของคุณ สิ่งนี้มีประโยชน์อย่างยิ่งหากคุณมีลูกค้าจำนวนมากที่กด REST API ของคุณอยู่แล้ว แต่คุณต้องการลองใช้ GraphQL โดยไม่กระทบกับโครงสร้างพื้นฐานที่มีอยู่ และนี่คือทางออกที่เรากำลังสำรวจในวันนี้
ดังที่ได้กล่าวไว้ก่อนหน้านี้ฉันจะแสดงบทช่วยสอนนี้พร้อมกับโครงการตัวอย่างขนาดเล็กที่มีอยู่ บน GitHub . เป็นเครื่องมือการจัดการโครงการที่ง่ายขึ้นกับผู้ใช้โครงการและงาน
เทคโนโลยีที่ใช้สำหรับโปรเจ็กต์นี้คือ Node.js และ Express สำหรับเว็บเซิร์ฟเวอร์ SQLite เป็นฐานข้อมูลเชิงสัมพันธ์และ Sequelize เป็น ORM โมเดลสามแบบ ได้แก่ ผู้ใช้โครงการและงานกำหนดไว้ในไฟล์ models
โฟลเดอร์ จุดสิ้นสุด REST /api/users
, /api/projects
และ /api/tasks
ได้สัมผัสกับโลกและถูกกำหนดไว้ในไฟล์ rest
โฟลเดอร์
โปรดทราบว่า GraphQL สามารถติดตั้งบนแบ็คเอนด์และฐานข้อมูลประเภทใดก็ได้โดยใช้ภาษาโปรแกรมใดก็ได้ เทคโนโลยีที่ใช้ที่นี่ถูกเลือกเพื่อความเรียบง่ายและอ่านง่าย
เป้าหมายของเราคือการสร้าง /graphql
จุดสิ้นสุดโดยไม่ต้องลบจุดสิ้นสุด REST จุดสิ้นสุดของ GraphQL จะเข้าสู่ ORM ฐานข้อมูลโดยตรงเพื่อดึงข้อมูลดังนั้นจึงเป็นอิสระจากตรรกะ REST โดยสิ้นเชิง
โมเดลข้อมูลแสดงใน GraphQL โดย ประเภท ซึ่งพิมพ์ผิดมาก ควรมีการแมปแบบ 1 ต่อ 1 ระหว่างโมเดลของคุณและประเภท GraphQL ของเรา User
ประเภทจะเป็น:
type User { id: ID! # The '!' means required firstname: String lastname: String email: String projects: [Project] # Project is another GraphQL type }
แบบสอบถาม กำหนดคำค้นหาที่คุณสามารถเรียกใช้บน GraphQL API ของคุณ ตามแบบแผนควรมี RootQuery
ซึ่งมีคำค้นหาที่มีอยู่ทั้งหมด ฉันยังชี้ให้เห็นถึง REST ที่เทียบเท่าของแต่ละแบบสอบถาม:
type RootQuery { user(id: ID): User # Corresponds to GET /api/users/:id users: [User] # Corresponds to GET /api/users project(id: ID!): Project # Corresponds to GET /api/projects/:id projects: [Project] # Corresponds to GET /api/projects task(id: ID!): Task # Corresponds to GET /api/tasks/:id tasks: [Task] # Corresponds to GET /api/tasks }
หากแบบสอบถามเป็น GET
คำขอ การกลายพันธุ์ สามารถมองเห็นเป็น POST
/ PATCH
/ PUT
/ DELETE
คำขอ (แม้ว่าจะเป็นแบบสอบถามเวอร์ชันที่ซิงโครไนซ์ก็ตาม)
ตามแบบแผนเราใส่การกลายพันธุ์ทั้งหมดของเราใน a RootMutation
:
type RootMutation { createUser(input: UserInput!): User # Corresponds to POST /api/users updateUser(id: ID!, input: UserInput!): User # Corresponds to PATCH /api/users removeUser(id: ID!): User # Corresponds to DELETE /api/users createProject(input: ProjectInput!): Project updateProject(id: ID!, input: ProjectInput!): Project removeProject(id: ID!): Project createTask(input: TaskInput!): Task updateTask(id: ID!, input: TaskInput!): Task removeTask(id: ID!): Task }
โปรดทราบว่าเราได้เปิดตัวประเภทใหม่ที่นี่เรียกว่า UserInput
, ProjectInput
และ TaskInput
นี่เป็นแนวทางปฏิบัติทั่วไปสำหรับ REST เช่นกันเพื่อสร้างโมเดลข้อมูลอินพุตสำหรับการสร้างและอัปเดตทรัพยากร ที่นี่ UserInput
ของเรา ประเภทคือ User
ของเรา พิมพ์โดยไม่มี id
และ projects
และสังเกตคำหลัก input
แทน type
:
input UserInput { firstname: String lastname: String email: String }
ด้วยประเภทการสืบค้นและการกลายพันธุ์เรากำหนดไฟล์ สคีมา GraphQL ซึ่งเป็นสิ่งที่จุดสิ้นสุดของ GraphQL เปิดเผยต่อโลก:
schema { query: RootQuery mutation: RootMutation }
สคีมานี้ได้รับการพิมพ์อย่างชัดเจนและเป็นสิ่งที่ทำให้เราสามารถเติมข้อความอัตโนมัติที่มีประโยชน์เหล่านั้นได้ GraphiQL .
บทเรียนการเขียนโค้ด c++
ตอนนี้เรามีสคีมาสาธารณะแล้วก็ถึงเวลาบอก GraphQL ว่าต้องทำอย่างไรเมื่อมีการร้องขอ / การกลายพันธุ์แต่ละรายการ Resolvers ทำงานหนัก สามารถทำได้ตัวอย่างเช่น:
เรากำลังเลือกตัวเลือกที่สามในแอปตัวอย่างของเรา มาดูกันที่ ไฟล์ resolvers :
const models = sequelize.models; RootQuery: { user (root, { id }, context) { return models.User.findById(id, context); }, users (root, args, context) { return models.User.findAll({}, context); }, // Resolvers for Project and Task go here }, /* For reminder, our RootQuery type was: type RootQuery { user(id: ID): User users: [User] # Other queries }
ซึ่งหมายความว่าถ้า user(id: ID!)
มีการร้องขอแบบสอบถามบน GraphQL จากนั้นเราจะส่งคืน User.findById()
ซึ่งเป็นฟังก์ชัน Sequelize ORM จากฐานข้อมูล
สิ่งที่เกี่ยวกับการเข้าร่วมรุ่นอื่น ๆ ในคำขอ? เราจำเป็นต้องกำหนดตัวแก้ไขเพิ่มเติม:
User: { projects (user) { return user.getProjects(); // getProjects is a function managed by Sequelize ORM } }, /* For reminder, our User type was: type User { projects: [Project] # We defined a resolver above for this field # ...other fields } */
ดังนั้นเมื่อเราขอ projects
ฟิลด์ใน User
พิมพ์ GraphQL การเข้าร่วมนี้จะต่อท้ายแบบสอบถามฐานข้อมูล
กระบวนทัศน์การประกาศโดยพื้นฐานแล้วค่อนข้างคล้ายกับกระบวนทัศน์ขั้นตอน
และในที่สุดตัวแก้ไขสำหรับการกลายพันธุ์:
RootMutation: { createUser (root, { input }, context) { return models.User.create(input, context); }, updateUser (root, { id, input }, context) { return models.User.update(input, { ...context, where: { id } }); }, removeUser (root, { id }, context) { return models.User.destroy(input, { ...context, where: { id } }); }, // ... Resolvers for Project and Task go here }
คุณสามารถเล่นกับสิ่งนี้ได้ที่นี่ เพื่อรักษาข้อมูลบนเซิร์ฟเวอร์ให้สะอาดฉันจึงปิดใช้งานตัวแก้ไขสำหรับการกลายพันธุ์ซึ่งหมายความว่าการกลายพันธุ์จะไม่ทำการสร้างอัปเดตหรือลบการดำเนินการใด ๆ ในฐานข้อมูล (และส่งคืน null
บนอินเทอร์เฟซ) .
แบบสอบถาม
query getUserWithProjects { user(id: 2) { firstname lastname projects { name tasks { description } } } } mutation createProject { createProject(input: {name: 'New Project', UserId: 2}) { id name } }
ผลลัพธ์
{ 'data': { 'user': { 'firstname': 'Alicia', 'lastname': 'Smith', 'projects': [ { 'name': 'Email Marketing Campaign', 'tasks': [ { 'description': 'Get list of users' }, { 'description': 'Write email template' } ] }, { 'name': 'Hire new developer', 'tasks': [ { 'description': 'Find candidates' }, { 'description': 'Prepare interview' } ] } ] } } }
อาจใช้เวลาสักครู่ในการเขียนซ้ำทุกประเภทคำค้นหาและตัวแก้ไขสำหรับแอปที่คุณมีอยู่ อย่างไรก็ตามมีเครื่องมือมากมายที่ช่วยคุณได้ ตัวอย่างเช่น นั่นเอง คือ เครื่องมือ ที่แปลสคีมา SQL เป็นสคีมา GraphQL รวมถึงตัวแก้ไข!
ด้วยสคีมาและตัวแก้ไขที่กำหนดไว้อย่างดีเกี่ยวกับสิ่งที่ต้องทำในแต่ละแบบสอบถามของสคีมาเราสามารถติดตั้ง /graphql
จุดสิ้นสุดที่ส่วนหลังของเรา:
// Mount GraphQL on /graphql const schema = makeExecutableSchema({ typeDefs, // Our RootQuery and RootMutation schema resolvers: resolvers() // Our resolvers }); app.use('/graphql', graphqlExpress({ schema }));
และเราสามารถมีไฟล์ อินเทอร์เฟซ GraphiQL ที่สวยงาม ในส่วนหลังของเรา หากต้องการขอโดยไม่มี GraphiQL เพียงแค่คัดลอก URL ของคำขอ และเรียกใช้ด้วย cURL, AJAX หรือโดยตรงในเบราว์เซอร์ แน่นอนว่ามีไคลเอนต์ GraphQL บางตัวที่จะช่วยคุณสร้างแบบสอบถามเหล่านี้ ดูตัวอย่างด้านล่าง
บทความนี้มีจุดมุ่งหมายเพื่อให้คุณได้เห็นว่า GraphQL มีลักษณะอย่างไรและแสดงให้คุณเห็นว่าเป็นไปได้อย่างแน่นอนที่จะลองใช้ GraphQL โดยไม่ต้องทิ้งโครงสร้างพื้นฐาน REST ของคุณไป วิธีที่ดีที่สุดในการทราบว่า GraphQL เหมาะกับความต้องการของคุณหรือไม่คือการลองด้วยตัวเอง ฉันหวังว่าบทความนี้จะทำให้คุณดำดิ่งลง
มีคุณสมบัติมากมายที่เรายังไม่ได้พูดถึงในบทความนี้เช่นการอัปเดตแบบเรียลไทม์การแบตช์ฝั่งเซิร์ฟเวอร์การตรวจสอบสิทธิ์การอนุญาตการแคชฝั่งไคลเอ็นต์การอัปโหลดไฟล์ ฯลฯ แหล่งข้อมูลที่ยอดเยี่ยมในการเรียนรู้เกี่ยวกับคุณสมบัติเหล่านี้ คือ วิธี GraphQL .
ด้านล่างนี้เป็นแหล่งข้อมูลที่มีประโยชน์อื่น ๆ :
เครื่องมือฝั่งเซิร์ฟเวอร์ | คำอธิบาย |
---|---|
graphql-js | การใช้งานอ้างอิงของ GraphQL คุณสามารถใช้กับ express-graphql เพื่อสร้างเซิร์ฟเวอร์ |
graphql-server | เซิร์ฟเวอร์ GraphQL แบบ all-in-one ที่สร้างโดย ทีมอพอลโล . |
การใช้งานสำหรับแพลตฟอร์มอื่น ๆ | Ruby, PHP ฯลฯ |
เครื่องมือฝั่งไคลเอ็นต์ | คำอธิบาย |
---|---|
รีเลย์ | กรอบสำหรับเชื่อมต่อ React กับ GraphQL |
ลูกค้า apollo . | ไคลเอนต์ GraphQL ที่มีการผูกสำหรับ ตอบสนอง , เชิงมุม 2 และ front-end framework อื่น ๆ . |
โดยสรุปผมเชื่อว่า GraphQL เป็นมากกว่าโฆษณา มันจะยังไม่แทนที่ REST ในวันพรุ่งนี้ แต่จะนำเสนอวิธีแก้ปัญหาที่แท้จริง ถือเป็นเรื่องใหม่และแนวทางปฏิบัติที่ดีที่สุดยังคงพัฒนาอยู่ แต่เป็นเทคโนโลยีที่เราจะได้ยินในอีกไม่กี่ปีข้างหน้า
GraphQL เป็นภาษาแบบสอบถามและเป็นอีกทางเลือกหนึ่งของ REST เริ่มเป็นโครงการภายในที่ Facebook
GraphQL รวมจุดสิ้นสุด RESTful ทั้งหมดของคุณเป็นจุดเดียวและใช้ปริมาณการใช้งานเครือข่ายน้อยลง