إنشاء تطبيقات أمامية بدون خادم باستخدام Google Cloud Platform
نشرت: 2022-03-10في الآونة الأخيرة ، بدأ نموذج تطوير التطبيقات في التحول من الاضطرار يدويًا إلى نشر الموارد المستخدمة في أحد التطبيقات وتوسيع نطاقها وتحديثها إلى الاعتماد على موفري الخدمات السحابية من الأطراف الثالثة للقيام بمعظم إدارة هذه الموارد.
بصفتك مطورًا أو مؤسسة ترغب في إنشاء تطبيق مناسب للسوق في أسرع وقت ممكن ، فقد ينصب تركيزك الأساسي على تقديم خدمة التطبيق الأساسية للمستخدمين بينما تقضي وقتًا أقل في التكوين والنشر واختبار الضغط تطبيقك. إذا كانت هذه هي حالة الاستخدام الخاصة بك ، فإن التعامل مع منطق الأعمال الخاص بالتطبيق الخاص بك بطريقة بدون خادم قد يكون خيارك الأفضل. ولكن كيف؟
هذه المقالة مفيدة لمهندسي الواجهة الأمامية الذين يرغبون في بناء وظائف معينة داخل تطبيقاتهم أو مهندسي النهاية الخلفية الذين يرغبون في استخراج وظيفة معينة والتعامل معها من خدمة خلفية حالية باستخدام تطبيق بدون خادم تم نشره على Google Cloud Platform.
ملاحظة : للاستفادة مما سيتم تغطيته هنا ، يجب أن تكون لديك خبرة في العمل مع React. لا يلزم خبرة سابقة في التطبيقات بدون خادم.
قبل أن نبدأ ، دعنا نفهم ماهية التطبيقات التي لا تحتاج إلى خادم وكيف يمكن استخدام بنية بدون خادم عند إنشاء تطبيق في سياق مهندس الواجهة الأمامية.
تطبيقات بدون خادم
التطبيقات التي لا تحتوي على خادم هي تطبيقات مقسمة إلى وظائف صغيرة قابلة لإعادة الاستخدام تعتمد على الأحداث ، ويستضيفها ويديرها موفرو الخدمات السحابية من جهات خارجية داخل السحابة العامة نيابة عن مؤلف التطبيق. يتم تشغيلها بواسطة أحداث معينة ويتم تنفيذها عند الطلب. على الرغم من أن اللاحقة " less " المرفقة بالكلمة بدون خادم تشير إلى عدم وجود خادم ، فإن هذا ليس هو الحال بنسبة 100٪. لا تزال هذه التطبيقات تعمل على الخوادم وموارد الأجهزة الأخرى ، ولكن في هذه الحالة ، لا يتم توفير هذه الموارد من قبل المطور بل من قبل موفر خدمة سحابية تابع لجهة خارجية. لذا فهي خوادم - أقل لمؤلف التطبيق ولكنها لا تزال تعمل على الخوادم ويمكن الوصول إليها عبر الإنترنت العام.
من الأمثلة على حالة استخدام تطبيق بدون خادم إرسال رسائل بريد إلكتروني إلى المستخدمين المحتملين الذين يزورون صفحتك المقصودة ويشتركون في تلقي رسائل البريد الإلكتروني الخاصة بإطلاق المنتج. في هذه المرحلة ، ربما لا يكون لديك خدمة خلفية قيد التشغيل ولن ترغب في التضحية بالوقت والموارد اللازمة لإنشاء واحدة ونشرها وإدارتها ، كل ذلك لأنك بحاجة إلى إرسال رسائل بريد إلكتروني. هنا ، يمكنك كتابة ملف واحد يستخدم عميل بريد إلكتروني ونشره إلى أي مزود خدمة سحابية يدعم تطبيق بدون خادم والسماح لهم بإدارة هذا التطبيق نيابة عنك أثناء توصيل هذا التطبيق بدون خادم بصفحتك المقصودة.
في حين أن هناك الكثير من الأسباب التي تجعلك تفكر في الاستفادة من التطبيقات التي لا تحتوي على خادم أو الوظائف كخدمة (FAAS) كما يطلق عليها ، بالنسبة لتطبيق الواجهة الأمامية ، فإليك بعض الأسباب البارزة التي يجب أن تضعها في الاعتبار:
- تحجيم تلقائي للتطبيق
يتم قياس التطبيقات التي لا تحتوي على خادم أفقيًا ويتم إجراء هذا " التوسع " تلقائيًا بواسطة موفر السحابة استنادًا إلى مقدار الاستدعاءات ، لذلك لا يتعين على المطور إضافة الموارد أو إزالتها يدويًا عندما يكون التطبيق تحت عبء ثقيل. - الفعالية من حيث التكلفة
نظرًا لكونها مدفوعة بالأحداث ، لا يتم تشغيل التطبيقات التي لا تحتاج إلى خادم إلا عند الحاجة ، وهذا ينعكس على الرسوم حيث يتم إصدار فواتيرها بناءً على عدد الوقت الذي تم استدعاؤه. - المرونة
تم تصميم التطبيقات التي لا تحتوي على خادم لتكون قابلة لإعادة الاستخدام بدرجة كبيرة وهذا يعني أنها ليست مرتبطة بمشروع أو تطبيق واحد. يمكن استخراج وظيفة معينة في تطبيق بدون خادم ، ونشرها واستخدامها عبر مشاريع أو تطبيقات متعددة. يمكن أيضًا كتابة التطبيقات بدون خادم باللغة المفضلة لمؤلف التطبيق ، على الرغم من أن بعض موفري السحابة يدعمون فقط عددًا أقل من اللغات.
عند استخدام التطبيقات بدون خادم ، يمتلك كل مطور مجموعة كبيرة من موفري السحابة داخل السحابة العامة للاستفادة منها. في سياق هذه المقالة سنركز على التطبيقات بدون خادم على Google Cloud Platform - كيف يتم إنشاؤها وإدارتها ونشرها وكيف تتكامل أيضًا مع المنتجات الأخرى على Google Cloud. للقيام بذلك ، سنضيف وظائف جديدة إلى تطبيق React الحالي أثناء العمل من خلال عملية:
- تخزين واسترداد بيانات المستخدم على السحابة ؛
- إنشاء وإدارة وظائف cron على Google Cloud ؛
- نشر وظائف السحابة على جوجل كلاود.
ملاحظة : لا ترتبط التطبيقات التي لا تحتوي على خادم بـ React فقط ، طالما أن إطار العمل أو المكتبة الأمامية المفضلة لديك يمكنها تقديم طلب HTTP
، فيمكنها استخدام تطبيق بدون خادم.
وظائف جوجل كلاود
تتيح Google Cloud للمطورين إنشاء تطبيقات بدون خادم باستخدام وظائف السحابة وتشغيلها باستخدام Functions Framework. كما يطلق عليها ، فإن وظائف السحابة عبارة عن وظائف قائمة على الأحداث قابلة لإعادة الاستخدام يتم نشرها في Google Cloud للاستماع إلى مشغل معين من بين مشغلات الأحداث الستة المتاحة ثم تنفيذ العملية التي تمت كتابتها لتنفيذها.
وظائف السحابة قصيرة العمر ( مع مهلة تنفيذ افتراضية 60 ثانية و 9 دقائق كحد أقصى ) يمكن كتابتها باستخدام JavaScript و Python و Golang و Java وتنفيذها باستخدام وقت التشغيل. في JavaScript ، يمكن تنفيذها فقط باستخدام بعض الإصدارات المتاحة من Node runtime وتتم كتابتها في شكل وحدات CommonJS باستخدام JavaScript عادي حيث يتم تصديرها كوظيفة أساسية ليتم تشغيلها على Google Cloud.
مثال على وظيفة السحابة هو المثال أدناه الذي يمثل نموذجًا معياريًا فارغًا لوظيفة معالجة بيانات المستخدم.
// index.js exports.firestoreFunction = function (req, res) { return res.status(200).send({ data: `Hello ${req.query.name}` }); }
أعلاه لدينا وحدة لتصدير وظيفة. عند تنفيذه ، يتلقى وسيطات الطلب والاستجابة مشابهة لمسار HTTP
.
ملاحظة : تتطابق وظيفة السحابة مع كل بروتوكول HTTP
عند تقديم طلب. هذا جدير بالملاحظة عند توقع البيانات في وسيطة الطلب حيث أن البيانات المرفقة عند تقديم طلب لتنفيذ وظيفة سحابية ستكون موجودة في نص الطلب لطلبات POST
أثناء وجودك في نص الاستعلام لطلبات GET
.
يمكن تنفيذ وظائف السحابة محليًا أثناء التطوير عن طريق تثبيت حزمة @google-cloud/functions-framework
داخل نفس المجلد حيث يتم وضع الوظيفة المكتوبة أو إجراء تثبيت عالمي لاستخدامها لوظائف متعددة عن طريق تشغيل npm i -g @google-cloud/functions-framework
من سطر الأوامر. بمجرد التثبيت ، يجب إضافته إلى البرنامج النصي package.json
مع اسم الوحدة المصدرة على غرار الوحدة أدناه:
"scripts": { "start": "functions-framework --target=firestoreFunction --port=8000", }
أعلاه ، لدينا أمر واحد داخل البرامج النصية الخاصة بنا في ملف package.json
يقوم بتشغيل إطار عمل الوظائف ويحدد أيضًا وظيفة firestoreFunction
باعتبارها الوظيفة المستهدفة التي يجب تشغيلها محليًا على المنفذ 8000
.
يمكننا اختبار نقطة نهاية هذه الوظيفة عن طريق تقديم طلب GET
للمنفذ 8000
على المضيف المحلي باستخدام curl. سيؤدي لصق الأمر أدناه في Terminal إلى القيام بذلك وإعادة الرد.
curl https://localhost:8000?name="Smashing Magazine Author"
يقوم الأمر أعلاه بتقديم طلب باستخدام طريقة GET HTTP
ويستجيب برمز حالة 200
وبيانات كائن تحتوي على الاسم المضاف في الاستعلام.
نشر وظيفة السحابة
من بين طرق النشر المتاحة ، تتمثل إحدى الطرق السريعة لنشر وظيفة السحابة من جهاز محلي في استخدام SDK السحابي بعد تثبيته. يؤدي تشغيل الأمر أدناه من المحطة الطرفية بعد مصادقة gcloud sdk مع مشروعك على Google Cloud ، إلى نشر وظيفة تم إنشاؤها محليًا إلى خدمة Cloud Function.
gcloud functions deploy "demo-function" --runtime nodejs10 --trigger-http --entry-point=demo --timeout=60 --set-env-vars=[name="Developer"] --allow-unauthenticated
باستخدام العلامات الموضحة أدناه ، ينشر الأمر أعلاه وظيفة تشغيل HTTP إلى google cloud باسم " الوظيفة التجريبية ".
- اسم
هذا هو الاسم الذي يطلق على وظيفة السحابة عند نشرها وهو مطلوب. -
region
هذه هي المنطقة التي سيتم نشر وظيفة السحابة فيها. بشكل افتراضي ، يتم نشره فيus-central1
. -
trigger-http
هذا يحدد HTTP كنوع مشغل الوظيفة. -
allow-unauthenticated
يسمح ذلك باستدعاء الوظيفة خارج Google Cloud عبر الإنترنت باستخدام نقطة النهاية التي تم إنشاؤها دون التحقق مما إذا كان المتصل قد تمت مصادقته أم لا. -
source
المسار المحلي من الجهاز إلى الملف الذي يحتوي على الوظيفة المراد نشرها. -
entry-point
يتم نشر هذه الوحدة النمطية المحددة التي تم تصديرها من الملف حيث تمت كتابة الوظائف. -
runtime
هذا هو وقت تشغيل اللغة الذي سيتم استخدامه للوظيفة ضمن قائمة وقت التشغيل المقبول. -
timeout
هذا هو الحد الأقصى للوقت الذي يمكن أن تعمل فيه الوظيفة قبل انتهاء المهلة. إنها 60 ثانية بشكل افتراضي ويمكن ضبطها على 9 دقائق كحد أقصى.
ملاحظة : جعل وظيفة تسمح بالطلبات غير المصادق عليها يعني أن أي شخص لديه نقطة نهاية وظيفتك يمكنه أيضًا تقديم طلبات دون منحها. للتخفيف من ذلك ، يمكننا التأكد من أن نقطة النهاية تظل خاصة باستخدامها من خلال متغيرات البيئة ، أو عن طريق طلب رؤوس التفويض في كل طلب.
الآن بعد أن تم نشر وظيفتنا التجريبية ولدينا نقطة النهاية ، يمكننا اختبار هذه الوظيفة كما لو كانت تُستخدم في تطبيق حقيقي باستخدام تثبيت عالمي لـ autocannon. سيؤدي تشغيل autocannon -d=5 -c=300 CLOUD_FUNCTION_URL
من المحطة الطرفية المفتوحة إلى إنشاء 300 طلب متزامن لوظيفة السحابة في غضون 5 ثوانٍ. هذا أكثر من كافٍ لبدء وظيفة السحابة وأيضًا إنشاء بعض المقاييس التي يمكننا استكشافها في لوحة معلومات الوظيفة.
ملاحظة : ستتم طباعة نقطة نهاية الوظيفة في الجهاز بعد النشر. إذا لم يكن الأمر كذلك ، فقم بتشغيل gcloud function describe FUNCTION_NAME
من الجهاز للحصول على تفاصيل حول الوظيفة المنشورة بما في ذلك نقطة النهاية.
باستخدام علامة تبويب المقاييس في لوحة المعلومات ، يمكننا أن نرى تمثيلًا مرئيًا من الطلب الأخير يتكون من عدد الاستدعاءات التي تم إجراؤها ، ومدة استمرارها ، وبصمة الذاكرة للوظيفة ، وعدد الحالات التي تم نسجها للتعامل مع الطلبات المقدمة.

