ماهي لغة الإستعلام GraphQL ؟

------

تقدم لغة الإستعلام  GraphQl طريقة جديدة لتمثيل البيانات التي يرغب المستخدمون في الاستفهام عنها. وتُستخدم لتحميل البيانات من الخادم server إلى العميل client. كما تتميز استعلامات GraphQl بثلاث خصائص رئيسية:

  • تتيح للعميل تحديد البيانات التي يحتاجها بالضبط دون زيادة أو نقصان.
  • تسهل عملية تجميع البيانات من مصادر متعددة.
  • تستخدم type system لوصف البيانات.

كيف نشأت استعلامات GraphQl؟ وما هي المشكلة التي تسعى GraphQl لحلها؟ وكيف يمكننا البدء باستخدامها؟  تابع قراءة المقال إذا أردت معرفة ذلك.

مشاكل REST APIs

أنشأت استعلامات GraphQl في البداية من طرف فيسبوك كبديل REST APIs. ولكن حتى التطبيقات البسيطة سرعان ما تصطدم بالمشكلات الكبيرة التي تعاني منها REST APIs.

على سبيل المثال، لنفترض أنك تحتاج إلى تحميل قائمة المنشورات (facebook posts)، وتحت كل منشور قائمة المعجبين به بما في ذلك أسماء المعجبين وصورة الملف الشخصي الخاصة بهم. هذا سهل جدا، كل ما عليك فعله هو البحث عن قائمة المنشورات posts والتي تتضمن داخلها قائمة المعجبين وأسمائهم وصورهم.

الآن، حان الوقت للعمل بصفة فعلية على التطبيق، وستكتشف عندها أنك قد قمت بتحميل الكثير من البيانات الزائدة التي لا حاجة لها. وهذا ما يؤدي إلى بطء عملية البحث والتحميل. في المحصلة، ستضطر إلى تحميل نقطتي نهاية 2 endpoints: واحدة تتضمن قائمة المعجبين likes وأخرى من دونهم.

علاوة على البيانات الزائدة، لدينا مشاكل أخرى: فقد تكون قائمة المنشورات posts مخزنة في قاعدة بيانات MySQL في حين أن البيانات الخاصة بالمعجبين بالمنشورات likes مخزنة في قاعدة بيانات مختلفة تماما (Redis store مثلا).

فلنتخيل الآن أن هذه المشاكل تتكرر مع كل عملاء API ومصادر البيانات التي تديرها فيسبوك! عندها ستدرك حجم العيوب والقصور الذي تعاني منه REST APIs.

لغة الإستعلام GraphQL

إن الحل الذي قدمته فيسبوك هو بسيط جدا من الناحية النظرية: فبدلا من اعتماد العديد من نقاط النهاية “endpoints” غير الذكية، اعتمدت على نقطة نهاية واحدة ذكية وقادرة على تلقي الإستعلامات المعقدة وتنفيذها ومن ثم صياغتها بالشكل الذي يرغب به العميل.

من الناحية التطبيقية، تشكل استعلامات GraphQL طبقة بين العميل وواحدة أو أكثر من مصادر البيانات. وتتمثل مهمة GraphQL في تلقي الإستعلامات من العميل والبحث عن مصادر البيانات  الموافقة لهذه الإستعلامات ومن ثم مد العميل بها.

في الحقيقة، نموذج العمل الذي يعتمده REST شبيه بأن تقوم بطلب بيتزا، طلب البضائع من البقالة والإتصال بمنظف الملابس لطلب ملابسك. في المحصلة، لقد قمت بثلاث عمليات تسوق وثلاث اتصالات.

في المقابل، تعتمد استعلامات Graphql  نموذج عمل مختلف تماما. فهو شبيه بأن يكون لديك مساعد شخصي: ما عليك إلا أن تعطيه عناوين الأماكن الثلاثة وتحدد طلباتك (اجلب لي ملابسي من منظف الملابس الجافة وبيتزا كبيرة و22 بيضة) ثم ما عليك سوى الانتظار حتى وصول طلباتك المختلفة.

بعبارة أخرى ، تضع استعلامات GraphQL لغة موحدة ومقننة للتحدث إلى هذا المساعد الشخصي السحري.

تتكون GraphQL API من ثلاث أجزاء رئيسية: schema والاستعلامات queries و resolvers.

الاستعلامات queries

الطلب الذي تقدمه لمساعد  GraphQL الشخصي يسمى طلب استعلام query وتكون عادة على هذا الشكل:

query {
stuff
}

