تُسهّل GraphQL الاتصال بين الواجهة الأمامية لتطبيقك وقاعدة البيانات عبر استعلاماتها التصريحية والموجهة بطلبات العميل، والبنية التي توضح إمكانات هذه الاستعلامات وتفاصيلها هي مخططات GraphQL المحكومة بنظام النوع، فما هو نظام النوع؟ وكيف يساعدك فهمه على بناء مخططات فعالة تحقق لك الفائدة المرجوة من GraphQL؟ سنعرض ذلك مع بعض الأمثلة التطبيقية على كل نوع بدءًا من الأنواع الخمسة المفردة المبنية مسبقًا built-in scalar، والتعدادات Enums، والقوائم والواجهات Interfaces والأنواع غير الفارغة الغالفة non-null wrapping، ونوع الكائن Object، وأنواع التجريد Abstract والاتحاد Union أيضًا.
متطلبات العمل
ستحتاج هذه الأساسيات لتطبيق الأمثلة الموجودة هنا:
- الاطلاع على المقال الأول مقدمة إلى GraphQL لفهم المبادئ الأساسية للتقنية.
- بيئة عمل تحوي خادم GraphQL، يمكنك تجهيزها بتنفيذ الخطوات الموجودة في مقالنا الثاني إعداد خادم GraphQL في بيئة Node.js.
الأنواع المفردة 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.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.