كيفية نشر تطبيق Express وتوسيعه باستخدام إضافة MemCachier من منصة تطبيقات DigitalOcean


كيفية نشر تطبيق Express وتوسيعه باستخدام إضافة MemCachier من منصة تطبيقات DigitalOcean February 24, 2024 at 03:00PM

يُعد إطار Express أحد الإطارات الشائعة لبناء تطبيقات الويب السريعة وواجهات برمجة التطبيقات API باستخدام بيئة التشغيل Node.js.

منصة تطبيقات DigitalOcean عبارة عن منتج منصة على أساس خدمة PaaS لضبط إعدادات التطبيقات ونشرها من مستودع شيفرات برمجية، فهي توفر طريقة سريعة وفعالة لنشر تطبيقات Express.

سنتعلم في دليلنا هذا، كيفية نشر تطبيق Express على منصة تطبيقات DigitalOcean ثم توسيع نطاقه عن طريق إضافة التخزين المؤقت باستخدام إضافة متجر DigitalOcean Marketplace المخصصة لـ MemCachier. إذ يتوافق MemCachier مع نظام memcached للتخزين المؤقت للكائنات ولديه عدة مزايا، مثل تحسين سيناريوهات الفشل باستخدام مجموعة حواسيب عالية التوفر.

سننشئ أولاً تطبيق Express يحسب عددًا أوليًا، ويحتوي على زر "أعجبني"، ويستخدم محرك قوالب Template engine. ستمكنك هذه الميزات من تنفيذ عدة استراتيجيات تخزين مؤقت لاحقًا.

ثم ستدفع شيفرة التطبيق على Git Hub لتنشره بعدها على منصة التطبيقات App Platform. وأخيرًا، ستطبق ثلاث تقنيات تخزين مؤقت للكائنات لتسريع التطبيق وجعله سهل التطوير والتوسع. بنهاية هذا المقال، ستكون قادرًا على نشر تطبيق Express على منصة App Platform، وتطبيق تقنيات التخزين المؤقت لتخزرين العمليات ذات الاستخدام الكثيف للموارد، وتخزين طرق العرض المصيّر rendered views، والجلسات sessions.

المتطلبات الأساسية

لمتابعة خطوات هذا المقال التعليمي، ستحتاج إلى ما يلي:

  • تثبيت بيئة تشغيل Node.js على جهازك العامل بنظام أوبنتو 22.04، ننصحك باتباع خطوات المقال التالي. أما إن كنت تستخدم نظام تشغيل آخر، فعليك اتباع المقال التالي.
  • خادم Express مُثبّت عليه Node.js، ووللقيام بذلك ننصحك بالإطلاع على الخطوتين الأولى والثانية في هذا المقال.
  • حساب غيت هب GitHub و تثبيت غيت Git على جهازك، إذ سننشر التطبيق في حسابك على غيت هب GitHub ثم سننشره على منصة تطبيقات DigitalOcean، اتبع الخطوات الواردة في المقال التالي لتثبيت غيت على جهازك.
  • حساب على DigitalOcean لنشر التطبيق على منصة App Platform، وننبهك إلى أن نشر التطبيقات على هذه المنصة مأجور، لذا اطّلع على أجور منصة App Platform قبل البدء.
  • متصفح إنترنت مثل متصفح كروم Chrome أو فايرفوكس Fire Fox.
  • فهم أساسيات عمل موّلد قوالب Express.

  • فهم آلية عمل البرمجيات الوسيطة middle-ware .

الخطوة الأولى: ضبط قالب عرض مصيّر

سنثبّت في هذه الخطوة موّلد قوالب Express، وننشئ قالبًا للمسار الرئيسي للتطبيق GET/، ثم سنحدّث المسار كي يستخدم هذا القالب. تمَكّننا القوالب من استخدام التخزين المؤقت للعرض المصيّر rendered view، مما يزيد سرعة معالجة الطلب ويقلل استخدام الموارد.

لبدء العمل، انتقل إلى مجلد مشروع خادم Express باستخدام المحرر، سنثبّت محرك قوالب Express لاستخدام القوالب الثابتة في التطبيق، حيث يستبدل محرك القوالب المتغيرات الموجودة في ملف القالب بالقيم ثم يحول القالب إلى ملف HTML، ثم يُرسَل كرد أو استجابة response على طلب request. إذ يؤدي استخدام القوالب إلى تسهيل العمل باستخدام HTML.

والآن، ثبّت مكتبة قوالب جافا سكربت المضمنة ejs، كما يمكنك استخدام أحد محركات القوالب التي يدعمها Express مثل Mustache أو Pug أو Nunjucks.

npm install ejs

بعد تثبيت مكتبة ejs، عليك ضبط إعدادات تطبيق Express كي يستخدمها، وذلك عن طريق فتح الملف server.js في محرر النصوص البرمجية، وإضافة السطر الثالث كما يلي:

server.js
const express = require('express');

const app = express();

app.set('view engine', 'ejs');

app.get('/', (req, res) => {
  res.send('Successful response.');
});
...

يعدّل هذا السطر إعدادات التطبيق ويعين خاصيّة view engine على ejs. ثم احفظ الملف بعد ذلك.

ملاحظة: سنستخدم في هذا المقال الإعداد view engine، كما يمكنك استخدام الإعداد views الذي يدّل تطبيق Express على مكان وجود ملفات القوالب، وقيمته الافتراضية هي views/. والآن، أنشئ مجلد views ثم أنشئ فيه الملف views/index.ejs وافتحه في محرر النصوص البرمجية. وأضف توصيف القالب إلى الملف:

views/index.ejs
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Find the largest prime number</title>
  </head>
  <body>
    <h1>Find the largest prime number</h1>

    <p>
      For any number N, find the largest prime number less than or equal to N.
    </p>
  </body>
</html>

ثم احفظ الملف. بعد أن أنشأت القالب، عليك تعديل المسار كي تتمكن من استخدامه. افتح الملف server.js وأضف ما يلي إليه:

...

app.get('/', (req, res) => {
  res.render('index');
});

...

لاحظ أن دالة رد النداء render تأخذ اسم القالب كوسيط أول، وفي مثالنا يتوافق index مع اسم الملف views/index.ejs. والآن، أعد تشغيل التطبيق لكي تطبق التغيرات التي أجريتها. أوقف عمل الخادم في الطرفية باستخدام المفتاحين Ctrl+C ثم أعد تشغيل الخادم باستخدام الأمر التالي:

node server.js

الآن انتقل إلى localhost:3000 في المتصفح لمشاهدة محتوى القالب، ستحصل على نتيجة مماثلة لما يلي:

frhjvio

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

الخطوة الثانية، إضافة الوظائف إلى تطبيق Express

سنضيف في هذه الخطوة ميّزة العثور على عدد أولي، وميّزة إبداء الإعجاب باستخدام زر "أعجبني Like". سنستخدم هذه الميزات للتفاعل مع التطبيق بعد نشره على منصة App Platform في الخطوة الرابعة من هذا المقال.

العثور على العدد الأولي

سنضيف الآن دالة إلى تطبيقنا وظيفتها العثور على أكبر عدد أولي أقل من أو يساوي N، حيث يشير N إلى عدد ما. سنرسل العدد N عبر استمارة باستخدام التابع GET إلى المسار الرئيسي (/) مع إضافة N كوسيط للاستعلام query، كما يلي:

 localhost:3000/?n=10 

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

أضف نموذجًا كالتالي يحتوي على عنصر إدخال لإدخال N في الملف views/index.ejs:

...

<p>
  For any number N, find the largest prime number less than or equal to N.
</p>

<form action="/" method="get">
  <label>
    N
    <input type="number" name="n" placeholder="e.g. 10" required>
  </label>
  <button>Find Prime</button>
</form>

...

لاحظ أن إجراء النموذج form يُرسَل إلى المسار الرئيسي /، الذي سيُعالَج بواسطة المسار الرئيسي (...'/')get.app في ملف server.js. وبما أن التابع هو get، فستضاف بيانات n إلى عنوان URL كوسيط للاستعلام. بعد ذلك، عند إجراء طلب باستخدام وسيط استعلام n، ستُمَرَر تلك البيانات إلى القالب.

أضف الأسطر التالية بعد التابع get على الملف server.js، ثم احفظ الملف:

...

app.get('/', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.render('index');
    return;
  }

  const locals = { n };
  res.render('index', locals);
});

...

تتحقق الأسطر السابقة من وجود طلب له وسيط استعلام ذو القيمة n، فإذا تحقق ذلك يُصيَّر الملف index مع تمرير القيمة n إليه، وإلا فيُعرض الملف index بدون بيانات.

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

والآن، أصبح بإمكاننا عرض القالب بعد أن أضفنا إليه البيانات. أضف الأسطر التالية على ملف views/index.ejs:

<% if (locals.n) { %>
  <p>N: <%= n %></p>
<% } %>

...

وهكذا يعرض التطبيق المتغيرات المحلية n إن وجدت. احفظ الملف، ثم أعد تشغيل الخادم لتحديث التطبيق. سيُعرَض النموذج مع زر لإيجاد العدد الأولي Find Prime، حيث يأخذ التطبيق المدخلات من المستخدم ويعرضها تحت النموذج:

b28fugz

أدخل عددًا ما ولاحظ أن العنوان URL سيتغير وسيضاف إليه العدد الذي أدخلته، على سبيل المثال، إن أدخلت العدد 40 سيصبح العنوان كالتالي: http://localhost:3000/?n=40 كما سيُعرض العدد الذي أدخلته أسفل النموذج على الشكل التالي N: 40.

yv64q98

والآن بعد أن أصبح بإمكاننا إدخال قيمة للعدد N وعرضها، علينا إضافة دالة للعثور على أكبر عدد أولي أصغر أو مساوي للعدد N، وعرض النتيجة على الشاشة.

أنشئ مجلد utils، ثم أنشئ الملف utils/findPrime.js. افتح الملف findPrime.js في المحرر ثم أضف الكود التالية لتعريف وظيفة أو دالة للعثور على العدد الأولي، ولا تنسَ حفظ الملف بعدها:

utils/findPrime.js
/**
 * Find the largest prime number less than or equal to `n`
 * @param {number} n A positive integer greater than the smallest prime number, 2
 * @returns {number}
 */
module.exports = function (n) {
  let prime = 2; // initialize with the smallest prime number
  for (let i = n; i > 1; i--) {
    let isPrime = true;
    for (let j = 2; j < i; j++) {
      if (i % j == 0) {
        isPrime = false;
        break;
      }
    }
    if (isPrime) {
      prime = i;
      break;
    }
  }
  return prime;
};

يوّثق تعليق JSDoc عمل الدالة، إذ تبدأ الخوارزمية بالعدد الأولي الأول 2، ثم ثم تكرر العمل بدءًا من العدد n وتنقص الرقم بمقدار 1 في كل حلقة. وتستمر الدالة في حلقة التكرار والبحث عن عدد أولي حتى يصبح الرقم 2، وهو أصغر رقم أولي.

تفترض كل حلقة أن العدد الحالي هو عدد أولي، ثم تتحقق من صحة هذا الافتراض، عن طريق التحقق من وجود عامل آخر للعدد الحالي آخر غير 1 ونفسه. إذا أمكن قسمة العدد الحالي على أي رقم أكبر من 1 وأقل من العدد نفسه دون باقي، فهو ليس عددًا أوليًا. ثم ستتحق الدالة بعد ذلك من العدد التالي. بعدها، أضف السطر الثاني إلى ملف server.js لاستيراد دالة العدد الأولي:

const express = require('express');
const findPrime = require('./utils/findPrime');

...