تُظهر نظرة فاحصة على مخطط المثيلات النشطة داخل الصورة أعلاه قدرة التحجيم الأفقية لوظائف السحابة ، حيث يمكننا أن نرى أن 209 حالات تم نسجها في غضون بضع ثوانٍ للتعامل مع الطلبات التي تم إجراؤها باستخدام المدفع التلقائي.
سجلات وظائف السحابة
تحتوي كل وظيفة يتم نشرها على Google cloud على سجل وفي كل مرة يتم فيها تنفيذ هذه الوظيفة ، يتم إدخال إدخال جديد في هذا السجل. من علامة التبويب Log في لوحة معلومات الوظيفة ، يمكننا رؤية قائمة بجميع إدخالات السجلات من وظيفة السحابة.
فيما يلي إدخالات السجل من demo-function
تم نشرها والتي تم إنشاؤها نتيجة للطلبات التي قدمناها باستخدام autocannon
.

يُظهر كل إدخال من إدخالات السجل أعلاه بالضبط متى تم تنفيذ الوظيفة ، والمدة التي استغرقها التنفيذ ورمز الحالة الذي انتهى به. إذا كانت هناك أي أخطاء ناتجة عن وظيفة ، فسيتم عرض تفاصيل الخطأ بما في ذلك السطر الذي حدث فيه في السجلات هنا.
يمكن استخدام مستكشف السجلات على Google Cloud للاطلاع على تفاصيل أكثر شمولاً حول السجلات من وظيفة السحابة.
وظائف السحابة مع تطبيقات الواجهة الأمامية
وظائف السحابة مفيدة جدًا وقوية لمهندسي الواجهة الأمامية. يمكن لمهندس الواجهة الأمامية بدون معرفة إدارة التطبيقات الخلفية استخراج وظيفة إلى وظيفة سحابية ونشرها في Google Cloud واستخدامها في تطبيق الواجهة الأمامية عن طريق تقديم طلبات HTTP
إلى وظيفة السحابة من خلال نقطة النهاية الخاصة بها.
لإظهار كيف يمكن استخدام وظائف السحابة في تطبيق الواجهة الأمامية ، سنضيف المزيد من الميزات إلى تطبيق React هذا. يحتوي التطبيق بالفعل على توجيه أساسي بين المصادقة وإعداد الصفحات الرئيسية. سنقوم بتوسيعه لاستخدام واجهة برمجة تطبيقات سياق React لإدارة حالة التطبيق لدينا حيث سيتم استخدام وظائف السحابة التي تم إنشاؤها داخل مخفضات التطبيق.
للبدء ، نقوم بإنشاء سياق تطبيقنا باستخدام createContext
API وأيضًا إنشاء مخفض للتعامل مع الإجراءات داخل تطبيقنا.
// state/index.js import { createContext } from “react”;
export const UserReducer = (action، state) => {switch (action.type) {case “CREATE-USER”: break؛ الحالة "تحميل صورة المستخدم": استراحة ؛ الحالة "FETCH-DATA": حالة الكسر "LOGOUT": كسر ؛ الافتراضي: console.log (
${action.type} is not recognized
)}}؛تصدير const userState = {user: null، isLoggedIn: false}؛
تصدير const UserContext = createContext (userState) ؛
أعلاه ، بدأنا بإنشاء وظيفة UserReducer
التي تحتوي على عبارة تبديل ، مما يسمح لها بإجراء عملية بناءً على نوع الإجراء الذي تم إرساله إليها. يحتوي بيان التبديل على أربع حالات وهذه هي الإجراءات التي سنتعامل معها. في الوقت الحالي لم يفعلوا أي شيء حتى الآن ولكن عندما نبدأ في التكامل مع وظائف السحابة الخاصة بنا ، فإننا ننفذ بشكل تدريجي الإجراءات التي سيتم تنفيذها فيها.
قمنا أيضًا بإنشاء وتصدير سياق تطبيقنا باستخدام React createContext API وأعطيناها قيمة افتراضية لكائن userState
الذي يحتوي على قيمة مستخدم حاليًا والتي سيتم تحديثها من فارغة إلى بيانات المستخدم بعد المصادقة وأيضًا قيمة منطقية isLoggedIn
لمعرفة ما إذا كان تم تسجيل دخول المستخدم أم لا.
الآن يمكننا المضي قدمًا في استهلاك سياقنا ، ولكن قبل القيام بذلك ، نحتاج إلى التفاف شجرة التطبيق بالكامل مع الموفر المرفق بـ UserContext
حتى تتمكن المكونات الفرعية من الاشتراك في تغيير القيمة في سياقنا.
// index.js import React from "react"; import ReactDOM from "react-dom"; import "./index.css"; import App from "./app"; import { UserContext, userState } from "./state/"; ReactDOM.render( <React.StrictMode> <UserContext.Provider value={userState}> <App /> </UserContext.Provider> </React.StrictMode>, document.getElementById("root") ); serviceWorker.unregister();
نحن نلف تطبيق الإدخال الخاص بنا مع موفر UserContext
في المكون الجذر وقمنا بتمرير القيمة الافتراضية userState
التي تم إنشاؤها مسبقًا في الخاصية value.
الآن بعد أن تم الإعداد الكامل لحالة التطبيق لدينا ، يمكننا الانتقال إلى إنشاء نموذج بيانات المستخدم باستخدام Google Cloud Firestore من خلال وظيفة السحابة.
التعامل مع بيانات التطبيق
تتكون بيانات المستخدم داخل هذا التطبيق من معرف فريد وبريد إلكتروني وكلمة مرور وعنوان URL للصورة. باستخدام وظيفة السحابة ، سيتم تخزين هذه البيانات على السحابة باستخدام خدمة Cloud Firestore التي يتم تقديمها على Google Cloud Platform.
Google Cloud Firestore ، قاعدة بيانات NoSQL مرنة تم اقتطاعها من قاعدة بيانات Firebase Realtime مع ميزات محسّنة جديدة تتيح استعلامات أكثر ثراءً وأسرع إلى جانب دعم البيانات في وضع عدم الاتصال. يتم تنظيم البيانات داخل خدمة Firestore في مجموعات ووثائق مماثلة لقواعد بيانات NoSQL الأخرى مثل MongoDB.
يمكن الوصول إلى Firestore بصريًا من خلال Google Cloud Console. لتشغيله ، افتح جزء التنقل الأيسر وانتقل لأسفل إلى قسم قاعدة البيانات وانقر على Firestore. سيُظهر ذلك قائمة المجموعات للمستخدمين الذين لديهم بيانات موجودة أو يطالب المستخدم بإنشاء مجموعة جديدة عند عدم وجود مجموعة موجودة. سننشئ مجموعة مستخدمين ليستخدمها تطبيقنا.
على غرار الخدمات الأخرى على Google Cloud Platform ، يحتوي Cloud Firestore أيضًا على مكتبة عميل JavaScript مصممة لاستخدامها في بيئة العقدة ( سيتم طرح خطأ إذا تم استخدامه في المتصفح ). للارتجال ، نستخدم Cloud Firestore في وظيفة سحابية باستخدام حزمة @google-cloud/firestore
.
استخدام Cloud Firestore مع وظيفة السحابة
للبدء ، سنعيد تسمية الوظيفة الأولى التي أنشأناها من demo-function
firestoreFunction
، ثم نوسعها للاتصال بـ Firestore وحفظ البيانات في مجموعة مستخدمينا.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const { SecretManagerServiceClient } = require("@google-cloud/secret-manager"); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); console.log(document) // prints details of the collection to the function logs if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": break case "LOGIN-USER": break; default: res.status(422).send(`${type} is not a valid function action`) } };
للتعامل مع المزيد من العمليات التي تتضمن Fire-store ، أضفنا بيان تبديل مع حالتين للتعامل مع احتياجات المصادقة لتطبيقنا. يقوم بيان التبديل الخاص بنا بتقييم تعبير type
نضيفه إلى نص الطلب عند تقديم طلب لهذه الوظيفة من تطبيقنا ، وعندما لا تكون بيانات هذا type
موجودة في نص الطلب لدينا ، يتم تحديد الطلب على أنه طلب غير صالح ورمز حالة 400
بجانب رسالة للإشارة إلى type
المفقود يتم إرساله كرد.
نقوم بتأسيس اتصال مع Firestore باستخدام مكتبة بيانات اعتماد التطبيق الافتراضية (ADC) داخل مكتبة عميل Cloud Firestore. في السطر التالي ، نسمي طريقة التجميع في متغير آخر ونمرر اسم مجموعتنا. سنستخدم هذا لإجراء مزيد من العمليات الأخرى على جمع المستندات المتضمنة.
ملاحظة : مكتبات العميل للخدمات على Google Cloud تتصل بالخدمة الخاصة بها باستخدام مفتاح حساب خدمة تم إنشاؤه تم تمريره عند تهيئة المُنشئ. عندما لا يكون مفتاح حساب الخدمة موجودًا ، فإنه يتم تعيينه افتراضيًا على استخدام بيانات الاعتماد الافتراضية للتطبيق والتي تتصل بدورها باستخدام أدوار IAM
المعينة لوظيفة السحابة.
بعد تحرير الكود المصدري لوظيفة تم نشرها محليًا باستخدام Gcloud SDK ، يمكننا إعادة تشغيل الأمر السابق من محطة لتحديث وظيفة السحابة وإعادة نشرها.
الآن وقد تم إنشاء اتصال ، يمكننا تنفيذ حالة CREATE-USER
لإنشاء مستخدم جديد باستخدام البيانات من نص الطلب.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const path = require("path"); const { v4 : uuid } = require("uuid") const cors = require("cors")({ origin: true }); const client = new SecretManagerServiceClient(); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); if (!type) { res.status(422).send("An action type was not specified"); } switch (type) { case "CREATE-USER": if (!email || !password) { res.status(422).send("email and password fields missing"); } const id = uuid() return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document.doc(id) .set({ id : id email: email, password: hash, img_uri : null }) .then((response) => res.status(200).send(response)) .catch((e) => res.status(501).send({ error : e }) ); }); }); case "LOGIN": break; default: res.status(400).send(`${type} is not a valid function action`) } }); };
أنشأنا UUID باستخدام الحزمة uuid لاستخدامه كمعرف للمستند الذي سيتم حفظه عن طريق تمريره إلى طريقة set
في المستند وكذلك معرّف المستخدم. بشكل افتراضي ، يتم إنشاء معرف عشوائي على كل مستند مدرج ولكن في هذه الحالة ، سنقوم بتحديث المستند عند التعامل مع تحميل الصورة ويكون UUID هو ما سيتم استخدامه لتحديث مستند معين. بدلاً من تخزين كلمة مرور المستخدم في نص عادي ، فإننا نملحها أولاً باستخدام bcryptjs ثم نخزن تجزئة النتيجة ككلمة مرور المستخدم.

