فهم طفرات GraphQL
بينما تُستخدم الاستعلامات لجلب البيانات، تُستخدم الطفرات لتعديل البيانات على الخادم. تسمح لك الطفرات بإنشاء البيانات أو تحديثها أو حذفها. تماماً مثل الاستعلامات، يمكن للطفرات إرجاع البيانات، وهو أمر مفيد للحصول على الحالة المحدثة بعد العملية.
مفهوم رئيسي: الطفرات مشابهة للاستعلامات في بناء الجملة، لكنها تشير إلى الخادم أنك تنوي تعديل البيانات. بالاتفاق، يجب أن تكون أي عملية تسبب تأثيرات جانبية طفرة، وليس استعلاماً.
بناء جملة الطفرة الأساسي
إليك طفرة بسيطة لإنشاء مستخدم جديد:
# تعريف المخطط
type Mutation {
createUser(name: String!, email: String!): User!
}
# طلب الطفرة
mutation {
createUser(name: "أحمد حسن", email: "ahmed@example.com") {
id
name
email
createdAt
}
}
# الاستجابة
{
"data": {
"createUser": {
"id": "456",
"name": "أحمد حسن",
"email": "ahmed@example.com",
"createdAt": "2026-02-16T10:30:00Z"
}
}
}
لاحظ أن الطفرة تُرجع المستخدم المُنشأ، مما يسمح للعميل بالوصول الفوري إلى البيانات الجديدة دون إجراء استعلام منفصل.
الطفرات مع المتغيرات
مثل الاستعلامات، يجب أن تستخدم الطفرات المتغيرات للقيم الديناميكية:
mutation CreateUser($name: String!, $email: String!, $age: Int) {
createUser(name: $name, email: $email, age: $age) {
id
name
email
age
createdAt
}
}
# المتغيرات
{
"name": "أحمد حسن",
"email": "ahmed@example.com",
"age": 28
}
أفضل ممارسة: استخدم دائماً المتغيرات مع الطفرات. هذا يمنع هجمات الحقن ويسمح بتخزين استعلامات أفضل والتحقق منها.
أنواع الإدخال للوسائط المعقدة
عندما يكون للطفرات العديد من الوسائط، استخدم أنواع الإدخال لتجميع البيانات المرتبطة:
# المخطط مع نوع الإدخال
input CreateUserInput {
name: String!
email: String!
age: Int
bio: String
avatar: String
}
input UserAddressInput {
street: String!
city: String!
country: String!
postalCode: String!
}
type Mutation {
createUser(input: CreateUserInput!, address: UserAddressInput): User!
}
# الطفرة مع نوع الإدخال
mutation CreateUser($input: CreateUserInput!, $address: UserAddressInput) {
createUser(input: $input, address: $address) {
id
name
email
address {
city
country
}
}
}
# المتغيرات
{
"input": {
"name": "أحمد حسن",
"email": "ahmed@example.com",
"age": 28,
"bio": "مطور Full-stack"
},
"address": {
"street": "123 شارع الرئيسي",
"city": "القاهرة",
"country": "مصر",
"postalCode": "11511"
}
}
طفرات التحديث
تتطلب طفرات التحديث عادةً معرفاً لتحديد السجل وكائن إدخال بالحقول المراد تحديثها:
input UpdateUserInput {
name: String
email: String
age: Int
bio: String
}
type Mutation {
updateUser(id: ID!, input: UpdateUserInput!): User!
}
# الطفرة
mutation UpdateUser($id: ID!, $input: UpdateUserInput!) {
updateUser(id: $id, input: $input) {
id
name
email
age
bio
updatedAt
}
}
# المتغيرات - تحديث حقول محددة فقط
{
"id": "456",
"input": {
"bio": "مطور Full-stack أول",
"age": 29
}
}
مهم: بالنسبة لطفرات التحديث، اجعل حقول الإدخال قابلة للقيمة الفارغة حتى يتمكن العملاء من تحديث الحقول التي يريدون تغييرها فقط. الحقول غير القابلة للقيمة الفارغة ستتطلب إرسال جميع القيم في كل تحديث.
طفرات الحذف
تأخذ طفرات الحذف عادةً معرفاً وتُرجع إما الكائن المحذوف أو حالة النجاح:
type DeleteUserPayload {
success: Boolean!
message: String
deletedUserId: ID
}
type Mutation {
deleteUser(id: ID!): DeleteUserPayload!
}
# الطفرة
mutation DeleteUser($id: ID!) {
deleteUser(id: $id) {
success
message
deletedUserId
}
}
# الاستجابة
{
"data": {
"deleteUser": {
"success": true,
"message": "تم حذف المستخدم بنجاح",
"deletedUserId": "456"
}
}
}
استجابات الطفرة
من أفضل الممارسات إرجاع استجابات منظمة من الطفرات، بما في ذلك حالة النجاح والرسائل والبيانات المعدلة:
type CreateUserPayload {
success: Boolean!
message: String
user: User
errors: [UserError!]
}
type UserError {
field: String!
message: String!
}
type Mutation {
createUser(input: CreateUserInput!): CreateUserPayload!
}
# الطفرة مع أخطاء التحقق
{
"data": {
"createUser": {
"success": false,
"message": "فشل التحقق",
"user": null,
"errors": [
{
"field": "email",
"message": "البريد الإلكتروني مستخدم بالفعل"
},
{
"field": "age",
"message": "يجب أن يكون العمر 18 على الأقل"
}
]
}
}
}
أفضل ممارسة: قم بإرجاع معلومات خطأ مفصلة في استجابات الطفرة بدلاً من الاعتماد فقط على أخطاء GraphQL. هذا يمنح العملاء مزيداً من التحكم في معالجة الأخطاء وتعليقات المستخدم.
طفرات متعددة في طلب واحد
يمكنك تنفيذ طفرات متعددة في طلب واحد. على عكس الاستعلامات (التي يتم تنفيذها بالتوازي)، يتم تنفيذ الطفرات بالتسلسل:
mutation CreateMultipleUsers {
user1: createUser(input: {
name: "أحمد حسن"
email: "ahmed@example.com"
}) {
id
name
}
user2: createUser(input: {
name: "سارة محمد"
email: "sara@example.com"
}) {
id
name
}
user3: createUser(input: {
name: "عمر علي"
email: "omar@example.com"
}) {
id
name
}
}
مهم: يتم تنفيذ الطفرات بالترتيب الذي تظهر به في الطلب. هذا يضمن أن user1 يتم إنشاؤه قبل user2، و user2 قبل user3. هذا أمر بالغ الأهمية للحفاظ على اتساق البيانات.
مفهوم التحديثات المتفائلة
تحسن التحديثات المتفائلة تجربة المستخدم عن طريق تحديث واجهة المستخدم على الفور، قبل أن يستجيب الخادم. إذا فشلت الطفرة، تعكس واجهة المستخدم التغيير:
// كود عميل زائف
function updateUserProfile(userId, newName) {
// 1. تحديث واجهة المستخدم بشكل متفائل على الفور
updateUIWithNewName(newName);
// 2. إرسال الطفرة إلى الخادم
client.mutate({
mutation: UPDATE_USER,
variables: { id: userId, input: { name: newName } }
})
.then(response => {
// 3. نجح الخادم - واجهة المستخدم محدثة بالفعل
console.log('تم تأكيد التحديث');
})
.catch(error => {
// 4. فشل الخادم - إرجاع تغييرات واجهة المستخدم
revertUIToOldName();
showErrorMessage(error);
});
}
يوفر عملاء GraphQL مثل Apollo Client دعماً مدمجاً للتحديثات المتفائلة، مما يجعل هذا النمط سهل التنفيذ.
اتفاقيات تسمية الطفرات
يؤدي اتباع اتفاقيات تسمية متسقة إلى جعل واجهة برمجة التطبيقات الخاصة بك أكثر قابلية للتنبؤ وأسهل في الاستخدام:
type Mutation {
# عمليات CRUD
createUser(input: CreateUserInput!): CreateUserPayload!
updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload!
deleteUser(id: ID!): DeleteUserPayload!
# إجراءات محددة
publishPost(id: ID!): PublishPostPayload!
archivePost(id: ID!): ArchivePostPayload!
# عمليات جماعية
createManyUsers(inputs: [CreateUserInput!]!): CreateManyUsersPayload!
deleteManyUsers(ids: [ID!]!): DeleteManyUsersPayload!
# عمليات العلاقات
addPostTag(postId: ID!, tagId: ID!): AddPostTagPayload!
removePostTag(postId: ID!, tagId: ID!): RemovePostTagPayload!
# المصادقة
login(email: String!, password: String!): LoginPayload!
logout: LogoutPayload!
refreshToken(token: String!): RefreshTokenPayload!
}
أنماط تسمية شائعة:
- create{Resource} - إنشاء مورد جديد
- update{Resource} - تحديث مورد موجود
- delete{Resource} - حذف مورد
- {verb}{Resource} - إجراءات محددة (نشر، أرشفة، موافقة، إلخ.)
- {verb}Many{Resources} - عمليات جماعية
- add{Relationship} - إنشاء علاقة
- remove{Relationship} - إزالة علاقة
أفضل ممارسات الطفرات
- استخدم أنواع الإدخال - جمع الوسائط المرتبطة لتنظيم أفضل
- أرجع بيانات ذات معنى - قم بتضمين المورد المعدل وحالة العملية
- تعامل مع الأخطاء بأناقة - أرجع أخطاء منظمة في الحمولة
- سمّ الطفرات بوضوح - استخدم أنماط فعل-اسم متسقة
- تحقق من الإدخال - تحقق من البيانات قبل معالجة الطفرات
- كن متماثلاً عندما يكون ذلك ممكناً - نفس الطفرة مع نفس الإدخال يجب أن يكون له نفس التأثير
- استخدم المعاملات - تأكد من اتساق البيانات مع معاملات قاعدة البيانات
تمرين: صمم طفرات لمنصة مدونة مع المتطلبات التالية:
- إنشاء وتحديث وحذف منشورات المدونة
- المنشورات لها عنوان ومحتوى وحالة (مسودة/منشور) وعلامات
- إضافة وإزالة العلامات من المنشورات
- نشر وإلغاء نشر المنشورات (منفصل عن التحديث)
- إنشاء أنواع إدخال مناسبة لكل طفرة
- إرجاع أنواع حمولة منظمة مع حالة النجاح والرسائل والأخطاء
- استخدام اتفاقيات تسمية متسقة
ملخص
في هذا الدرس، تعلمت كيفية إنشاء طفرات GraphQL لتعديل البيانات. لقد استكشفت أنواع الإدخال واستجابات الطفرة والطفرات المتعددة والتحديثات المتفائلة واتفاقيات التسمية. الطفرات ضرورية لبناء تطبيقات تفاعلية، واتباع أفضل الممارسات يضمن أن واجهة برمجة التطبيقات الخاصة بك قوية وسهلة الاستخدام.