عدّل على المسار الرئيسي لإيجاد العدد الأولى وتمرير قيمته إلى القالب من خلال إضافة السطر التالي إلى ملف server.js، ثم احفظ الملف:

const prime = findPrime(n);

والآن، لعرض النتيجة في القالب وإظهار قيمة N أضف الشيفرة التالية في ملف views/index.ejs:

<% if (locals.n && locals.prime) { %>
  <p>
    The largest prime number less than or equal to <%= n %> is <strong><%= prime %></strong>.
  </p>
<% } %>
...

بدلًا عن:

<% if (locals.n) { %>
  <p>N: <%= n %></p>
<% } %>

ولا تنسَ حفظ الملف، ثم أعد تشغيل الخادم. اختبر عمل الدالة عن طريق إدخال عدد ما، كالعدد 10، ستظهر رسالة:

 The largest prime number less than or equal to 10 is 7 

ومفادها أن العدد الأولي الأعظمي الأصغر أو يساوي العدد 10 هو العدد 7.

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

إضافة زر أعجبني

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

/**
 * Key is `n`
 * Value is the number of 'likes' for `n`
 */
const likesMap = {};

يُستخدم الغرض likesMap كخريطة لتخزين إعجابات الأعداد. المفتاح هو n وقيمه هي عدد إعجابات n. يجب تهيئة إعجابات العدد عند إرساله. أضف السطر الثاني والثالث على ملف server.js لتهيئة إعجابات N:

...

  const prime = findPrime(n);

  // Initialize likes for this number when necessary
  if (!likesMap[n]) likesMap[n] = 0;

  const locals = { n, prime };
  res.render('index', locals);

..

تتحقق عبارة if من وجود إعجابات للعدد الحالي. في حالة عدم وجود إعجابات، يُهيَّئ likesMaps على القيمة 0. ثم أضف الإعجابات كمتغير محلي للعرض، عن طريق تعديل سطر المتغيرات المحلية ليصبح كما يلي ولا تنسَ حفظ الملف:

const locals = { n, prime, likes: likesMap[n] };
  res.render('index', locals);

بعد أن أصبح لدينا بيانات الإعجابات، أصبح بإمكاننا عرض قيمتها وإضافة زر أعجبني. عدّل على ملف views/index.ejs لإضافة توصيف Markup لزر أعجبني كما يلي:

<% if (locals.n && locals.prime) { %>
  <p>
    The largest prime number less than or equal to <%= n %> is <strong><%= prime %></strong>.
  </p>

  <form action="/like" method="get">
    <input type="hidden" name="n" value="<%= n %>">
    <input type="submit" value="Like"> <%= likes %>
  </form>
<% } %>

يجب أن يبدو ملف views/index.ejs على النحو التالي:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Find the largest prime number</title>
  </head>
  <body>
    <h1>Find the largest prime number</h1>

    <p>
      For any number N, find the largest prime number less than or equal to N.
    </p>

    <form action="/" method="get">
      <label>
        N
        <input type="number" name="n" placeholder="e.g. 10" required>
      </label>
      <button>Find Prime</button>
    </form>

    <% if (locals.n && locals.prime) { %>
      <p>
        The largest prime number less than or equal to <%= n %> is <strong><%= prime %></strong>.
      </p>
      <form action="/like" method="get">
        <input type="hidden" name="n" value="<%= n %>">
        <input type="submit" value="Like"> <%= likes %>
      </form>
    <% } %>
  </body>
</html>

لا تنسَ حفظ الملف. ثم أعد تشغيل الخادم. سيظهر على الشاشة زر أعجبني له القيمة 0 بعد ظهور نتيجة العدد الأولي.

likebutton

يؤدي النقر فوق زر "أعجبني" إلى إرسال طلب GET إلى المسار like/، بالقيمة الحالية لـ N كوسيط استعلام عبر إدخال غير مرئي. لكن ستحصل على رسالة خطأ 404 والعبارة Cannot GET /like، لأن التطبيق ليس لديه مسار مطابق بعد. سنضيف الآن المسار اللازم للتعامل مع الطلب، وذلك عبر إضافة الأسطر التالية:

app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  res.redirect(`/?n=${n}`);
});

...

يتحق المسار الجديد من وجود n وفي حال عدم وجودها نعود إلى المسار الرئيسي. أما في حال وجودها، فيزيد التطبيق عدد الإعجابات لهذا العدد. ثم يوجهنا إلى شاشة العرض التي ضغطنا فيها على زر "أعجبني". يجب أن يبدو الملف على النحو التالي:

const express = require('express');
const findPrime = require('./utils/findPrime');

const app = express();

app.set('view engine', 'ejs');

/**
 * Key is `n`
 * Value is the number of 'likes' for `n`
 */
const likesMap = {};

app.get('/', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.render('index');
    return;
  }

  const prime = findPrime(n);

  // Initialize likes for this number when necessary
  if (!likesMap[n]) likesMap[n] = 0;

  const locals = { n, prime, likes: likesMap[n] };
  res.render('index', locals);
});

app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  res.redirect(`/?n=${n}`);
});

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`Example app is listening on port ${port}.`)
);

احفظ الملف، ثم أعد تشغيل التطبيق واختبر زر "أعجبني"، يجب أن يزداد عدد الإعجابات مع كل نقرة.

ملاحظة: يمكنك استخدام تابع POST بدلاً من GET لهذا المسار، والذي يوافق نمط RESTful أكثر لأننا حدثنا أحد الموارد. نستخدم في مقالنا تابع GET بدلاً من التعامل مع طلب POST حتى تتمكن من العمل مع وسطاء استعلام الطلب المألوفة.

أصبح التطبيق مكتملًا الآن ويعمل جيدًا، ويمكنك الاستعداد لنشره على منصة App Platform. في الخطوة التالية، سنُوْدِع الشيفرة في غيت Git، ثم ندفعها إلى مستودع غيت هب GitHub.

الخطوة الثالثة، إنشاء مستودع الشيفرات البرمجية