بدمج وظيفة السحابة firestoreFunction
في التطبيق ، نستخدمها من حالة CREATE_USER
داخل مخفض المستخدم.
بعد النقر على زر إنشاء حساب ، يتم إرسال إجراء إلى CREATE_USER
بنوع CREATE_USER لتقديم طلب POST
يحتوي على البريد الإلكتروني وكلمة المرور المكتوبة إلى نقطة نهاية وظيفة firestoreFunction
.
import { createContext } from "react"; import { navigate } from "@reach/router"; import Axios from "axios"; export const userState = { user : null, isLoggedIn: false, }; export const UserReducer = (state, action) => { switch (action.type) { case "CREATE_USER": const FIRESTORE_FUNCTION = process.env.REACT_APP_FIRESTORE_FUNCTION; const { userEmail, userPassword } = action; const data = { type: "CREATE-USER", email: userEmail, password: userPassword, }; Axios.post(`${FIRESTORE_FUNCTION}`, data) .then((res) => { navigate("/home"); return { ...state, isLoggedIn: true }; }) .catch((e) => console.log(`couldnt create user. error : ${e}`)); break; case "LOGIN-USER": break; case "UPLOAD-USER-IMAGE": break; case "FETCH-DATA" : break case "LOGOUT": navigate("/login"); return { ...state, isLoggedIn: false }; default: break; } }; export const UserContext = createContext(userState);
أعلاه ، استخدمنا Axios لتقديم الطلب إلى firestoreFunction
وبعد أن يتم حل هذا الطلب ، قمنا بتعيين الحالة الأولية للمستخدم من null
إلى البيانات التي يتم إرجاعها من الطلب وأخيرًا نقوم بتوجيه المستخدم إلى الصفحة الرئيسية كمستخدم مصدق عليه .
في هذه المرحلة ، يمكن للمستخدم الجديد إنشاء حساب بنجاح وتوجيهه إلى الصفحة الرئيسية. توضح هذه العملية كيف نستخدم Firestore لإجراء إنشاء أساسي للبيانات من وظيفة السحابة.
التعامل مع تخزين الملفات
يعد تخزين ملفات المستخدم واستردادها في تطبيق ما في أغلب الأحيان ميزة مطلوبة بشدة داخل التطبيق. في تطبيق متصل بخلفية node.js ، غالبًا ما يتم استخدام Multer كبرنامج وسيط للتعامل مع البيانات متعددة الأجزاء / النموذج التي يأتي بها الملف الذي تم تحميله. ولكن في حالة عدم وجود node.js backend ، يمكننا استخدام ملف عبر الإنترنت خدمة التخزين مثل Google Cloud Storage لتخزين أصول التطبيق الثابتة.
Google Cloud Storage هي خدمة تخزين ملفات متاحة عالميًا تُستخدم لتخزين أي كمية من البيانات ككائنات للتطبيقات في مجموعات. إنه مرن بدرجة كافية للتعامل مع تخزين الأصول الثابتة لكل من التطبيقات الصغيرة والكبيرة الحجم.
لاستخدام خدمة التخزين السحابي داخل أحد التطبيقات ، يمكننا الاستفادة من نقاط نهاية واجهة برمجة تطبيقات التخزين المتاحة أو باستخدام مكتبة عميل التخزين الرسمية لعقدة التخزين. ومع ذلك ، لا تعمل مكتبة عميل Node Storage داخل نافذة المتصفح ، لذا يمكننا الاستفادة من وظيفة السحابة حيث سنستخدم المكتبة.
مثال على ذلك ، هو وظيفة السحابة التي تعمل أدناه على توصيل وتحميل ملف إلى Cloud Bucket تم إنشاؤه.
const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); exports.Uploader = (req, res) => { const { file } = req.body; StorageClient.bucket("TEST_BUCKET") .file(file.name) .then((response) => { console.log(response); res.status(200).send(response) }) .catch((e) => res.status(422).send({error : e})); }); };
من وظيفة السحابة أعلاه ، نقوم بتنفيذ العمليتين الرئيسيتين التاليتين:
أولاً ، نقوم بإنشاء اتصال بـ Cloud Storage داخل
Storage constructor
ويستخدم ميزة بيانات اعتماد التطبيق الافتراضية (ADC) على Google Cloud للمصادقة مع التخزين السحابي.ثانيًا ، نقوم بتحميل الملف المضمن في نص الطلب إلى
TEST_BUCKET
من خلال استدعاء أسلوب.file
وتمرير اسم الملف. نظرًا لأن هذه عملية غير متزامنة ، فإننا نستخدم وعدًا لمعرفة متى يتم حل هذا الإجراء ونرسل استجابة200
مرة أخرى وبالتالي إنهاء دورة حياة الاستدعاء.
الآن ، يمكننا توسيع وظيفة Uploader
Cloud Function أعلاه للتعامل مع تحميل صورة الملف الشخصي للمستخدم. ستتلقى وظيفة السحابة صورة ملف تعريف المستخدم ، وتخزنها في الحاوية السحابية لتطبيقنا ، ثم تقوم بتحديث بيانات img_uri
الخاصة بالمستخدم ضمن مجموعة مستخدمينا في خدمة Firestore.
require("dotenv").config(); const { Firestore } = require("@google-cloud/firestore"); const cors = require("cors")({ origin: true }); const { Storage } = require("@google-cloud/storage"); const StorageClient = new Storage(); const BucketName = process.env.STORAGE_BUCKET exports.Uploader = (req, res) => { return Cors(req, res, () => { const { file , userId } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); StorageClient.bucket(BucketName) .file(file.name) .on("finish", () => { StorageClient.bucket(BucketName) .file(file.name) .makePublic() .then(() => { const img_uri = `https://storage.googleapis.com/${Bucket}/${file.path}`; document .doc(userId) .update({ img_uri, }) .then((updateResult) => res.status(200).send(updateResult)) .catch((e) => res.status(500).send(e)); }) .catch((e) => console.log(e)); }); }); };
الآن قمنا بتوسيع وظيفة التحميل أعلاه لإجراء العمليات الإضافية التالية:
- أولاً ، يقوم بإجراء اتصال جديد بخدمة Firestore للحصول على مجموعة
users
من خلال تهيئة مُنشئ Firestore ويستخدم بيانات اعتماد التطبيق الافتراضية (ADC) للمصادقة مع التخزين السحابي. - بعد تحميل الملف المضاف إلى نص الطلب ، نجعله عامًا حتى يمكن الوصول إليه عبر عنوان URL عام عن طريق استدعاء طريقة
makePublic
في الملف الذي تم تحميله. وفقًا للتحكم في الوصول الافتراضي لـ Cloud Storage ، بدون جعل الملف عامًا ، لا يمكن الوصول إلى الملف عبر الإنترنت وتكون قادرًا على القيام بذلك عند تحميل التطبيق.
ملاحظة : يعني جعل الملف عامًا أن أي شخص يستخدم التطبيق الخاص بك يمكنه نسخ رابط الملف ولديه وصول غير مقيد إلى الملف. تتمثل إحدى طرق منع ذلك في استخدام عنوان URL موقّع لمنح وصول مؤقت إلى ملف داخل الحاوية الخاصة بك بدلاً من جعله عامًا بالكامل.
- بعد ذلك ، نقوم بتحديث بيانات المستخدم الحالية لتشمل عنوان URL للملف الذي تم تحميله. نعثر على بيانات المستخدم المعين باستخدام استعلام Firestore's
WHERE
ونستخدمuserId
المضمن في نص الطلب ، ثم قمنا بتعيين الحقلimg_uri
ليحتوي على عنوان URL للصورة المحدثة حديثًا.
يمكن استخدام وظيفة Upload
السحابي أعلاه في أي تطبيق به مستخدمون مسجلون ضمن خدمة Firestore. كل ما هو مطلوب لتقديم طلب POST
إلى نقطة النهاية ، ووضع IS للمستخدم وصورة في نص الطلب.
مثال على ذلك داخل التطبيق هو حالة UPLOAD-FILE
التي تقدم طلب POST
للوظيفة وتضع رابط الصورة الذي تم إرجاعه من الطلب في حالة التطبيق.
# index.js import Axios from 'axios' const UPLOAD_FUNCTION = process.env.REACT_APP_UPLOAD_FUNCTION export const UserReducer = (state, action) => { switch (action.type) { case "CREATE-USER" : # .....CREATE-USER-LOGIC .... case "UPLOAD-FILE": const { file, id } = action return Axios.post(UPLOAD_FUNCTION, { file, id }, { headers: { "Content-Type": "image/png", }, }) .then((response) => {}) .catch((e) => console.log(e)); default : return console.log(`${action.type} case not recognized`) } }
من حالة التبديل أعلاه ، قمنا بتقديم طلب POST
باستخدام Axios إلى UPLOAD_FUNCTION
بالمرور في الملف المضاف ليتم تضمينه في نص الطلب وقمنا أيضًا بإضافة صورة Content-Type
في رأس الطلب.
بعد تحميل ناجح ، تحتوي الاستجابة التي تم إرجاعها من وظيفة السحابة على مستند بيانات المستخدم الذي تم تحديثه ليحتوي على عنوان url صالح للصورة التي تم تحميلها على تخزين google cloud. يمكننا بعد ذلك تحديث حالة المستخدم لاحتواء البيانات الجديدة ، وهذا سيؤدي أيضًا إلى تحديث عنصر صورة ملف تعريف المستخدم src
في مكون الملف الشخصي.

