اذهب إلى المحتوى

فهم نظام النوع في GraphQL


رشا سعد

تُسهّل GraphQL الاتصال بين الواجهة الأمامية لتطبيقك وقاعدة البيانات عبر استعلاماتها التصريحية والموجهة بطلبات العميل، والبنية التي توضح إمكانات هذه الاستعلامات وتفاصيلها هي مخططات GraphQL المحكومة بنظام النوع، فما هو نظام النوع؟ وكيف يساعدك فهمه على بناء مخططات فعالة تحقق لك الفائدة المرجوة من GraphQL؟ سنعرض ذلك مع بعض الأمثلة التطبيقية على كل نوع بدءًا من الأنواع الخمسة المفردة المبنية مسبقًا built-in scalar، والتعدادات Enums، والقوائم والواجهات Interfaces والأنواع غير الفارغة الغالفة non-null wrapping، ونوع الكائن Object، وأنواع التجريد Abstract والاتحاد Union أيضًا.

متطلبات العمل

ستحتاج هذه الأساسيات لتطبيق الأمثلة الموجودة هنا:

الأنواع المفردة Scalar

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

نوع العدد الصحيح Int

Int هو نوع عدد صحيح لا يحتوي فاصلة عشرية، ومُؤشّر بإشارة موجب أو سالب، ويتكون من 32 بت، لذا تتراوح قيمته بين "2,147,483,647-" و "2,147,483,647"، والعدد الصحيح هو واحد من نوعين فقط لتمثيل الأعداد في GraphQL.

نوع العدد العشري Float

Float هو نوع عدد عشري ثنائي الدقة، مثل 1.2، ومُؤشّر بإشارة موجب أو سالب، وهو النوع المفرد الثاني لتمثيل الأعداد في GraphQL.

نوع السلسلة النصية String

String هو نوع سلسلة محارف بترميز UTF-8 تُستخدم لكتابة النصوص والأعداد الكبيرة جدًا، وتُعد السلاسل النصية أكثر الأنواع المفردة استخدامًا.

النوع البولياني Boolean

يحمل النوع البولياني Boolean إما قيمة صحيحة true أو خاطئة false.

نوع الرقم التعريفي ID

ID هو رقم تعريفي فريد، يُمثّل دائمًا بصيغة سلسلة نصية حتى لو تضمن أرقامًا، ويُولّد غالبًا بواسطة خوارزميات المُعرّف العالمي الفريد Universally Unique Identifier أو اختصارًا UUID.

أنواع مفردة مخصصة

تغطي الأنواع المفردة الخمسة السابقة معظم حالات الاستخدام، لكنك قد تحتاج أنواعًا أخرى تناسب طبيعة تطبيقك، مثل الوقت Time، والتاريخ Date، وعناوين url، والتي يمكنك تعريفها باستعمال الكلمة المفتاحية scalar، وسيعتمد الخادم عندها هذه الأنواع معيارًا للتحقق من صحة بياناتك المدخلة ومطابقتها لمواصفات النوع المخصص الذي تنتمي إليه. إليك مثالًا عن تعريف نوع التاريخ Date:

scalar Date

يستخدم خادم GraphQL الوحدة البرمجية GraphQLScalarType للتعامل مع الأنواع الجديدة المُضافة.

نوع التعداد Enum

يصف التعداد مجموعةً من القيم المحتملة للعنصر.

لو طبقت الأمر على اللعبة التي استخدمناها مثالًا في مقالات السلسلة، ستجد أنك تحتاج نوع التعداد عند تعريف وظائف شخصيات اللعبة Job، وأنواعهم Species، وأسلحتهم وما إلى ذلك؛ فلكل شخصية قيم محددة لا ينبغي أن يقبل النظام غيرها. يُعرّف نوع التعداد باستعمال الكلمة المفتاحية enum، وتكتب التعدادات ضمنه بالحروف الإنجليزية الكبيرة كما يلي:

"The job class of the character."
enum Job {
  FIGHTER
  WIZARD
}