سننشئ في هذه الخطوة مستودع شيفرات برمجية Code Repository كي نخزّن فيه الملفات اللازمة للنشر. سنُوْدِع commit الشيفرة في غيت Git، ثم ندفعها push أو نضيفها إلى مستودع غيت هب GitHub، الذي سنستخدمه لنشر التطبيق على App platform.

إيداع الشيفرة في جيت

سنُودع الشيفرة في جيت كي تصبح جاهزةً لإضافتها أو دفعها إلى جيت هب.

ملاحظة: إن لم تسجل الدخول إلى حسابك وتضبط الإعدادات، فاحرص على إعداد غيت و مصادقته حسابك على غيت هب باستخدام بروتوكول SSH.

أولًا، عليك تهيئة مستودع غيت:

git init

ثم عليك استبعاد اعتماديات التطبيق dependencies، وذلك بإنشاء ملف اسمه gitignore. وإضافة الشيفرة التالية فيه:

.gitignore
node_modules

# macOS file
.DS_Store

ملاحظة: السطر DS_Store. خاص بالأجهزة التي تعمل باستخدام نظام ماك أو إس mac OS ولا حاجة لإضافة إن كان نظام التشغيل مختلفًا. والآن احفظ الملف.

ثم أضف كل الملفات إلى غيت:

git add .

وأَودِع التغييرات بواسطة الأمر التالي:

git commit -m "Initial commit"

يُستخدم الخيار m- لتحديد رسالة إيداع من اختيارك. والآن بعد إيداع الشيفرة، ستحصل على خرج مماثل لما يلي:

Output
[main (root-commit) deab84e] Initial commit
 6 files changed, 1259 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 package-lock.json
 create mode 100644 package.json
 create mode 100644 server.js
 create mode 100644 utils/findPrime.js
 create mode 100644 views/index.ejs

أي أنك أودعت الشيفرة في غيت، والآن عليك دفعها إلى غيت هب.

دفع الشيفرة إلى مستودع غيت هب

أصبح بإمكانك الآن دفع الشيفرة إلى غيت هب، ثم ربط الشيفرة بمنصة App Platform ونشره. أولاً، سجل الدخول إلى غيت هب من المتصفح وأنشئ مستودعًا جديدًا باسم Express-memcache. ثم أنشئ مستودعًا فارغًا بدون ملفات README أو gitignore. أو ملفات الشهادات. يمكنك جعل المستودع خاصًا أو عامًا. كما يمكنك مراجعة توثيق غيت هب حول كيفية إنشاء مستودع جديد. ثم عد إلى الطرفية، وأضف المستودع الذي أنشأته بمثابة remote origin، وحدّث اسم المستخدم الخاص بك:

git remote add origin https://github.com/your_username/express-memcache.git

يوجه الأمر السابق غيت إلى المكان الذي يجب عليه إضافة الشيفرة فيه. ثم سمّي الفرع الافتراضي فرعًا رئيسيًا main:

git branch -M main

ثم أضف الشيفرة إلى مستودعك:

git push -u origin main

ثم أدخل بياناتك إذا طُلب منك ذلك. وستحصل على خرج مشابه لما يلي:

Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (7/7), done.
Writing objects: 100% (10/10), 9.50 KiB | 9.50 MiB/s, done.
Total 10 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/your_username/express-memcache.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

وبهذا أصبحت شيفرة التطبيق موجودةً على غيت هب، وأصبح التطبيق جاهزًا للنشر على منصة App Platform.

الخطوة الرابعة، نشر التطبيق على منصة App Platform

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

تحديث إعدادات بيئة التطبيق

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

...

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`Example app is listening on port ${port}.`)
);

تنّص هذه الأسطر على استخدام متغير البيئة port في حال وجوده، أو استخدام المنفذ الافتراضي 3000. والآن، أصبح التطبيق قادرًا على القراءة من منفذ بيئة منصة التطبيقات التي سننشره عليها.

إنشاء ونشر التطبيق على منصة App Platform

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

أولاً، سجل الدخول إلى حسابك على DigitalOcean، ثم انقر على إنشاء create من لوحة تحكم التطبيقات Apps Dashboard. كما يمكنك الاطلاع على توثيق كيفية إنشاء التطبيقات في منصة App Platform.

ثم ادخل إلى صفحة Create Resource From Source Code screen، واختر غيت هب كمزود الخدمة Service Provider، وامنح الإذن
لـDigitalOcean للوصول إلى مستودعك. ننصحك بتحديد المستودع الذي تريد نشره فقط. سُتطالب بتثبيت تطبيق DigitalOcean GitHub. حدد مستودعك من القائمة وانقر فوق التالي Next. في شاشة الموارد Resources، انقر على تعديل الشريحة Edit Plan لتحديد حجم الاشتراك.

سنستخدم في مقالنا الاشتراك الأساسي ذو خدمات الويب الأصغر حجمًا (512 ميغابايت من ذاكرة الوصول العشوائي vCPU | RAM) مخصصة لمورد express-memcache. إذ يوفر الاشتراك الأساسي وأصغر خدمة ويب موارد كافيةً لتطبيقنا التدريبي. بعد اختيار الاشتراك، انقر على رجوع Back. ثم انقر على علامة تبويب المعلومات info في شريط التنقل الأيسر وانتبه على المنطقة التطبيق، إذ سنحتاج إلى ذلك في الخطوة التالية لإضافة MemCachier من متجر DigitalOcean. أخيرًا، انقر على نافذة المعانية Review، ثم زر إنشاء موارد Create Resources لإنشاء التطبيق ونشره. سيستغرق الأمر بعض الوقت لإنشاء التطبيق ونشره. ستصلك رسالة عند الانتهاء فيها رابط التطبيق بعد نشره.

