واجهات GraphQL

المحللات (Resolvers) بالتفصيل

18 دقيقة الدرس 6 من 35

فهم المحللات (Resolvers)

المحللات هي قلب كل خادم GraphQL. تحدد كيفية جلب البيانات لكل حقل في المخطط الخاص بك. فهم المحللات بعمق أمر بالغ الأهمية لبناء واجهات برمجة تطبيقات GraphQL فعالة وقابلة للصيانة.

توقيع دالة المحلل

تستقبل كل دالة محلل أربعة معاملات:

const resolvers = { Query: { user: (parent, args, context, info) => { // parent: النتيجة من المحلل الأب // args: المعاملات الممررة إلى الحقل // context: البيانات المشتركة عبر جميع المحللات // info: معلومات حول تنفيذ الاستعلام return getUserById(args.id); } } };
المعاملات الأربعة:
  • parent: القيمة المُرجعة من المحلل الأب (أو القيمة الجذرية)
  • args: كائن يحتوي على المعاملات الممررة إلى الحقل
  • context: كائن مشترك متاح لجميع المحللات (المصادقة، اتصالات قاعدة البيانات، إلخ)
  • info: يحتوي على معلومات حول العملية (اسم الحقل، المسار، تفاصيل المخطط)

سلاسل المحللات

يتم تنفيذ المحللات في سلسلة، حيث يتم تمرير نتيجة المحلل الأب إلى محللاته الفرعية:

const resolvers = { Query: { user: (parent, args, context, info) => { return { id: args.id, name: 'John Doe', email: 'john@example.com' }; } }, User: { // يحتوي Parent على كائن المستخدم من محلل Query.user posts: (parent, args, context, info) => { return getPostsByUserId(parent.id); }, followers: (parent, args, context, info) => { return getFollowersByUserId(parent.id); } } };
يتيح لك معامل parent الوصول إلى البيانات من المحلل الأب، مما يمكّن جلب البيانات المتداخلة دون استعلامات زائدة.

المحللات الافتراضية

يوفر GraphQL محللات افتراضية للحقول التي تطابق أسماء الخصائص على كائن الأب:

const resolvers = { Query: { user: () => ({ id: 1, name: 'Alice', email: 'alice@example.com' }) }, User: { // لا حاجة لتعريف محللات لـ id، name، email // يحلها GraphQL تلقائيًا من كائن الأب // حدد فقط محللات مخصصة للحقول المحسوبة fullProfile: (parent) => { return `${parent.name} (${parent.email})`; } } };
إذا كان الحقل موجودًا على كائن الأب ولم تحدد محللًا، فسيستخدم GraphQL تلقائيًا قيمة الخاصية. يُسمى هذا المحلل الافتراضي.

المحللات غير المتزامنة (Async)

تحتاج معظم المحللات إلى جلب البيانات بشكل غير متزامن. يدعم GraphQL بالكامل async/await:

const resolvers = { Query: { users: async (parent, args, context, info) => { const users = await context.db.user.findMany(); return users; }, user: async (parent, { id }, context) => { const user = await context.db.user.findUnique({ where: { id } }); if (!user) throw new Error('User not found'); return user; } }, User: { posts: async (parent, args, context) => { return await context.db.post.findMany({ where: { authorId: parent.id } }); } } };
ينتظر GraphQL تلقائيًا اكتمال جميع المحللات غير المتزامنة قبل إرجاع الاستجابة. يمكنك استخدام async/await بأمان في أي محلل.

أنماط المحللات

إليك أنماط شائعة لتنظيم المحللات:

1. نمط طبقة الخدمة

// services/userService.js class UserService { constructor(db) { this.db = db; } async getUsers() { return await this.db.user.findMany(); } async getUserById(id) { return await this.db.user.findUnique({ where: { id } }); } } // resolvers/userResolvers.js const resolvers = { Query: { users: (parent, args, { services }) => { return services.user.getUsers(); }, user: (parent, { id }, { services }) => { return services.user.getUserById(id); } } };

2. نمط DataLoader (لمشكلة N+1)

const DataLoader = require('dataloader'); const userLoader = new DataLoader(async (ids) => { const users = await db.user.findMany({ where: { id: { in: ids } } }); return ids.map(id => users.find(user => user.id === id)); }); const resolvers = { Post: { author: (parent, args, { loaders }) => { return loaders.user.load(parent.authorId); } } };

3. نمط محلل الحقل

const resolvers = { User: { // حقل محسوب fullName: (parent) => { return `${parent.firstName} ${parent.lastName}`; }, // حقل افتراضي يتطلب استعلام قاعدة بيانات postCount: async (parent, args, { db }) => { return await db.post.count({ where: { authorId: parent.id } }); }, // حقل مشروط email: (parent, args, { currentUser }) => { if (currentUser && currentUser.id === parent.id) { return parent.email; } return null; // إخفاء البريد الإلكتروني عن المستخدمين الآخرين } } };
اعتبار الأداء: كن حذرًا مع محللات الحقول التي تنفذ استعلامات قاعدة البيانات. يتم تنفيذها لكل مثيل من هذا النوع. استخدم DataLoader أو استعلامات الدُفعات لتجنب مشاكل N+1.

معالجة الأخطاء في المحللات

const resolvers = { Query: { user: async (parent, { id }, context) => { try { const user = await context.db.user.findUnique({ where: { id } }); if (!user) { throw new Error('User not found'); } return user; } catch (error) { // معالجة أخطاء مخصصة throw new Error(`Failed to fetch user: ${error.message}`); } } } };
تمرين تطبيقي:
  1. أنشئ محللًا يجلب مقالة مدونة ويسجل كل من المعاملات الأربعة (parent, args, context, info)
  2. أنشئ محللًا متداخلًا لـ Post.comments يستخدم معرف المقالة الأب
  3. نفذ حقلًا محسوبًا Post.isPublished يتحقق مما إذا كان publishedAt في الماضي
  4. أنشئ محللًا غير متزامن يجلب البيانات من قاعدة بيانات ويعالج الأخطاء بشكل صحيح