عقلية عامل الخدمات

طريقة التفكير في عمال الخدمة.

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

ولكن عاملي الخدمة يختلفون عن أي شيء اعتاد عليه معظم مطوّري برامج الويب. وتتطلب هذه الأدوات فترة تعلم طويلة وبعض الخطوات التي يجب الانتباه إليها.

تعاونتُ مؤخرًا مع فريق Google Developers في مشروع Service Workies، وهي لعبة مجانية لفهم مشغّلات الخدمات. أثناء إنشاء التطبيق والعمل على التفاصيل المعقدة لخدمة workers، واجهت بعض المشاكل. ما ساعدني كثيرًا هو ابتكار عدد من الاستعارات التصويرية. في هذه المشاركة، سنستكشف هذه النماذج الذهنية ونحاول فهم السمات المتناقضة التي تجعل موظفي الخدمات صعبين ومذهلين في الوقت نفسه.

الإجراء نفسه، ولكن بشكل مختلف

أثناء كتابة رمز عامل الخدمة، ستلاحظ أنّ العديد من العناصر مألوفة لك. يمكنك استخدام ميزات لغة JavaScript الجديدة المفضّلة لديك. يمكنك الاستماع إلى أحداث دورة النشاط تمامًا كما هو الحال مع أحداث واجهة المستخدم. يمكنك إدارة تدفّق التحكّم باستخدام الوعود كما اعتدت.

ولكن سلوك عمال الخدمة الآخرين يثير حيرتك. خاصةً عند إعادة تحميل الصفحة وعدم رؤية التغييرات التي أجريتها على الرمز.

طبقة جديدة

عند إنشاء موقع إلكتروني، عادةً ما يكون لديك طبقتان فقط للتفكير فيهما: العميل والخادم. ويكون "عامل الخدمة" طبقة جديدة تمامًا تقع في الوسط.

يعمل موظّف الخدمة كطبقة وسيطة بين العميل والخادم.

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

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

في لعبة Service Workies، نتناول التفاصيل العديدة لدورة حياة الخدمة وسنقدّم لك الكثير من التمارين للتعرّف على كيفية استخدامها.

فعّالة، ولكنّها محدودة

يمنحك وجود عامل خدمة على موقعك فوائد مذهلة. يمكن لموقعك الإلكتروني إجراء ما يلي:

على الرغم من الإجراءات الكثيرة التي يمكن أن يتّخذها موظّفو الخدمة، إلا أنّهم محدودون من حيث التصميم. ولا يمكن تنفيذ أي إجراء بشكل متزامن أو في سلسلة المحادثات نفسها التي يعمل بها موقعك الإلكتروني. وهذا يعني عدم إمكانية الوصول إلى ما يلي:

  • localStorage
  • نموذج DOM
  • النافذة

والخبر السارّ هو أنّ هناك عددًا من الطرق التي يمكن لصفحتك من خلالها التواصل مع عامل الخدمة، بما في ذلك postMessage المباشر وقنوات الرسائل بين شخصَين وقنوات البث بين شخص والعديد من الأشخاص.

طويلة الأجل، ولكنها قصيرة الأجل

يستمر تشغيل Worker نشط في الخدمة حتى بعد مغادرة المستخدِم لموقعك الإلكتروني أو إغلاق علامة التبويب. يحافظ المتصفّح على مشغّل الخدمة هذا لكي يكون جاهزًا في المرة التالية التي يعود فيها المستخدم إلى موقعك الإلكتروني. قبل تقديم الطلب الأول، يحصل مشغّل الخدمة على فرصة اعتراضه والتحكم في الصفحة. وهذا ما يسمح لموقع إلكتروني بالعمل بلا اتصال بالإنترنت، إذ يمكن لمشغّل الخدمة عرض نسخة مخزّنة مؤقتًا من الصفحة نفسها، حتى إذا لم يكن لدى المستخدم اتصال بالإنترنت.

في Service Workies، نوضّح هذا المفهوم من خلال شخصية "كولو" (عامل خدمة ودود) الذي يعترض الطلبات ويعالجها.

متوقفة

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