كما هو ملاحظ في المثال السابق، نعلن عن استعلام GraphQL جديد باستخدام الكلمة المفتاحية query، ثم نطلب البيانات المخزنة في حقل field يسمى  “stuff”. إن من أهم مميزات استعلامات GraphQL هو أنها تدعم الحقول المتداخلة nested fields. وتتيح لنا هذه الميزة أن نطلب استعلامات أكثر تعقيدا وعمقا.

{stuff {
eggs
shirt
pizza
}
}

يمكنكم بسهولة أن تلاحظوا من خلال الأمثلة السابقة أن العميل الذي يطلب الاستعلام لا يهتم بالمصدر الذي تأتي منه البيانات. فهو يكتفي بإرسال الإستعلام ويترك باقي المهمة لخادم GraphQL. كما تجدر الإشارة إلى أن حقول البيانات التي يطلبها العميل يمكن أن تكون جدولا كاملا من البيانات وليس مجرد حقل وحيد.

فيما يلي سنقدم مثالا لطلب استعلام يسأل عن قائمة كاملة من المنشورات:

query {
posts { # this is an array
title
body
author { # we can go deeper!
name
avatarUrl
profileUrl
}
}
}

تدعم حقول الإستعلام أيضا الوسائط arguments. فإن كنا مثلا نرغب في الإستعلام عن منشور بعينه، فيمكننا أن نضيف مثلا وسيط من نوع id إلى حقل post:

query {
post(id: "123foo"){
title
body
author{
name
avatarUrl
profileUrl
}
}
}

وأخيرا، إذا كنت ترغب في جعل هذا الوسيط id ديناميكي، فيمكن أن تعرف متغير ومن ثم تقوم بإعادة استعماله داخل الإستعلام (من الملاحظ أنه يجب أيضا تسمية الإستعلام في هذه الحالة)

query getMyPost($id: String) {
post(id: $id){
title
body
author{
name
avatarUrl
profileUrl
}
}
}

لتجربة كل هذا تطبيقيا يمكنك أن تستخدم GitHub’s GraphQL API Explorer. على سبيل المثال ، قم بتجربة الاستعلام التالي:

query {
repository(owner: "graphql", name: "graphql-js"){
name
description
}
}

لاحظ أنه عند محاولة كتابة اسم حقل جديد أسفل description ، ستقوم IDE تلقائيًا بعرض أسماء الحقول الممكنة التي يتم إكمالها تلقائيا من GraphQL API نفسه.

Resolvers

إن أفضل مساعد شخصي في العالم لا يمكنه الذهاب إلى محل تنظيف الملابس وجلب ملابسك إذا لم تعطه عنوان المحل.

كذلك فإن خادم GraphQL لن يتمكن من معرفة ما يجب عليه القيام به مع أي استعلام يرد إليه ما لم تخبره باستخدام resolver.

يرشد resolver خادم GraphQL إلى المكان والكيفية التي يجلب بها البيانات الموافقة لحقل معين. على سبيل المثال ، إليك ما يمكن أن يبدو عليه resolver الخاص بحقل post  المذكور أعلاه.

Query: {
post(root, args) {
return Posts.find({ id: args.id });
}
}

كما يمكن استخدام resolvers مع الحقول الفرعية أيضا. مثال يمكننا أن نستخدم resolver لمعرفة العنوان الذي تخزن فيه البيانات الخاصة بحقل author التابع لحقل post.

Query: {
post(root, args) {
return Posts.find({ id: args.id });
}
},
Post: {
author(post) {
return Users.find({ id: post.authorId})
}
}

من الجدير بالذكر أن مهمة resolver لا تقتصر على تحديد العنوان الموافق للبيانات المطلوبة. إذ بإمكانه أيضا احتساب عدد التعليقات التي حصل عليها منشور معين:

Post: {
author(post) {
return Users.find({ id: post.authorId})
},
commentsCount(post) {
return Comments.find({ postId: post.id}).count()
}
}

المفتاح الرئيسي لفهم كل هذا هو ادراك أن استعلامات GraphQL تفصل بين API schema و database schemas. بعبارة أخرى، من المحتمل جدا أن تكون الحقول التالية: author و commentsCount غير موجودة في قاعدة البيانات ولكن GraphQL resolver قادر على محاكاة وجودها.

في الحقيقة، بفضل استخدام resolver، يمكنك كتابة الكود الذي ترغب به. وهذا هو السبب الذي يمكنك أيضًا من استخدامها لتعديل محتويات قاعدة البيانات الخاصة بك، وفي هذه الحالة تُعرف باسم mutation resolvers.