بهذا نكون قد أنشأنا تطبيق Express يمكنه العثور على رقم أولي ويحتوي على زر أعجبني. ثم أَودَعنا شيفرة التطبيق إلى غيت ودفعناه إلى غيت هب، ثم نشرنا التطبيق على App Platform. والآن، لجعل تطبيق Express أسرع وأكثر قابلية للتوسع، سوف ننفذ ثلاث استراتيجيات للتخزين المؤقت للكائنات. ستحتاج إلى ذاكرة تخزين مؤقت Cache، والتي سننشئها في الخطوة التالية.

الخطوة الخامسة، إعداد ذاكرة تخزين مؤقت للكائنات باستخدام MemCachier

في هذه الخطوة، سننشئ ونضبط إعدادات ذاكرة تخزين مؤقت للكائنات. يمكنك استخدام أي ذاكرة تخزين مؤقت متوافقة مع memcached. سنستخدم في مقالنا إضافة MemCachier من متجر DigitalOcean. وذاكرة التخزين المؤقت MemCachier هي عبارة عن ذاكرة تخزين مؤقت تخزن البيانات على هيئة قيمة-مفتاح.

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

ملاحظة: لمعرفة اختصارات اسماء المناطق، ننصحك بزيارة مركز بيانات DigitalOcean، فمثلًا منطقة سان فرانسيسكو اختصارها هو sfo3.

والآن، عليك ضبط إعدادات التطبيق لاستخدام الذاكرة. انتقل إلى لوحة تحكم الإضافات Add-Ons dashboard ثم انقر على إضافة MemCachier للانتقال إلى لوحة التحكم. بعدها، انقر على زر عرض Show متغيرات الإعدادات Configuration Variables لعرض القيم التالية MEMCACHIER_USERNAME، MEMCACHIER_PASSWORD، MEMCACHIER_SERVERS. انتبه على هذه القيم لأننا سنحتاجها لاحقًا.

shown

سنحفظ الآن متغيرات إعدادات MemCachier كمتغيرات بيئة للتطبيق. انتقل إلى لوحة تحكم التطبيق ثم انقر على الإعدادات Settings. ثم من Components انقر على express-memc وانتقل لقسم متغيرات البيئة Environment Variables وانقر على تعديل Edit ثم أضف متغيرات إعدادات MemCachier ذات المفاتيح: (MEMCACHIER_USERNAME، MEMCACHIER_PASSWORD، MEMCACHIER_SERVERS) وأضف القيم كل منها وفقًا للقيم الموجودة في لوحة تحكم MemCachier> انقر على خيار التشفير Encrypt بجانب المفتاح MEMCACHIER_PASSWORD لتشفير كلمة المرور، ثم انقر على حفظ Save.

envar

والآن، علينا ضبط عميل ذاكرة memcache client في التطبيق باستخدام المتغيرات التي أدخلنا كي يستطيع التطبيق التخاطب مع الذاكرة. ثبّت المكتبة memjs في الطرفية:

npm install memjs

ثم أنشأ مجلد خدمات services، وأنشأ فيه الملف services/memcache.js وافتحه في المحرر. ثم استورد مكتبة memjs في أعلى الملف واضبط إعدادات عميل الذاكرة cache client:

const { Client } = require('memjs');

module.exports = Client.create(process.env.MEMCACHIER_SERVERS, {
  failover: true,
  timeout: 1,
  keepAlive: true,
});

ولا تنسَ حفظ الملف.

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

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

والآن، أصبحنا جاهزين لتطبيق للتخزين المؤقت في Express، وهو ما سنتعلمه في خطوتنا التالية.

الخطوة السادسة، تطبيق تقنيات التخزين المؤقت في Express باستخدام إضافة MemCachier

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

التخزين المؤقت للعمليات ذات الاستخدام الكثيف للموارد

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

const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');

...

ثم خزّن عددًا أوليًا محسوبًا من قبل في الذاكرة المؤقتة كما يلي:

...

  const prime = findPrime(n);

  const key = 'prime_' + n;

  memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
    if (err) console.log(err);
  });

...

ولا تنسَ حفظ الملف. لاحظ أن التابع set يقبل مفتاحًا key كوسيط أول، وقيمة للسلسلة أي value كوسيطه الثاني. لذا حولنا العدد الأولي إلى سلسلة، أما الشرط الثالث فيحرص على عدم انتهاء مدة العنصر المُخزّن، والشرط الرابع والأخير هو تابع استدعاء اختياري عند حدوث خطأ ما.

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

ملاحظة 2: بإمكان التطبيق الآن العمل محليًا ولكن بدون تخزين مؤقت، وسيظهر خطأ في الخرج عند استدعاء memcache.set لأن التطبيق لن يستمكن من إيجاد خادم تخزين مؤقت، وستحصل حينها على خرج مشابه للتالي:

Output
MemJS: Server <localhost:11211> failed after (2) retries with error - connect ECONNREFUSED 127.0.0.1:11211
Error: No servers available
...

لن تحتاج إلى التخزين المحلي لإكمال باقي خطوات المقال. إذا يمكنك تشغيل localhost:11211 في العنوان الذي يُعد العنوان الافتراضي لـ memjs.

والآن، عليك إيداع التغييرات:

git add . && git commit -m "Add memjs client and cache prime number"

ثم أضف هذه التغييرات إلى غيت هب، والتي ستُنشر إلى منصة التطبيقات تلقائيًا:

git push

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

ملاحظة: قد تظهر رسالة "Waiting for service" على لوحة التحكم، وتعني حالة انتظار الخدمة، وهذه الرسالة ستختفى من تلقاء نفسها، ولكن إن طال ظهورها عليك تحديث التطبيق للتأكد أن التطبيق قد نُشر بعد اكتمال إنشائه.

والآن، عليك الرجوع إلى صفحة إضافات لوحة التحكم ثم النقر على View MemCachier لعرض لوحة تحليلات ذاكرة التخزين المؤقت.

khcukjg

في هذه اللوحة، زدنا الخيار Set Cmds في لوحة All Time Stats وحالة العناصر Items في لوحة التخزين Storage بمقدار 1.
في كل مرة ترسل عددًا، سيزداد كل من Set Cmds وItems. ويجب عليك الضغط على زر التحديث لتحميل الإحصائيات الجديدة.