"The species or ancestry of the character."
enum Species {
  HUMAN
  ELF
  DWARF
}

تضمن بهذه الطريقة أن وظيفة الشخصية Job ستكون إما مقاتل FIGHTER أو ساحر WIZARD فقط، ولن يقبل الخادم أي كلمة أخرى مهما كانت؛ أما لو عرّفت وظيفة الشخصية على أنها سلسلة نصية عادية String، فستكون جميع الكلمات سيان عند الخادم وسيقبل أي مجموعة محارف تُعطى له.

تفيدك التعدادات أيضًا في تمرير القيم الوسيطة، إذ يمكنك مثلًا تعريف تعداد enum لتحديد إذا كان سلاح الشخصية يُحمَل بيد واحدة مثل السيف أم أنه ثقيل مثل الفأس ويحتاج لكلتا اليدين، سيساعدك هذا التعداد على التحكم بتجهيز الشخصية بسلاح واحد أو بسلاحين، لنسمي التعداد اليد Hand، ونعرّفه بالتعليمات التالية:

enum Hand {
  SINGLE
  DOUBLE
}

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
  hand: Hand
}

type Query {
  weapons(hand: Hand = SINGLE): [Weapon]
}

صُرّح عن التعداد Hand بحالتين لاستعمال اليد، مفردة SINGLE، ومزدوجة DOUBLE، أما الوسيط المسند إلى الحقل weapons في الاستعلام فيحمل القيمة الافتراضية SINGLE، وهي القيمة التي يأخذها في حال لم تمنحه قيمة مغايرة.

النوع غير الفارغ Non-Null

قد تستغرب عدم وجود النوع null أو undefined بين أنواع GraphQL المفردة رغم أنه يُعدّ نوعًا شائعًا في معظم لغات البرمجة، ويرجع السبب في ذلك إلى أن جميع أنواع GraphQL تقبل القيمة Null افتراضيًا وفي حال أردت تغيير الأمر عليك استخدام إشارة التعجب مع اسم النوع.

يُعرّف النوع غير الفارغ Non-Null في GraphQL بأنه مُعدِّل للأنواع الأخرى، فمثلًا الحقل String هو حقل اختياري قد يحمل قيمة أو لا، أما الحقل من النوع !String فهو إجباري أي لا يجب أن يكون فارغًا.

نوع القائمة List

نوع القائمة هو أيضًا من الأنواع المُعدِّلة للأنواع الأخرى، فأي نوع يوضع بين قوسين مربعين [] يتحول لقائمة.

على سبيل المثال يُعرّف النوع [Int] قائمةً من نوع الأعداد الصحيحة Int والنوع [String] قائمةً من السلاسل النصية String وقِس على ذلك، ويمكنك استخدام القائمة مع إشارة التعجب لتحصل على نوع غير فارغ ومُعرّف على أنه قائمة مثل ![String].

نوع الكائن Object

شبهنا الأنواع المفردة بأوراق الشجرة في بداية المقال، وتبعًا لهذا التشبيه تكون الكائنات هي الأغصان، وهي معظم ما سنكتبه في مخطط GraphQL.

يبدأ تعريف الكائن بالكلمة المفتاحية type، ويحتوي كل كائن على حقل واحد على الأقل. تُسمى الحقول بالمفاتيح وبجانب كل حقل تجد نوع القيمة التي يقبلها. لا يجوز أن تبدأ أسماء الحقول برمز الشرطتين السفليتين __، فهو مخصص لنظام الاستقراء.

لو أعطينا مثالًا في سياق أمثلتنا السابقة عن اللعبة، فيمكنك إنشاء كائن يمثل المقاتلين Fighter:

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
}

يمتلك هذا الكائن أربعة حقول:

  • id رقم تعريفي من نوع ID غير فارغ.
  • name سلسلة نصية String غير فارغة.
  • level عدد صحيح Int.
  • active قيمة منطقية Boolean غير فارغة.

ونود الإشارة هنا إلى قدرتك على كتابة تعليق قبل تعريف الكائن مباشرةً بين علامتي اقتباس، مثل:

"A hero with direct combat ability and strength."

وسيظهر تعليقك بمثابة الوصف للكائن.

قد تكون حقول الكائن بيانات مفردة كما في مثالنا السابق أو كائنات أخرى. ألقِ نظرةً على المثال أدناه، إذ عرّفنا في البداية كائنًا من نوع السلاح Weapon يتضمن أسماء الأسلحة وطبيعة هجومها ومدى تأثيرها، ثم أضفنا السلاح weapon على أنه أحد حقول كائن المقاتل Fighter وهو weapon. يمكن لمخطط GraphQL أن يُضبط بحيث يعالج الحقل weapon الكائن Weapon:

"A valiant weapon wielded by a fighter."
type Weapon {
  name: String!
  attack: Int
  range: Int
}

"A hero with direct combat ability and strength."
type Fighter {
  id: ID!
  name: String!
  level: Int
  active: Boolean!
  weapon: Weapon
}

يمكن أن تتداخل الكائنات ببعضها ويشترك أكثر من كائن بالحقل نفسه.

أنواع عمليات الجذر

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

تُعرّف هذه العمليات ضمن كائن الجذر schema في مخطط GraphQL، والمميز بالكلمة المفتاحية schema. ألقِ نظرةً على التعليمات التالية:

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}

لنبدأ بنوع الاستعلام Query type، وهو عملية أساسية لا غنى عنها في أي مخطط GraphQL، ويمثّل عملية قراءة، ويقابل الطلب GET في واجهة REST API. ألقِ نظرةً على تعريف استعلام الجذر Query للعبة نفسها، إذ يُرجِع هذا الاستعلام قائمة بالمقاتلين:

type Query {
  fighters: [Fighter]
}

أما الطفرات فتمثل طلبات الكتابة لأنها تعدل على المخطط، فهي تشبه طلبات PUT و DELETE و POST في REST API. يبين المثال أدناه الطفرة Mutation التي تستخدم الوسيط input لإضافة مقاتل addFighter:

type Mutation {
  addFighter(input: FighterInput): Fighter
}

عملية الجذر الأخيرة هي الاشتراكات Subscription، التي تفيدك في حالات تدفق الأحداث بين الخادم والعميل، وتعمل بتوافق تام مع تقنية WebSocket.

بالعودة للعبة، يمكنك استخدام الاشتراكات لتمثيل الاصطدامات العشوائية في المعارك مثلًا على النحو التالي:

type Subscription {
  randomBattle(enemy: Enemy): BattleResult
}

وسطاء الحقول Field Arguments

حقول الكائن في GraphQL هي دوال تقليدية ترجِِع قيمًا وتقبل متغيراتٍ وسيطة مثلها مثل الدوال الأخرى، وتنتمي وسطاء الحقل إلى أي نوع غير فارغ من أنواع GraphQL باستثناء الكائنات، وتُعرّف بكتابة اسم المتغير يليه نوعه.

ألقِ نظرةً على المثال التالي لترشيح الكائن Fighter باستخدام الحقل id، ولاحظ إشارة التعجب بعد النوع للدلالة على عدم قبول قيم ID الفارغة.

type Query {
  fighter(id: ID!): Fighter
}

تلعب الوسطاء في هذا المثال دورًا في ترشيح كائن المقاتل Fighter بحسب الرقم التعريفي، فتمكّنك من جلب عنصر محدد من مخزن البيانات، وتستطيع استخدامها لأي استعلامات أخرى تريدها.

نوع الواجهة Interface

تشبه بنية نوع الواجهة بنية الكائنات، فهي تتكون من عدة حقول لكل منها اسم ونوع، والغاية منها وضع نموذج مشترك يمكن تطبيقه على مجموعة كائنات تمتلك حقولًا مشتركة.