waitUntil

بسبب احتمالية إيقافه باستمرار، يحتاج العامل في الخدمة إلى طريقة لإعلام المتصفّح عندما يكون في وضع تنفيذ مهمة مهمة ولا يريد أخذ قيلولة. هنا يأتي دور event.waitUntil(). وتؤدي هذه الطريقة إلى إطالة دورة الحياة التي يتم استخدامها فيها، ما يمنع إيقافها والانتقال إلى المرحلة التالية من دورة حياتها إلى أن نصبح جاهزين. يمنحنا ذلك الوقت اللازم لإعداد ذاكرات التخزين المؤقت، وجلب الموارد من الشبكة، وما إلى ذلك.

يُعلم هذا المثال المتصفّح بأنّه لم تكتمل عملية تثبيت الخدمة إلى أن يتم إنشاء ذاكرة التخزين المؤقت assets وملؤها بصورة سيف:

self.addEventListener("install", event => {
  event.waitUntil(
    caches.open("assets").then(cache => {
      return cache.addAll(["/weapons/sword/blade.png"]);
    })
  );
});

انتبه إلى الحالة الشاملة

عند حدوث عملية البدء/الإيقاف هذه، تتم إعادة ضبط النطاق الشامل لعامل الخدمة. لذا، احرِص على عدم استخدام أي حالة عامة في worker الخدمة، وإلا ستصاب بخيبة أمل في المرة التالية التي يتم فيها إعادة تشغيله وتكون حالته مختلفة عن الحالة المتوقّعة.

إليك مثال يستخدم حالة عامة:

const favoriteNumber = Math.random();
let hasHandledARequest = false;

self.addEventListener("fetch", event => {
  console.log(favoriteNumber);
  console.log(hasHandledARequest);
  hasHandledARequest = true;
});

عند كل طلب، سيسجِّل عامل الخدمة هذا رقمًا، لنفترض أنّه ‎0.13981866382421893. يتغيّر المتغيّر hasHandledARequest أيضًا إلى true. يتوقف الآن عامل الخدمة عن العمل لبعض الوقت، لذا يوقفه المتصفّح. في المرة التالية التي يتم فيها تقديم طلب، يكون مشغّل الخدمة مطلوبًا مرة أخرى، لذا يوقظه المتصفّح. يتم تقييم النص البرمجي مرة أخرى. تم الآن إعادة ضبط hasHandledARequest على false، وأصبح favoriteNumber شيئًا مختلفًا تمامًا، وهو 0.5907281835659033.

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

في الفصل 3 من "مشغّلو الخدمات"، نعرض مشغّل الخدمة المتوقف على أنّه يفقد كل الألوان أثناء انتظار إعادة تشغيله.

صورة توضيحية لمشغّل خدمة متوقف

معًا، ولكن منفصلة

لا يمكن التحكّم في صفحتك إلا من خلال مشغّل خدمات واحد في كل مرة. ولكن يمكن أن يكون لديه عاملان للخدمة مثبَّتان في الوقت نفسه. عند إجراء تغيير على رمز عامل الخدمة وإعادة تحميل الصفحة، لا يتم تعديل عامل الخدمة على الإطلاق. ملفات تشغيل الخدمات غير قابلة للتغيير. بدلاً من ذلك، يتم إنشاء حساب جديد تمامًا. سيتم تثبيت عامل الخدمة الجديد هذا (لنسمّيه SW2) ولكن لن يتم تفعيله بعد. يجب أن ينتظر حتى يتم إنهاء عامل الخدمة الحالي (SW1) (عندما يغادر المستخدم موقعك الإلكتروني).

العبث بمخازن مؤقتة لمشغّل خدمة آخر