ملاحظة: يُعد التحقق من سجلات التطبيق على منصة App Platform أمرًا مفيدًا لتصحيح الأخطاء. يمكنك النقر من لوحة تحكم التطبيق على Runtime Logs لعرض الأخطاء.

يمكن الاستفادة من العناصر المخزنة في الذاكرة المؤقتة. ستتحقق الآن مما إذا كان العنصر قد خُزّن مسبقًا، وإذا كان الأمر كذلك، فسوف يُعرض من ذاكرة التخزين المؤقت، وإلا سيبحث التطبيق عن العدد الأولي كما في السابق. عّدل ملف server.js، إذ عليك تعديل بعض الأسطر وإضافة بعد الاسطر الجديدة كما يلي:

...

app.get('/', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.render('index');
    return;
  }

  let prime;

  const key = 'prime_' + n;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // Use the value from the cache
      // Convert Buffer string before converting to number
      prime = parseInt(val.toString());
    } else {
      // No cached value available, find it
      prime = findPrime(n);

      memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
        if (err) console.log(err);
      });
    }

    // Initialize likes for this number when necessary
    if (!likesMap[n]) likesMap[n] = 0;

    const locals = { n, prime, likes: likesMap[n] };
    res.render('index', locals);
  });
});

...

تهيئ الشبفرة السابقة العدد الأولي prime بدون قيمة، باستخدام الكلمة المفتاحية Let، حيث أُعيدَ تعيين قيمتها. ثم يحاول تابع memcache.get استرداد العدد الأولي المخزن. توجد معظم شيفرة المتحكم الآن في تابع الاستدعاء memcache.get لأن نتيجته لازمة لتحديد كيفية التعامل مع الطلب. فإن كان ثمة قيمة مخزنة متاحة، فعليه استخدامها، وإلا عليه إجراء الحسابات اللازمة للعثور على العدد الأولي وتخزين النتيجة في ذاكرة التخزين المؤقت. إن القيمة التي يستدعيها تابع الاستدعاء memcache.get هي قيمة تخزين مؤقتة Buffer، لذا عليك تحويلها إلى سلسلة قبل تحويل التابع prime إلى عدد. أودع التغييرات ثم ادفعها إلى غيت هب لتنشرها:

git add . && git commit -m "Check cache for prime number" && git push

عندما ترسل رقمًا لم يخزّن بعد في التطبيق، ستزداد إحصائيات Set Cmds وحالة العناصر Items وإحصائيات الأخطاء get misses في لوحة تحكم MemCachier بمقدار 1. تحدث حالة الخطأ miss عند محاولة الحصول على العنصر من ذاكرة التخزين المؤقت قبل تعيينه، فالعنصر غير موجود في ذاكرة التخزين المؤقت، مما يؤدي إلى حدوث خطأ، ثم يخزّن العنصر بعد ذلك. عند إدخال عدد مخزّن مسبقًا، سيزداد عدد مرات الحصول على النتائج get hits.

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

تخزين طرق العرض المصيّرة

سنخزّن الآن طرق عرض التطبيق مؤقتًا باستخدام برنامج وسيط. أعددنا في خطوة سابقة ejs كمولد قوالب وأنشأنا قالبًا لعرض كل رقم مُدخل n. قد يستهلك إنشاء العرض كثيرًا من الموارد، لذا فإن تخزينه مؤقتًا يمكن أن يسرع معالجة الطلب ويستخدم موارد أقل.

أولًا، أنشئ مجلدًا للبرنامج الوسيط وسمّه middleware. ثم أنشئ الملف middleware/cacheView.js وافتحه في المحرر. ثم أضف هذه الأسطر في ملف CacheView.js لدالة البرنامج الوسيط:

const memcache = require('../services/memcache');

/**
 * Express middleware to cache views and serve cached views
 */
module.exports = function (req, res, next) {
  const key = `view_${req.url}`;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // Convert Buffer string to send as the response body
      res.send(val.toString());
      return;
    }
  });
};

استوردنا في الشيفرة السابقة عميل الذاكرة memcache. ثم عرفنا عن مفتاح، مثل view_/?n=100. وبعدها، تحققنا من وجود عرض لهذا المفتاح في ذاكرة التخزين المؤقت باستخدام التابع memcache.get. إذا لم يكن هناك خطأ ووُجدت قيمة لهذا المفتاح، ينتهي الطلب عن طريق إرسال العرض مرةً أخرى إلى العميل.

بعد ذلك، إذا لم يكن العرض مخزنًا، فعليك تخزينه مؤقتًا، عن طريق تعديل override التابع res.send بإضافة الأسطر التالية على ملف middleware/cacheView.js:

const originalSend = res.send;
    res.send = function (body) {
      memcache.set(key, body, { expires: 0 }, (err) => {
        if (err) console.log(err);
      });

      originalSend.call(this, body);
    };

يجب أن يصبح محتوى الملف middleware/cacheView.js كالتالي:

const memcache = require('../services/memcache');

/**
 * Express middleware to cache views and serve cached views
 */
module.exports = function (req, res, next) {
  const key = `view_${req.url}`;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // Convert Buffer string to send as the response body
      res.send(val.toString());
      return;
    }
  const originalSend = res.send;
    res.send = function (body) {
      memcache.set(key, body, { expires: 0 }, (err) => {
        if (err) console.log(err);
      });

      originalSend.call(this, body);
    };
  });
};

يمكنك تعديل التابع res.send باستعمال دالة تخزن العرض في ذاكرة التخزين المؤقت قبل استدعاء دالة الإرسال الأساسية send كالمعتاد. إذ يمكنك استدعاء دالة الإرسال الأساسية send باستخدام تابع الاستدعاء call الذي يستخدم this والذي يعبر عن context الحالي بعد تعليمة الذاكرة memcache.set. احرص على استخدام دالة مجهولة (وليس دالة سهمية)، كي تحدد قيمة this الصحيحة. بعد ذلك، مرر التحكم إلى البرنامج الوسيط عن طريق إضافة الأمر next:

...

/**
 * Express middleware to cache views and serve cached views
 */
module.exports = function (req, res, next) {
  const key = `view_${req.url}`;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // Convert Buffer to UTF-8 string to send as the response body
      res.send(val.toString());
      return;
    }

    const originalSend = res.send;
    res.send = function (body) {
      memcache.set(key, body, { expires: 0 }, (err) => {
        if (err) console.log(err);
      });

      originalSend.call(this, body);
    };

    next();
  });
};

...

إن إضافة التابع next يؤدي استدعاء دالة البرنامج الوسيط التالي في التطبيق. لكن لا يوجد في مثالنا برامج وسيط آخر، لذلك يُستدعى المتحكم controller. يوّلد التابع res.render الخاص بـ Express صفحة عرض view، ثم يستدعي التابع res.send داخليًا باستخدام طريقة العرض. والآن، في وحدة تحكم المسار الرئيسي، يُستدعى تابع التعديل override عند استدعاء res.render، ليخزّن العرض في ذاكرة التخزين المؤقت قبل استدعاء دالة send الأصلية لإكمال الرد.

ملاحظة: يمكنك تمرير استدعاء للتابع render في المتحكم، ولكن عليك تكرار شيفرة التخزين المؤقت للعرض في وحدة التحكم لكل مسار يخزن مؤقتًا. والآن، استورد البرنامج الوسيط في الملف server.js:

const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');

...

ثم أضف الوسيط cacheView لاستخدامه في المسار الرئيسي مع التابع / Get:

...

app.get('/', cacheView, (req, res) => {
  ...
});

...

ثم أودع التغييرات وادفعها إلى غيت هب لنشرها:

git add . && git commit -m "Add view caching" && git push

عند إدخال عدد جديد، ستزداد إحصائيات لوحة تحكم MemCachier لكل من Set Cmds وItems وget misses بمقدار اثنين: مرة لحساب العدد الأولي ومرة للعرض. عند تحديث التطبيق بنفس الرقم، فسترى زيادةً بمقدار 1 في get hit لوحة التحكم. لأن صفحة العرض استُرِدت من ذاكرة التخزين المؤقت، لذلك لا توجد حاجة لجلب نتيجة العدد الأولي من التطبيق ملاحظة: إن خيار view cache مفعّل تلقائيًا في الإنتاج، لكن هذا الخيار لا يخزن محتوى خرج القالب، بل يخزن القالب فقط.ويعاد عرض الصفحة مع كل طلب، حتى عندما تكون ذاكرة التخزين المؤقت قيد التشغيل. إذًا فخيار view cache، مختلف ولكنه مكمل للتخزين المؤقت للعرض الذي طبقناه.

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

ثم سنوقف العرض المخزّن عندما يتغير عدد الإعجابات likes وذلك عن طريق حذفه من ذاكرة التخزين المؤقت. عدّل دالة إعادة التوجيه redirect في ملف server.js عن طريق إضافة الأسطر التالية:

likesMap[n]++;

  // The URL of the page being 'liked'
  const url = `/?n=${n}`;

  res.redirect(url);

لتصبح الدالة كما يلي:

...
app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  // The URL of the page being 'liked'
  const url = `/?n=${n}`;

  res.redirect(url);
});

...

بعد تغير عدد الاعجابات في صفحة العرض، ستصبح النسخة المخزنة غير صالحة، لذا أضف الأسطر التالية لحذف عدد الإعجابات من ذاكرة التخزين المؤقت عندما تتغير الإعجابات likes:

...
  const url = `/?n=${n}`;

  // The view for this URL has changed, so the cached version is no longer valid, delete it from the cache.
  const key = `view_${url}`;
  memcache.delete(key, (err) => {
    if (err) console.log(err);
  });

  res.redirect(url);
...

يجب أن يكون ملف server.js مماثلًا لما يلي:

const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');

const app = express();

app.set('view engine', 'ejs');

/**
 * Key is `n`
 * Value is the number of 'likes' for `n`
 */
const likesMap = {};

app.get('/', cacheView, (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.render('index');
    return;
  }

  let prime;

  const key = 'prime_' + n;

  memcache.get(key, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      // Use the value from the cache
      // Convert Buffer string before converting to number
      prime = parseInt(val.toString());
    } else {
      // No cached value available, find it
      prime = findPrime(n);

      memcache.set(key, prime.toString(), { expires: 0 }, (err) => {
        if (err) console.log(err);
      });
    }

    // Initialize likes for this number when necessary
    if (!likesMap[n]) likesMap[n] = 0;

    const locals = { n, prime, likes: likesMap[n] };
    res.render('index', locals);
  });
});

app.get('/like', (req, res) => {
  const n = req.query.n;

  if (!n) {
    res.redirect('/');
    return;
  }

  likesMap[n]++;

  // The URL of the page being 'liked'
  const url = `/?n=${n}`;

  // The view for this URL has changed, so the cached version is no longer valid, delete it from the cache.
  const key = `view_${url}`;
  memcache.delete(key, (err) => {
    if (err) console.log(err);
  });

  res.redirect(url);
});

const port = process.env.PORT || 3000;
app.listen(port, () =>
  console.log(`Example app is listening on port ${port}.`)
);

لا تنسَ حفظ الملف، ثم أودع التغييرات وادفعها للنشر:

git add . && git commit -m "Delete invalid cached view" && git push