أنشأنا في الفقرات السابقة كائن المقاتل Fighter وهو يشترك في بعض حقوله مع كائنات أخرى تلعب أدوارًا في اللعبة، مثل المعالجين Healer والسحرة Wizard وغيرهم. يمكنك في هذه الحالة إنشاء واجهة تتضمن الحقول المشتركة بين جميع هذه الشخصيات، ثم إنشاء كائنات منفصلة لكل شخصية تكون تطبيقًا للواجهة. ألقِ نظرةً على المثال التالي لتوضيح الأمر.

أنشئ في البداية واجهة تسمى BaseCharacter تبدأ بالكلمة المفتاحية interface وتتضمن كل الحقول المشتركة بين الشخصيات، كما يلي:

"A hero on a quest."
interface BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job
}

لكل شخصية في اللعبة رقم تعريفي خاص id واسم name ومستوى level ونوع species وعمل job.

خذ مثلًا نوع المقاتل Fighter ونوع الساحر Wizard لدى كل منهم جميع الحقول الموجودة في الواجهة BaseCharacter مع بعض الاختلاف؛ فالمقاتل يستعمل الأسلحة Weapon؛ والساحر يستعمل التعويذات Spell. يمكنك إذًا تطبيق الواجهة لتسهيل تعريف النوعين، وذلك باستخدام الكلمة المفتاحية implements، وفق التالي:

"A hero with direct combat ability and strength."
type Fighter implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  weapon: Weapon
}

"A hero with a variety of magical powers."
type Wizard implements BaseCharacter {
  id: ID!
  name: String!
  level: Int!
  species: Species
  job: Job!
  spells: [Spell]
}

تذكر أن شرط تطبيق الواجهة على كائن معين هو امتلاك الكائن لجميع حقول الواجهة دون استثناء.

نوع الاتحاد Union

يمثل نوع الاتحاد مجموعة كائنات تصلح لأن تكون استجابة لاستعلام واحد.

لننشئ مثلًا اتحادًا لشخصيات اللعبة Character تتضمن كافة كائنات الشخصيات، وهي في حالتنا كائنات المقاتل Fighter والساحر Wizard.

union Character = Wizard | Fighter

يبدأ تعريف الاتحاد بالكلمة المفتاحية Union، والتي تكون مدخلاتها إما كائنات أو واجهات فهي لا تقبل الأنواع المفردة. ترمز إشارة اليساوي = ضمن التعريف إلى إسناد القيمة، أما الخط الرأسي | فيكافئ عبارة OR المنطقية.

الآن، إذا رغبت بالاستعلام عن شخصيات اللعبة فيمكنك استخدام الاتحاد Character ليستجيب الخادم بقائمة تضم جميع الكائنات من نوع مقاتل Fighter أو ساحر Wizard.

الخاتمة

تعرّفنا في هذا المقال على أنواع GraphQL المختلفة، شبهنا المخطط بالشجرة فكانت الأنواع المفردة بمثابة الأوراق التي ينتهي إليها كل شيء، فهي أنواعٌ تساهم بتشكيل الأنواع الأخرى، ولها خمسة أنواع أساسية هي Int و Float و String و Boolean و ID ويمكنك إنشاء أنواعك الخاصة، ثم عرضنا نوع التعداد الذي يمنحك تحكمًا أكبر في استجابة الخادم فتزوده بمجموعة محددة من القيم الصالحة، وبعدها اطلعنا على مُعدِّلات الأنواع أي القائمة والقيم غير الفارغة التي تُغيّر طبيعة الأنواع الأخرى، وتوسعنا في الكائنات لأنها عماد مخططات GraphQL فهي أشبه بأغصان الشجرة بما فيها من كائنات جوهرية تتمثل في عمليات الجذر أي الاستعلامات والطفرات والاشتراكات، ولا ننسَ الواجهات والروابط التي تُسهّل علينا التعامل مع الكائنات ذات الخصائص المشتركة.

لديك الآن أساسٌ نظري جيد ننصحك ببناء خادمك الخاص بمساعدة مقال إعداد خادم GraphQL في بيئة Node.js والتدرب عمليًّا على تطبيق الأنواع لحين إتقانها.

ترجمة -وبتصرف- للمقال Understanding the GraphQL Type System لصاحبته Tania Rascia.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...