واجهات GraphQL

استراتيجيات التخزين المؤقت في GraphQL

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

مقدمة في التخزين المؤقت لـ GraphQL

يعتبر التخزين المؤقت أمرًا حاسمًا لتحسين أداء تطبيقات GraphQL. على عكس واجهات برمجة التطبيقات REST ذات عناوين URL القابلة للتنبؤ، يتطلب هيكل استعلام GraphQL المرن استراتيجيات تخزين مؤقت متطورة. يوفر Apollo Client إمكانيات تخزين مؤقت قوية مدمجة يمكنها تحسين أداء التطبيق بشكل كبير.

ذاكرة التخزين المؤقت في Apollo Client

يقوم تطبيق التخزين المؤقت الافتراضي بتطبيع البيانات وتخزينها في جدول بحث مسطح:

import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ uri: 'https://api.example.com/graphql', cache: new InMemoryCache({ typePolicies: { Query: { fields: { posts: { merge(existing = [], incoming) { return [...existing, ...incoming]; } } } } } }) });

تطبيع التخزين المؤقت

يقوم Apollo Client تلقائيًا بتطبيع بياناتك عن طريق تقسيم نتائج الاستعلام إلى كائنات فردية وتخزينها في هيكل مسطح:

// استجابة الاستعلام { user: { id: '1', name: 'John Doe', posts: [ { id: '10', title: 'GraphQL Basics', author: { id: '1', name: 'John Doe' } } ] } } // التخزين المؤقت المطبع { 'User:1': { id: '1', name: 'John Doe', posts: [{ __ref: 'Post:10' }] }, 'Post:10': { id: '10', title: 'GraphQL Basics', author: { __ref: 'User:1' } } }
ملاحظة: يتطلب تطبيع التخزين المؤقت أن تحتوي الكائنات على حقل id أو _id، أو يجب عليك تعريف تكوين keyFields مخصص.

سياسات التخزين المؤقت

تحكم في كيفية جلب البيانات وتخزينها مؤقتًا باستخدام سياسات الجلب:

import { useQuery, gql } from '@apollo/client'; const GET_USER = gql` query GetUser($id: ID!) { user(id: $id) { id name email } } `; // cache-first (افتراضي) - استخدم التخزين المؤقت، والعودة إلى الشبكة const { data } = useQuery(GET_USER, { variables: { id: '1' }, fetchPolicy: 'cache-first' }); // network-only - احصل دائمًا على بيانات جديدة const { data } = useQuery(GET_USER, { variables: { id: '1' }, fetchPolicy: 'network-only' }); // cache-only - لا تقم بطلبات شبكة أبدًا const { data } = useQuery(GET_USER, { variables: { id: '1' }, fetchPolicy: 'cache-only' }); // cache-and-network - أرجع البيانات المخزنة مؤقتًا أولاً، ثم قم بالتحديث باستجابة الشبكة const { data } = useQuery(GET_USER, { variables: { id: '1' }, fetchPolicy: 'cache-and-network' }); // no-cache - تجاوز التخزين المؤقت تمامًا const { data } = useQuery(GET_USER, { variables: { id: '1' }, fetchPolicy: 'no-cache' });

سياسات الأنواع

حدد سلوكيات مخصصة لأنواع محددة في المخطط الخاص بك:

const cache = new InMemoryCache({ typePolicies: { User: { keyFields: ['username'], // استخدم اسم المستخدم بدلاً من id fields: { fullName: { read(_, { readField }) { const firstName = readField('firstName'); const lastName = readField('lastName'); return `${firstName} ${lastName}`; } } } }, Post: { fields: { comments: { merge(existing = [], incoming, { args }) { if (args?.offset === 0) { return incoming; // استبدل إذا كان offset يساوي 0 } return [...existing, ...incoming]; // ألحق خلاف ذلك } } } } } });

سياسات الحقول

تحكم في سلوك القراءة والدمج للحقول الفردية:

const cache = new InMemoryCache({ typePolicies: { Query: { fields: { feed: { // حدد كيفية إنشاء مفاتيح التخزين المؤقت لهذا الحقل keyArgs: ['filter', 'sortBy'], // حدد كيفية دمج النتائج المقسمة إلى صفحات merge(existing = { items: [] }, incoming, { args }) { const { offset = 0 } = args; const merged = existing.items.slice(0); for (let i = 0; i < incoming.items.length; i++) { merged[offset + i] = incoming.items[i]; } return { ...incoming, items: merged }; }, // حدد كيفية قراءة الحقل من التخزين المؤقت read(existing, { args }) { if (!existing) return undefined; const { offset = 0, limit = 10 } = args; return { ...existing, items: existing.items.slice(offset, offset + limit) }; } } } } } });
نصيحة: استخدم keyArgs للتحكم في الوسائط التي تؤثر على مفاتيح التخزين المؤقت. على سبيل المثال، keyArgs: ['filter'] يعني أن قيم الفلتر المختلفة تنشئ إدخالات تخزين مؤقت منفصلة، لكن وسائط الصفحات مثل offset و limit لا تفعل ذلك.

إعادة توجيه التخزين المؤقت

أعد توجيه الاستعلامات إلى البيانات المخزنة مؤقتًا الموجودة لتجنب طلبات الشبكة المكررة:

const cache = new InMemoryCache({ typePolicies: { Query: { fields: { // أعد توجيه استعلام مستخدم واحد إلى المستخدم المخزن مؤقتًا من قائمة المستخدمين user: { read(_, { args, toReference }) { return toReference({ __typename: 'User', id: args.id }); } } } } } }); // إذا كان User:1 مخزنًا مؤقتًا بالفعل من استعلام users، // سيستخدم هذا الاستعلام البيانات المخزنة مؤقتًا دون إجراء طلب شبكة const { data } = useQuery(gql` query GetUser { user(id: "1") { id name } } `);

الاستعلامات الدائمة

أرسل تجزئات الاستعلام بدلاً من سلاسل الاستعلام الكاملة لتقليل النطاق الترددي وتحسين الأمان:

import { createPersistedQueryLink } from '@apollo/client/link/persisted-queries'; import { createHttpLink } from '@apollo/client'; import { sha256 } from 'crypto-hash'; const httpLink = createHttpLink({ uri: 'https://api.example.com/graphql' }); const persistedQueriesLink = createPersistedQueryLink({ sha256, useGETForHashedQueries: true // استخدم طلبات GET للاستعلامات الدائمة }); const client = new ApolloClient({ link: persistedQueriesLink.concat(httpLink), cache: new InMemoryCache() }); // الطلب الأول يرسل الاستعلام الكامل + التجزئة // الطلبات اللاحقة ترسل التجزئة فقط
ملاحظة: تتطلب الاستعلامات الدائمة دعم الخادم. يجب أن يكون الخادم قادرًا على تخزين واسترجاع الاستعلامات من خلال تجزئات SHA-256 الخاصة بها.

معالجة التخزين المؤقت

اقرأ مباشرة من واكتب إلى التخزين المؤقت:

import { gql } from '@apollo/client'; // اقرأ من التخزين المؤقت const cachedUser = client.readQuery({ query: gql` query GetUser($id: ID!) { user(id: $id) { id name } } `, variables: { id: '1' } }); // اكتب إلى التخزين المؤقت client.writeQuery({ query: gql` query GetUser($id: ID!) { user(id: $id) { id name } } `, variables: { id: '1' }, data: { user: { __typename: 'User', id: '1', name: 'Jane Doe' } } }); // حدث جزء مخزن مؤقتًا client.writeFragment({ id: 'User:1', fragment: gql` fragment UpdateUser on User { name } `, data: { name: 'Jane Updated' } }); // احذف من التخزين المؤقت client.cache.evict({ id: 'User:1' }); client.cache.gc(); // جمع القمامة للكائنات غير المشار إليها
تحذير: يمكن أن تؤدي معالجة التخزين المؤقت اليدوية إلى عدم الاتساق. تأكد دائمًا من أن بياناتك المخزنة مؤقتًا تطابق المخطط الخاص بك وتتضمن الحقول المطلوبة مثل __typename.

استمرارية التخزين المؤقت

احتفظ بالتخزين المؤقت في localStorage للدعم دون اتصال:

import { InMemoryCache } from '@apollo/client'; import { persistCache } from 'apollo3-cache-persist'; const cache = new InMemoryCache(); async function setupApollo() { await persistCache({ cache, storage: window.localStorage, maxSize: 1048576, // 1 ميجابايت debug: true }); const client = new ApolloClient({ uri: 'https://api.example.com/graphql', cache }); return client; } setupApollo().then(client => { // العميل جاهز مع التخزين المؤقت الدائم });
تمرين: أنشئ عميل GraphQL مع تكوين تخزين مؤقت مخصص يقوم بـ:
  1. استخدام اسم المستخدم كحقل مفتاح لنوع User
  2. تنفيذ دمج الترقيم لموجز المنشورات
  3. إنشاء إعادة توجيه التخزين المؤقت من استعلام منشور واحد إلى قائمة المنشورات
  4. الاحتفاظ بالتخزين المؤقت في localStorage
اختبر عن طريق جلب المنشورات، ثم جلب منشور واحد والتحقق من عدم إجراء طلب شبكة.