والآن سيعمل زر الاعجاب في التطبيق وستتغير الإحصائيات التالية في لوحة تحكم MemCachier عند النقر على الزر:

  • delete hits تزداد عند حذف العرض.
  • get misses تزداد عند حذف العرض وعدم وجوده في ذاكرة التخزين المؤقت.
  • get hits تزداد عند العثور على العدد الأولي في ذاكرة التخزين المؤقت.
  • Set Cmds تزداد عند إضافة العرض إلى ذاكرة التخزين المؤقت.
  • Items تبقى على حالها عند حذف أو إضافة العرض. وهكذا نكون قد طبقنا التخزين المؤقت للعرض وأبطلنا استخدام صفحات العرض المخزنة عند تغييرها. أما التقنية الأخيرة التي سننفذها هي التخزين المؤقت للجلسة.

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

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

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

والآن، ثبّت أداة الجلسات express-session كي تضيف جلسات إلى التطبيق وإلى مكتبة connect-memjs لاستخدام MemCachier كمخزن للجلسات:

npm install express-session connect-memjs

استورد ملف express-session و connect-memjs إلى ملف server.js:

const express = require('express');
const findPrime = require('./utils/findPrime');
const memcache = require('./services/memcache');
const cacheView = require('./middleware/cacheView');
const session = require('express-session');
const MemcacheStore = require('connect-memjs')(session);

...

تُمرَر جلسة البرنامج الوسيط يتم تمرير البرنامج الوسيط للجلسة إلى وحدة الاتصال connect في memcached، مما يسمح لها بالوراثة من Express.session.Store.

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

...

app.set('view engine', 'ejs');

app.use(
  session({
    secret: 'your-session-secret',
    resave: false,
    saveUninitialized: true,
    store: new MemcacheStore({
      servers: [process.env.MEMCACHIER_SERVERS],
      prefix: 'session_',
    }),
  })
);

...

يستخدم secret لتسجيل ملفات تعريف الارتباط للجلسة أو ما يُعرف باسم cookie. لا تنسَ استبدال your-session-secret بسلسلة مميزة. ملاحظة: يجب استخدام متغيرات البيئة لضبط secret لمراحل الإنتاج، وذلك بإضافة السطر

secret: process.env.SESSION_SECRET || 'your-session-secret'

لكن عندها يجب ضبط متغير البيئة في لوحة تحكم App Platform. يجبر الأمر Resave الجلسة على إعادة الحفظ إذا تُعدَل أثناء الطلب. ولأننا لا نريد تخزين العنصر في الذاكرة التخزين المؤقت مرة أخرى، لذا علينا ضبط قيمته على false. saveUninitialized: false is useful when you only want to save modified sessions, as is often the case with login sessions where a user property might be added to the session after authentication. In this case, you will store all sessions indiscriminately, so you set it to true. يُعد الأمر saveUninitialized: false مفيدًا لحفظ الجلسات المعدلة فقط، كما هو الحال غالبًا مع جلسات تسجيل الدخول، حيث يمكن إضافة خاصية مستخدم إلى الجلسة بعد المصادقة، حينها سنخزّن جميع الجلسات بصورة عشوائية، لذلك سنضبطها على القيمة true.

أخيرًا، اربط قيمة store مع ذاكرة التخزين المؤقتة، واضبط بادئة ذاكرة التخزين المؤقت للجلسة على ._session وهذا يعني أن مفتاح عنصر الجلسة في الذاكرة سيبدو كالتالي: session_<session ID>.

بعدها، أضف برامجًا وسيطًا لتصحيح الأخطاء على مستوى التطبيق، والتي ستساعد في تحديد الجلسات المخزنة قيد التنفيذ:

...

app.use(
  session({
    ...
  })
);

/**
 * Session sanity check middleware
 */
app.use(function (req, res, next) {
  console.log('Session ID:', req.session.id);

  // Get the item from the cache
  memcache.get(`session_${req.session.id}`, (err, val) => {
    if (err) console.log(err);

    if (val !== null) {
      console.log('Session from cache:', val.toString());
    }
  });

  next();
});

...

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

git add . && git commit -m "Add session caching" && git push

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

الخطوة السابعة، حذف الموارد (اختيارية)

ثمة رسوم عليك دفعها عند نشر التطبيق على منصة App platform، لذا يمكنك حذف التطبيق وإضافة MemCachier عند الانتهاء. انقر على الإجراءات Actions من لوحة التحكم ثم انقر على حذف التطبيق Destroy App. لحذف إضافة MemCachier، انقر على الإضافات Add-Ons، ثم على MemCachier، وانقر بعدها على الإعدادات والحذف Settings and Destroy. تلغى ذاكرة MemCachier المجانية بعد 30 يومًا من عدم استخدامها، ولكن يُعد تنظيف الادوات أمرًا جيدًا.

ختامًا

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

يمكنك الاطلاع على ملفات هذا المقال في مستودع مجتمع DigitalOcean، تحتوي المفاتيح في كل استراتيجيات التخزين المؤقت التي نفذناها على بادئة: _prime و  _view و _session prime_ و view_ وsession_. بالإضافة إلى ميزة فضاء الاسم، توفر البادئة فائدةً إضافيةً وهي السماح بتحديد أداء ذاكرة التخزين المؤقت.

استخدمنا في مقالنا شريحة المطورين developer plan في MemCachier، ولكن يمكنك تجربة شريحة مُدارة بالكامل تأتي مع مجموعة ميزات االفحص الدقيق Introspection، مما يتيح لك تتبع أداء البادئات الفردية. على سبيل المثال، يمكنك مراقبة معدل النقر hit rate أو نسبة النقر hit ratio على أي بادئة، مما يوفر رؤية تفصيلية لأداء ذاكرة التخزين المؤقت. إن أردت متابعة استخدام MemCachier، يمكنك مراجعة التوثيقات التالية.

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

ترجمة -وبتصرف- [للمقال ](How To Deploy an Express Application and Scale with MemCachier on DigitalOcean App Platform) من موقع DigitalOcean لكاتبيه Patrick O'Hanlon و Caitlin Postal.

اقرأ أيضًا

#ArabProgrammers #المبرمجون_العرب #arab_programmers #3qpa7meed #عقبة_البرق #5

تعليقات