التعامل مع وظائف كرون
تعد المهام الآلية المتكررة مثل إرسال رسائل البريد الإلكتروني إلى المستخدمين أو تنفيذ إجراء داخلي في وقت محدد في معظم الأحيان ميزة مضمنة في التطبيقات. في تطبيق node.js العادي ، يمكن التعامل مع مثل هذه المهام كوظائف cron باستخدام node-cron أو جدول العقدة. عند إنشاء تطبيقات بدون خادم باستخدام Google Cloud Platform ، تم تصميم Cloud Scheduler أيضًا لأداء عملية cron.
ملاحظة : على الرغم من أن Cloud Scheduler يعمل بشكل مشابه لأداة Unix cron المساعدة في إنشاء المهام التي يتم تنفيذها في المستقبل ، فمن المهم ملاحظة أن Cloud Scheduler لا ينفذ أمرًا كما تفعل الأداة المساعدة cron. بدلا من ذلك ، فإنه ينفذ عملية باستخدام هدف محدد.
كما يوحي الاسم ، يسمح Cloud Scheduler للمستخدمين بجدولة عملية ليتم تنفيذها في وقت لاحق. تسمى كل عملية وظيفة ويمكن إنشاء المهام وتحديثها بشكل مرئي وحتى إتلافها من قسم "المجدول" في وحدة التحكم السحابية. إلى جانب حقل الاسم والوصف ، تتكون الوظائف في Cloud Scheduler مما يلي:
- تكرر
يستخدم هذا لجدولة تنفيذ مهمة Cron. يتم تحديد الجداول باستخدام تنسيق unix-cron الذي يُستخدم في الأصل عند إنشاء وظائف في الخلفية على جدول cron في بيئة Linux. يتكون تنسيق unix-cron من سلسلة بها خمس قيم تمثل كل منها نقطة زمنية. أدناه يمكننا رؤية كل من السلاسل الخمسة والقيم التي تمثلها.
- - - - - - - - - - - - - - - - minute ( - 59 ) | - - - - - - - - - - - - - hour ( 0 - 23 ) | | - - - - - - - - - - - - day of month ( 1 - 31 ) | | | - - - - - - - - - month ( 1 - 12 ) | | | | - - - - - -- day of week ( 0 - 6 ) | | | | | | | | | | | | | | | | | | | | | | | | | * * * * *
تكون أداة منشئ Crontab سهلة الاستخدام عند محاولة إنشاء قيمة وقت التردد لوظيفة ما. إذا كنت تجد صعوبة في وضع قيم الوقت معًا ، فإن منشئ Crontab يحتوي على قائمة منسدلة مرئية حيث يمكنك تحديد القيم التي تشكل جدولًا ونسخ القيمة التي تم إنشاؤها واستخدامها كتردد.
- وحدة زمنية
المنطقة الزمنية التي يتم تنفيذ وظيفة cron من خلالها. نظرًا لاختلاف التوقيت بين المناطق الزمنية ، فإن مهام cron التي يتم تنفيذها باستخدام مناطق زمنية محددة مختلفة سيكون لها أوقات تنفيذ مختلفة. - استهداف
هذا هو ما يستخدم في تنفيذ الوظيفة المحددة. يمكن أن يكون الهدف نوعHTTP
حيث تقدم الوظيفة طلبًا في الوقت المحدد إلى عنوان URL أو موضوع Pub / Sub يمكن للوظيفة نشر الرسائل إليه أو سحب الرسائل منه وأخيرًا تطبيق App Engine.
يتحد برنامج Cloud Scheduler جيدًا مع وظائف السحابة التي يتم تشغيلها بواسطة HTTP. عندما يتم إنشاء وظيفة ضمن Cloud Scheduler مع تعيين هدفها على HTTP ، يمكن استخدام هذه الوظيفة لتنفيذ وظيفة سحابية. كل ما يجب القيام به هو تحديد نقطة نهاية وظيفة السحابة ، وتحديد فعل HTTP للطلب ثم إضافة أي بيانات تحتاج إلى تمريرها لتعمل في حقل النص المعروض. كما هو موضح في النموذج أدناه:

سيتم تشغيل وظيفة cron في الصورة أعلاه بحلول الساعة 9 صباحًا كل يوم لتقديم طلب POST
إلى نقطة نهاية العينة لوظيفة السحابة.
حالة الاستخدام الأكثر واقعية لوظيفة cron هي إرسال رسائل بريد إلكتروني مجدولة إلى المستخدمين في فترة زمنية معينة باستخدام خدمة بريدية خارجية مثل Mailgun. لرؤية هذا أثناء العمل ، سننشئ وظيفة سحابية جديدة ترسل بريدًا إلكترونيًا بتنسيق HTML إلى عنوان بريد إلكتروني محدد باستخدام حزمة جافا سكريبت nodemailer للاتصال بـ Mailgun:
# index.js require("dotenv").config(); const nodemailer = require("nodemailer"); exports.Emailer = (req, res) => { let sender = process.env.SENDER; const { reciever, type } = req.body var transport = nodemailer.createTransport({ host: process.env.HOST, port: process.env.PORT, secure: false, auth: { user: process.env.SMTP_USERNAME, pass: process.env.SMTP_PASSWORD, }, }); if (!reciever) { res.status(400).send({ error: `Empty email address` }); } transport.verify(function (error, success) { if (error) { res .status(401) .send({ error: `failed to connect with stmp. check credentials` }); } }); switch (type) { case "statistics": return transport.sendMail( { from: sender, to: reciever, subject: "Your usage satistics of demo app", html: { path: "./welcome.html" }, }, (error, info) => { if (error) { res.status(401).send({ error : error }); } transport.close(); res.status(200).send({data : info}); } ); default: res.status(500).send({ error: "An available email template type has not been matched.", }); } };
Using the cloud function above we can send an email to any user's email address specified as the receiver value in the request body. It performs the sending of emails through the following steps:
- It creates an SMTP transport for sending messages by passing the
host
,user
andpass
which stands for password, all displayed on the user's Mailgun dashboard when a new account is created. - Next, it verifies if the SMTP transport has the credentials needed in order to establish a connection. If there's an error in establishing the connection, it ends the function's invocation and sends back a
401 unauthenticated
status code. - Next, it calls the
sendMail
method to send the email containing the HTML file as the email's body to the receiver's email address specified in theto
field.
Note : We use a switch statement in the cloud function above to make it more reusable for sending several emails for different recipients. This way we can send different emails based on the type
field included in the request body when calling this cloud function.
Now that there is a function that can send an email to a user; we are left with creating the cron job to invoke this cloud function. This time, the cron jobs are created dynamically each time a new user is created using the official Google cloud client library for the Cloud Scheduler from the initial firestoreFunction
.
We expand the CREATE-USER
case to create the job which sends the email to the created user at a one-day interval.
require("dotenv").config();cloc const { Firestore } = require("@google-cloud/firestore"); const scheduler = require("@google-cloud/scheduler") const cors = require("cors")({ origin: true }); const EMAILER = proccess.env.EMAILER_ENDPOINT const parent = ScheduleClient.locationPath( process.env.PROJECT_ID, process.env.LOCATION_ID ); exports.firestoreFunction = function (req, res) { return cors(req, res, () => { const { email, password, type } = req.body; const firestore = new Firestore(); const document = firestore.collection("users"); const client = new Scheduler.CloudSchedulerClient() if (!type) { res.status(422).send({ error : "An action type was not specified"}); } switch (type) { case "CREATE-USER":
const job = { httpTarget: { uri: process.env.EMAIL_FUNCTION_ENDPOINT, httpMethod: "POST", body: { email: email, }, }, schedule: "*/30 */6 */5 10 4", timezone: "Africa/Lagos", }
if (!email || !password) { res.status(422).send("email and password fields missing"); } return bcrypt.genSalt(10, (err, salt) => { bcrypt.hash(password, salt, (err, hash) => { document .add({ email: email, password: hash, }) .then((response) => {
client.createJob({ parent : parent, job : job }).then(() => res.status(200).send(response)) .catch(e => console.log(`unable to create job : ${e}`) )
}) .catch((e) => res.status(501).send(`error inserting data : ${e}`) ); }); }); default: res.status(422).send(`${type} is not a valid function action`) } }); };
From the snippet above, we can see the following:
- A connection to the Cloud Scheduler from the Scheduler constructor using the Application Default Credentials (ADC) is made.
- We create an object consisting of the following details which make up the cron job to be created:
-
uri
The endpoint of our email cloud function in which a request would be made to. -
body
This is the data containing the email address of the user to be included when the request is made. -
schedule
The unix cron format representing the time when this cron job is to be performed.
-
- After the promise from inserting the user's data document is resolved, we create the cron job by calling the
createJob
method and passing in the job object and the parent. - The function's execution is ended with a
200
status code after the promise from thecreateJob
operation has been resolved.
After the job is created, we'll see it listed on the scheduler page.

From the image above we can see the time scheduled for this job to be executed. We can decide to manually run this job or wait for it to be executed at the scheduled time.
خاتمة
Within this article, we have had a good look into serverless applications and the benefits of using them. We also had an extensive look at how developers can manage their serverless applications on the Google Cloud using Cloud Functions so you now know how the Google Cloud is supporting the use of serverless applications.
Within the next years to come, we will certainly see a large number of developers adapt to the use of serverless applications when building applications. If you are using cloud functions in a production environment, it is recommended that you read this article from a Google Cloud advocate on “6 Strategies For Scaling Your Serverless Applications”.
The source code of the created cloud functions are available within this Github repository and also the used front-end application within this Github repository. The front-end application has been deployed using Netlify and can be tested live here.
مراجع
- جوجل كلاود
- وظائف السحابة
- Cloud Source Repositories
- Cloud Scheduler overview
- سحابة Firestore
- “6 Strategies For Scaling Your Serverless Applications,” Preston Holmes