أثناء التثبيت، يمكن أن يبدأ SW2 عملية الإعداد، والتي تشمل عادةً إنشاء ذاكرات التخزين المؤقت وملؤها. يُرجى العِلم أنّ عامل الخدمة الجديد هذا يمكنه الوصول إلى كل ما يمكن لعامل الخدمة الحالي الوصول إليه. إذا لم تكن حذرًا، يمكن أن يؤدي مشغّل الخدمة الجديد في وضع الانتظار إلى إفساد الأمور لمشغّل الخدمة الحالي. في ما يلي بعض الأمثلة التي قد تؤدي إلى حدوث مشاكل:

  • يمكن أن يحذف SW2 ذاكرة التخزين المؤقت التي يستخدمها SW1 بشكل نشط.
  • قد تعدِّل SW2 محتوى ذاكرة التخزين المؤقت التي تستخدمها SW1، ما يؤدي إلى استجابة SW1 بمواد عرض لا تتوقعها الصفحة.

تخطّي skipWaiting

يمكن لمشغّل الخدمات أيضًا استخدام طريقة skipWaiting() الخطيرة للتحكّم في الصفحة فور انتهاء عملية التثبيت. لا يُنصح بتنفيذ ذلك بشكل عام ما لم تكن تحاول استبدال عامل خدمة يتضمّن أخطاء عن قصد. قد يستخدم العامل الجديد للخدمات موارد معدَّلة لا تتوقعها الصفحة الحالية، ما يؤدي إلى ظهور أخطاء وعيوب.

البدء بصفحة نظيفة

لمنع عمال الخدمة من التداخل مع بعضهم، تأكَّد من أنّهم يستخدمون ذاكرات تخزين مؤقت مختلفة. إنّ أسهل طريقة لتحقيق ذلك هي إضافة إصدارات إلى أسماء ذاكرة التخزين المؤقت التي يستخدمونها.

const version = 1;
const assetCacheName = `assets-${version}`;

self.addEventListener("install", event => {
  caches.open(assetCacheName).then(cache => {
    // confidently do stuff with your very own cache
  });
});

عند نشر مشغّل خدمة جديد، ستحتاج إلى ترقية version لكي ينفِّذ ما يحتاجه باستخدام ذاكرة تخزين مؤقت منفصلة تمامًا عن مشغّل الخدمة السابق.

تصور لذاكرة التخزين المؤقت

تنظيف نهاية الفيديو

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

طريقة caches.match() هي اختصار يُستخدَم غالبًا لاسترداد عنصر من أي ذاكرة تخزين مؤقت حيث تتطابق القيمة. لكنه يتكرر من خلال ذاكرات التخزين المؤقت بالترتيب الذي تم إنشاؤه به. لنفترض أنّ لديك نسختَين من ملف نص برمجي app.js في ذاكرتَي تخزين مؤقت مختلفتَين، assets-1 وassets-2. تتوقع صفحتك النص البرمجي الأحدث المخزّن في assets-2. ولكن إذا لم تحذف ذاكرة التخزين المؤقت القديمة، سيعرض محرّك بحث caches.match('app.js') الإصدار القديم من assets-1، ومن المرجّح أن يؤدي ذلك إلى تعطُّل موقعك الإلكتروني.

كل ما عليك فعله لتنظيف الخدمة السابقة هو حذف أي ذاكرة تخزين مؤقت لا يحتاج إليها العامل الجديد للخدمة:

const version = 2;
const assetCacheName = `assets-${version}`;

self.addEventListener("activate", event => {
  event.waitUntil(
    caches.keys().then(cacheNames => {
      return Promise.all(
        cacheNames.map(cacheName => {
          if (cacheName !== assetCacheName){
            return caches.delete(cacheName);
          }
        });
      );
    });
  );
});

يتطلب منع موظفي الخدمة من ارتباك بعضهم بعضًا من العمل والانضباط، ولكنه يستحق العناء.

عقلية مشغِّل الخدمات

سيساعدك الاستعداد الذهني المناسب عند التفكير في موظفي الخدمة على بناء أسلوبك بثقة. وبعد التعرف عليها، ستتمكن من تقديم تجارب مذهلة للمستخدمين.

إذا أردت فهم كل هذا من خلال ممارسة لعبة، إليك بعض المعلومات المفيدة. يمكنك تشغيل لعبة Service Workies للتعرّف على طرق استخدام مهام الخدمة من أجل القضاء على الوحوش التي تعمل بلا إنترنت.