بناء RESTful API متوافق مع المبادئ
السلام عليكم ورحمة الله وبركاته
المقدمة
في مقالة كيفية التعامل مع أي RESTful API وفهم مكوناته قمنا باعطاء نبذة شاملة لكيفية التعامل مع RESTful API من وجه نظر الـ Frontend
أما في هذه المقالة سأقوم بشرح كيف تقوم أن كـ Backend محترم ببناء RESTful API محترم
وكيف يمكنه ان يصمم API يقوم باتباع قواعد ومبادئ الـ RESTful API على قد المستطاع
ستجد العديد من العوامل المشتركة بين المقالتين وقد تجد اننا نعيد شرح بعض الاجزاء ذاتها لكن بتفاصيل وزاوية أخرى
هذا بالطبع لانهما يتحدثان عن الـ RESTful API لكن من زاويتين مختلفتين، زاوية الـ Frontend في المقالة السابقة وكيفية تعامله مع الـ RESTful API وزاوية الـ Backend في هذه المقالة في إنشاءه للـ RESTful API
تذكر
- الـ
Backendهو المسؤول عن الـServerالمسؤول عن تخزين البيانات ويصمم لك الـAPI - الـ
Frontendيستخدم هذا الـAPIليقوم بإحضار بيانات أو إضافة بيانات أو تعديلها أو حذفها والقيام بالعمليات على الـServer
ما هو الـ RESTful API ؟
الـ RESTful API هو أسلوب أو مجموعة من القواعد تم التعارف عليها تهتم بتصميم وتطوير الـ API وفقًا لمبادئ معينة وثابتة
لجعله سهل الاستخدام والفهم وتستطيع إضافة العديد من المميزات بكل سهولة وبسيطة
الكلام قد يبدو سهل وهو كذالك بالفعل لكن قد تجد بعض الاشخاص يعترضون على بعض الأمور ويضيفون أو ينقصون أو يغيرون بعض الاشياء في الـ RESTful API وهذا أمر طبيعي ستجده في كل شيء موجود
في هذه المقالة سأتطرق لبعض المبادئ العامة التي ستراها تطبق بشكل عملي في معظم الـ RESTful API
الـ RESTful API مبادئه كثيرة لكن سهلة الفهم والحفظ والتعود ولست مجبرًا على تطبيقه بشكل حرفي لان قد تحدث بعض التغيرات والاستثناءات لبعض الامور بسبب متطلبات المشروع
أنت فقط حاول دائمًا اتباعه على قد المستطاع
تمثيل وإرسال البيانات على هيئة JSON
أول وأهم شيء لدينا هو اختيار شكل أو صيغة ثابتة لتمثيل وارسال البيانات
وهذه الصيغة يجب ان تكون جيدة لتصف البيانات وتمثيلها بشكل قابل لتعامل معها
بمعنى إن طلب منك صديقك الـ Frontend بتحديث بيانات مستخدم معين وانت ترد عليه بـ Response على هيئة رسالة نصية تقول له User Updated Successfully :) أو أن ترجع له رقم مثل 200 فقط لا غير
أنت هكذا لم تفده بشئ وهو لن يستطيع أن يتعامل مع تلك الرسالة فقط
أريدك كشخص يصمم الـ API أن تضع نفسك دائمًا مكان الـ Frontend الذي سيستخدم هذا الـ API
حسنًا تخيل معي الآتي قام صديقك الـ Frontend بطلب تحديث بيانات مستخدم معين فانت رددت عليه برسالة User Updated Successfully :)
الآن الـ Frontend اعتمد التعديل وعدل البيانات المتواجدة معه بالبيانات الجديدة التي عدلها المستخدم
تبدو الأمور جميلة حاليًا، لكن ماذا إذا كانت هناك بيانات اخرى للمستخدم تغيرت بشكل تلقائي ؟
بمعنى انه لنفترض ان المستخدم قام بتغير عنوان اقامته، هذا التغير البسيط قد يغير بيانات اخرى للمستخدم بشكل تلقائي كرمز مركز البريد الخاص بمكان الاقامة الجديد على سبيل المثال
فكيف سيعرف الـ Frontend بهذه التغيرات التلقائية التي تحدث ؟ لذا يريدك أن ترسل بيانات المستخدم ليعرف ما البيانات الاخرى التي تغيرت
فالرسالة النصية لن تفيده بشيء
والشكل أو الهيكل التي يفضله ويرشحه الـ Restful API هو الـ JSON في ارسال البيانات لان الـ JSON أصبح هو النظام السائد والمتعارف عليه بين المواقع والمطورين في تخزين وارسال البيانات وأصبح ينصح بها بشدة في تصميم الـ RESTful API
وهذا بسبب بساطتها وتنظيمها ويسهل وصف وتمثيل البيانات بشكل واضح ويسهل عليك قراءتها والتعامل معها بسلاسة
وأيضا أصبحت اللغات والمكاتب تدعمها بشكل كامل
لذا بدلًا من أن ترسل رسالة نصية لا تنفع يمكنك أن ترسل كم هائل من المعلومات والتفاصيل كهيئة JSON
{
"status": "success",
"code": 200,
"message": "User updated successfully",
"data": {
"id": 2,
"name": "Ahmed Tab",
"email": "ahmed@domain.com",
"age": 23
// ...etc.
}
}
من خلال قراءتك له تستطيع ان تفهم محتواه بسهولة
فالبيانات مترتبة ومنظمة في هيئة keys وvalues كل عنصر يصف البيانات أو القيمة التي لديه
كل هذه المعلومات تستطيع استخراجها بسهولة بطريقة منظمة من خلال أي لغة برمجية
بالطبع هناك طرق أخرى لتمثيل البيانات مثل XML لكنه غير مدعوم بشكل واسع في المكاتب المختلفة أو حتى اللغات وأحيانا يضطر المبرمجين لاجاد طرق لتحويل البيانات من XML وغيره لـ JSON بطريقة ما
لذا يفضل التمسك بالـ JSON لانه كما قلنا هو الشكل السائد والمنتشر بين المطورين واصبح هو الشكل الثابت والمتعارف عليه في نقل البيانات
اختيار الـ Methods / Actions الصحيح في التعبير عن معناه
الـ methods أو احيانا تسمى actions هي مجموعة من الوظائف أو الأوامر التي تُمرَر للـ API ليعرف ما هو طلبك، هل تريد إحضار شيء ؟ أو إضافة شيء ؟ أو تعديل شيء ؟ أو حذف شيء ؟
نوع الطلب يحدده الـ method
وانواع الـ method كثيرة وأهم 5 انواع أساسية هم:-
| method | وظيفته |
|---|---|
| GET | تستخدم لإحضار البيانات فقط |
| POST | تستخدم لإضافة بيانات جديدة |
| PUT | تستخدم للقيام باستبدال بيانات عنصر معين بشكل كامل |
| PATCH | تستخدم للقيام بتعديل بيانات عنصر معين بشكل جزئي |
| DELETE | تستخدم لحذف شيء أو عنصر من قاعدة البيانات |
حاول ان تصمم الـ API ليتبع المعنى العام لكل method
لأن صديقك الـ Frontend عندما يحاول احضار شيء ما هكذا:
GET /api/users
فهو يتوقع منك أن ترجع له جميع المستخدمين
ومن غير المنطقي هنا ان تقوم بتعديل البيانات فجأة برغم من أنه استخدم الـ GET
نفس الأمر مع PATCH على سبيل المثال
PATCH /api/users/25
Body: {
"name": "Ahmed El-Tabarani",
}
صديقك الـ Frontend عندما يحاول تعديل بيانات المستخدم رقم 25 فهو سيستخدم الـ PATCH
ويعطيك في الـ body الشيء المراد تعديله
هذا ما يتوقعه وهذا هو المعنى المنطقي للـ PATCH أنها تقوم بالتعديل
لذا من غير المنطقي ان تجعلها تقوم بحذف المستخدم بالكامل أو تقوم بوظيفة مغايرة للوظيفة المتفق أو المتعارف عليها
نفس الأمر ينطبق على باقي الـ methods كـ POST للإضافة و DELETE للحذف وهكذا
كل method يجب ان تقوم وتعبر عن المسمى العام لها
الفرق بين PATCH و PUT
عليك ان تدرك الفرق الجوهري بين PATCH و PUT لان البعض يختلط عليه الأمر ويظنهما نفس الشيء ولا يفرقون بينهما
الفرق بينهما متقارب لكن عليك أن تعرف ما هو المتعارف عليه بين PATCH و PUT
أولًا ببساطة الـ PATCH تعدل العنصر أو مجموعة من العناصر بشكل جزئي
أو حتى ببعض الوظائف والعمليات الصغيرة لتنفيذ غرض معين
أما الـ PUT فهو يستخدم للاستبدال الكلي للعنصر وليس لتعديل جزء منه
بمعنى ان كان لديك عنصر معين به name, email age, address
وأرسلت البيانات الجديدة في الـ body الـ name, email فقط
فسيتم مسح جميع مكونات العنصر الموجودة مسبقًا ويستبدلهم بالـ name, email
وستجد ان العنصر لم بعد لديه age, address لأنه كما قلنا الـ PUT يقوم باستبدال البيانات الجديدة المرسلة في الـ body بالبيانات القديمة
بمعنى ان الـ
PUTلا يقوم بعمل تعديل بشكل حرفي بل يقوم باستبدال كل البيانات القديمة بالجديدة
وان لم يكن هذا العنصر موجود فسيتم إنشاءه في قاعدة البيانات
اي ان PUT يعمل عمل الـ POST في حالة ان العنصر المراد تعديله غير موجود في قاعدة البيانات
لذا ستجد انه عندما نستخدم الـ PUT يتم ارسال جميع بيانات ومكونات العنصر
بحيث انه اذا كان العنصر موجودا فسيتم استبدال بياناته وان لم يكن موجودًا فسيتم إنشاءه ببساطة
إرسال Status Code يناسب الحدث أو النتيجة
عندما تدخل إلى https://developer.mozilla.org/en-US/docs/Web/HTTP/Status أو https://www.restapitutorial.com/httpstatuscodes ستجد العديد من الحالات المتعارف عليها وكل واحدة تستخدم لغرض معين وتصف حدث معين
لذا عليك أن تحاول على قد المستطاع ان تلتزم بارسال الـ Status Code المناسبة ليصف الحدث المناسب
ليست مرغمًا على حفظها كلها بالطبع لكن عليك أن تعرف على الاقل الحالات المشهورة والتي ستستخدمها بكثرة
سأعرض الآن جدول يحتوي على أهم الحالات شائعة الاستخدام
| Status Code | الاسم | الوصف |
|---|---|---|
| 200 | OK | تم تنفيذ العملية بنجاح وسيرسل الـ Backend البيانات الناتجة من تنفيذ تلك العملية |
| 201 | Created | تم إنشاء البيانات المطلوبة بنجاح |
| 202 | Accepted | تم الاستلام بنجاح وجاري تنفيذ العملية تستخدم مع العمليات المؤجلة أو التي لم تنتهي بعد وتحتاج وقتًا لتنفيذها انتبه هنا العملية لم تنفذ بعد وقد يحدث خطأ ما في نهايتها، 202 تخبرك بأنه تم استلام الطلب بنجاح لكن لن تخبرك هل العملية ستنجح ام لا |
| 204 | No Content | تم تنفيذ العمليات بنجاح ولكن لن يرسل الـ Backend أي بيانات خاصة بتلك العملية (لاحظ الفرق بينها وبين 200 في سيرسل ولن يرسل) |
| 400 | Bad Request | خطأ من ناحية المستخدم وليس من الـ Server، بمعنى ان الـ Server يتوقع منك ارسال البيانات بشكل معين لكن المستخدم ارساله بشكل اخر |
| 401 | Unauthorized | المستخدم الذي يحاول الوصول لشيء ما داخل الـ Server ليس مصرح له بأي شكل من الاشكال، بمعنى ان الـ Server لا يعرف أي شيء عن هذا الشخص |
| 403 | Forbidden | المستخدم الذي يحاول الوصول لشيء ما داخل الـ Server مصرح له بالوصول لبعض الامور داخل الـ Server، لكن محظور عليه بعض الامور الأخرى، بمعنى ان الشخص لديه بعض الصلاحيات داخل الـ Server وإن حاول تجاوزها سيعطيه الـ Server أجمل 403 له لينبه ان هذا الشيء محظور عليه |
| 404 | Not Found | لم يتم العثور على البيانات المطلوبة |
| 409 | Conflict | لم تنجح العملية بسبب حصول تعارض أو تناقض في البيانات الجديدة والقديمة لسبب ما وغالبا ما يكون السبب هو أنك تريد إضافة بيانات موجودة بالفعل او تريد تفعيل حساب مفعل مسبقًا وهكذا |
| 410 | Gone | تم حذف البيانات بشكل دائم، بمعنى ان البيانات التي تحاول الوصول اليها لم تعد موجودة على الـ Server بأي شكل من الاشكال بمعنى أنها كانت موجودة مسبقًا لكن الآن حذفت بشكل دائم دون رجعة |
| 429 | Too Many Requests | لقد قمت بإرسال الكثير من الطلبات في وقت قصير، فأحيانًا يكون هناك حد معين لعدد المحاولات المسموح بها، لتخفيف الهمل والضغط او تقليل محاولات الاختراق |
| 500 | Internal Server Error | خطأ مجهول من ناحية الـ Server، بمعنى ان هناك شيء ما الـ Server لن يحسب له اي حسبان بالتالي رمز الحالة الافتراضي لأي شيء خطأ مجهول يكون 500 |
عليك استخدام كل واحدة في مكانها الصحيح فعلى سبيل المثال عندما يحاول صديقك الـ Frontend احضار بيانات مستخدم معين
ان كان هذا الشخص موجود فأرسل 200 OK ليعلم الـ Frontend ان الشخص موجود والقيمة الراجعة من طلب الـ API ستكون بيانات هذا الشخص
وان كان هذا الشخص غير موجود فأرسل 404 Not Found ليعلم الـ Frontend ان الشخص غير موجود
والقيمة الراجعة ستكون بها بيانات عن نوع الخطأ بالتفصيل وغالبا ما تكون رسالة توضح ذلك بأن الشخص غير موجود
استعمال الرمز الصحيح للحدث الصحيح يجعل التعامل مع الـ API الخاص بك سهل وسلس
وعدم فعل ذلك سيجعل التعامل مع الـ API صعب وغير مريح، فتخيل أنك ترسل دائما 200 في كل حدث حتى وان كان هناك مشكلة وبيانات غير موجودة
او ترسل رمز 400 مع كل خطأ أو مشكلة تحدث حتى وان كانت البيانات غير موجودة أو الشخص غير مصرح به
فالمفترض انك ترسل 404 ان كانت البيانات غير موجودة و 401 أو 403 إذا كان الشخص غير مصرح به
لذا الصحيح أنك يجب ان ترسل الرمز المناسب للحدث الذي يناسبه
أو عندما يظل يطلب بيانات بشكل ناجح وكل مرة يحصل على 200 ثم فجأه يحصل على 429 To Many Requests هنا سيعرف تلقائيًا أن البيانات صحيحة لكنه تجاوز عدد الطلبات المسموحة له
لمزيد من التوضيع عن الفرق بين
401و403يمكنك قراءة هذه الفضفضة الصغيرة من هنا الفرق بين الـ 401 و 403 في الـ RESTful API
توحيد مسميات الـ Endpoints
يأتي الـ Restful API ويفرض لك نظام تسمية خاص من أجل توحيد المقامات في جزء التسمية
الـ Endpoints يجب أن تعبر عن أسماء وليس أفعال
من المبادئ التي ينادي بها الـ Restful API هو أن تكون مسميات الـ Endpoints تعبر عن اسماء وليس افعال، بمعني أننا لو أردنا أن نحضر كل المستخدمين فسنجعل الـ Endpoints أو إنشاء مستخدم جديد هكذا:
- Good ✔️, Get All Users (simple and clear)
GET /api/users
POST /api/users
فتلك الطريقة واضحة وبسيط GET -> Users استخدما الـ method الذي يعبر عن احضار البيانات GET ثم قلنا له اسم المورد Resource الذي نريد البيانات منه users وهذا الشكل البسيط GET api/users هو ما يتبناه الـ Restful API لتوحيد المقامات على كل شيء
نفس الأمر مع باقي الـ methods الأخرى مثل POST عندما نريد إنشاء مستخدم فقط نغير الـ method من GET إلى POST ليعبر بكل بساطة على اننا نريد إنشاء أو إضافة مستخدم جديد
- Get all users from users resource
GET /api/users
- Add new user to users resource
POST /api/users
- Get all article from articles resource
GET /api/articles
- Update article number 25 from articles resource
PATCH /api/articles/25
...etc.
ولا أريدك أن يصل بك الحال وتقوم بفعل شيء قبيح كهذا:
- Bad ❌, (any one can make its own names)
GET /api/users/get-all-users
GET /api/users/all
GET /api/get-users/all
POST /users/create-new-user
POST /users/add-user
POST /users/addNewUser
POST /user/add
POST /user/createOne
هذه الطريقة كما ترى عبارة عن أن كل شخص يبتكر المسمى الذي يعجبه ولا يوجد معيار يستند له في المسميات بل يبتكر في كل مرة شكل
وهذا عكس ما ينادي به الـ Restful API في أننا نوحد المسميات ليسهل على باقي المطورين توقع شكل الـ API ويسهل عليهم التعامل معه
إذًا، السلوك الصحيح هو استخدام اسم الـ Resource مع الـ method المناسب الذي يعبر عن الغرض الذي ترغب في تحقيقه
نحن نسمي المستخدمين والمقالات أو اي كيان يخزن في قاعدة البيانات بمصطلح الموارد Resources
وفي الـ Restful API نسميها في صيغة الجمع في الـ Endpoints هكذا /users, /articles, ... إلخ
ولا نستخدم صيغة الجمع في كل شيء بالطبع، ان كان لدينا عنصر وحيد نريده مثل عنوان مقالة معينة فلا نجمعها بل نجعلها هكذا ببساطة
يمكنك حتى أن تقراه بشكل واضح ومنطقي فعلى سبيل المثال في Endpoint كـ GET /articles/25/title يمكنك قراءته كالآتي اذهب إلى جميع المقالات ثم أحضر المقالة رقم 25 ثم احضر لي عنوانها
الجدول التالي يوضع المسميات الموحدة التي يريدها الـ Restful API عندما نتعامل مع الـ method المختلفة لتنفيذ العمليات كالإضافة والتعديل والحذف وغيرها
| Endpoint | الوصف |
|---|---|
| GET /api/users/ | احضار جميع المستخدمين |
| GET /api/users/25 | احضار المستخدم صاحب الـ id رقم 25 |
| POST /api/users/ | إنشاء مستخدم جديد |
| PUT /api/users/25 | استبدال بيانات المستخدم صاحب الـ id رقم 25 |
| PATCH /api/users/25 | تعديل بيانات المستخدم صاحب الـ id رقم 25 |
| DELETE /api/users/25 | حذف المستخدم صاحب الـ id رقم 25 |
تسمية الــ Resources المتداخلة
المقصد هنا انك إذا اردت ان تحضر لي كل المقالات التابعة للمستخدم صاحب الـ id رقم 25
أو تريد إضافة مقالة جديدة له ؟ أو ... إلخ
| Endpoint | الوصف |
|---|---|
| GET /api/users/25/articles | احضار جميع المقالات التابعة للمستخدم صاحب الـ id رقم 25 |
| GET /api/users/25/articles/5 | احضار المقالة رقم 5 التابعة للمستخدم صاحب الـ id رقم 25 |
| POST /api/users/25/articles | إنشاء مقالة جديدة للمستخدم صاحب الـ id رقم 25 |
| PUT /api/users/25/articles/5 | استبدال بيانات المقالة رقم 5 التابعة للمستخدم صاحب الـ id رقم 25 |
| PATCH /api/users/25/articles/5 | تعديل بيانات المقالة رقم 5 التابعة للمستخدم صاحب الـ id رقم 25 |
| DELETE /api/users/25/articles/5 | حذف المقالة رقم 5 التابعة للمستخدم صاحب الـ id رقم 25 |
فصل الكلمات في الجملة بـ -
عندما يكون لديك جملة أو اسم مكون من مجموعة كلمات، نستخدم الشرطة - لفصل هذه الكلمات ببعضها البعض
هذا يسهم في جعل النصوص أكثر وضوحًا اثناء قراءتها
لاحظ هذا الوضوح في الامثلة التالية
- Clear 👌
GET /api/users/25/devices-tokens
PATCh /api/users/25/notification-settings
GET /api/users/25/articles/top-five-rating
- Unclear 🤔
GET /api/users/25/devicesTokens
PATCH /api/users/25/notificationSettings
GET /api/users/25/articles/topFiveRating
وضع أو عدم وضع / في نهاية الـ Endpoint
مسألة إذا كان يجب أن تحتوي روابط الـ Endpoint على / في نهايتها أم لا هو موضوع ستجد جدلًا كبيرًا فيه،
لان مسألة ان تضع / في نهاية الـ Endpoint أم لا يحدث بعض المشاكل
ففي بعض الاحيان عند وضع / في نهاية الـ Endpoint هذا يجعله Endpoint مختلف تماما وبعض المواقع أو لغات البرمجة أو الانظمة قد تعتقد انه مسار مختلف بمعنى أن /api/users لا يساوي /api/users/ بالنسبة لها
سيجد الـ Frontend نفسه يحصل على Not Found 404 أو Internal Server Error 500 لانه يضع أو لا يضع / في نهاية الـ Endpoint
لكي تتخيل لما وكيف تحدث المشكلة فأنظر للمثال التالي:
// GET /api/users/
if (req.url === '/api/users') {
res.send(/* users data */);
} else {
res.send('404 Not Found');
}
لاحظ اننا طلبنا /api/users/ لكن الـ Backend أعطانا 404 Not Found لاننا وضعنا / في نهاية الـ Endpoint
لذا أنت كـ Backend يجب أن تختار إحدى الطرق سواء بوضع / في نهاية الـ Endpoint ام لا
وتلتزم بها وتقوم بتحويل الأشخاص بشكل تلقائيًا للطريقة التي اخترتها بشكل سلس إذا استخدموا الأسلوب الأخر
// GET /api/users/
if (req.url.at(-1) === '/') req.url.pop();
if (req.url === '/api/users') {
res.send(/* users data */);
}
الأمثلة السابقة للتوضيح لا أكثر
في الغالب ستجد إطار العمل Framework الذي تستخدمه يهتم بهذا الموضوع أو به بعض الاعدادات التي يجب ان تقوم بتفعيلها لذا ابحث عن الأمر واختار ما يناسبك
وضع اصدار للـ API الخاص بك | API Versioning
المقصود بـ API Versioning هي عمل اصدارات للـ API الذي تقوم ببناءه
وهذا سيسهل عليك تنظيم الـ API أو إضافة تغيرات جزرية له بكل ببساطة من غير ان تأثر على المطورين الاخرين الذين يستخدمون الـ API الخاص بك
على سبيل المثال عندما تقوم ببناء Restful API وتراه قد وصل لمرحلة مستقرة وجاهز لتنشره ليستخدمه اناس اخرون تبدأ بتحديده على انه الاصدار الأول بوضع /api/v1 في رابط الـ API
بالتالي عندما تقوم بعمل تغير جزري كبير على الـ API تقوم فقط بتغير الاصدار لـ v2
بالتالي لن تقوم بافساد أي تطبيق يستعمل الـ API لانهم سيكونون يستعملون الاصدار السابق ولن يتأثروا بأي تغير
وعندما يريدون ان يحدثوا الـ API فقط سيقومون بقراءة ومعرفة ما الجديد وعندما يكونون مستعدون للتحديث والتأقلم على التغيرات الجزرية الجديدة سيقومون بتغير الاصدار
رفع الاصدار وتغيره يكون فقط عندما يكون هناك تغيير جزري سواء تغير في شكل الرد أو تعديل في المسميات والعناصر أو حتى حذف بعض الـ Endpoint أو إلى اخره من الاسباب
لكن في حين انك تصلح مشكلة ما أو تضيف شيء جديد لن يأثر باي شكل من الاشكال على من يستخدموا الـ API فلا داعي لتغير الرقم
أحيانا بعض الـ API قد تكون تدعم ثلاث خانات لتعريف الاصدار /api/v1.2.3
واسلوب الخانات الثلاثة اسلوب شائع جدًا لوضع اصدار لأي شيء سواء API أو مكتبة أو مشروع
الخانة الأولى تدل على رقم الاصدار بشكل عام وتتغير عندما يحدث تغير جزري
الخانة الثانية تدل على تغير طفيف لا يأثر على شيء أو إضافة Endpoint جديدة أو عناصر جديدة
الخانة الثالثة تعبر عن اصلاحات لمشاكل وتحسينات
وضع الاصدار للـ API ليس شرطًا أن يكون في رابط الـ API
فأحيانا يفضل البعض وضعه في الـ headers أو في الـ query params
- URL:
/api/v1/users
- Query Params:
/api/users?version=1
- Header:
/api/users
"headers": {
"Accept": "version=1.0"
}
Idempotent Methods
يوجد مفهوم شائع في عالم الـ RESTful API وهو مفهوم الـ idempotent وهو مفهوم رياضي في الاصل يسمى تساوي القوى وهو عندما تقوم بتكرار نفس العملية عدة مرات وتحصل على نفس الناتج في كل مرة 1 * 1 * 1 ... * 1 = 1
فبالتالي الـ idempotent في عالم الـ RESTful API عندما نكرر نفس الطلب للـ API عدة مرات ونحصل في كل مرة على نفس النتيجة وهذا يعني أنه بغض النظر عن عدد مرات تكرار طلب، تظل النتيجة ثابتة
فعلى سبيل المثال عندما يقوم أحدهم بعمل GET /api/users/25 10 مرات فإنه يحصل على نفس النتيجة في كل مرة
فهنا يمكننا نقول أن اي عملية GET تكون idempotent
لاننا عندما نكرر أي طلب GET مرة واحدة أو عشر مرات، النتيجة دائمًا هي نفسها
ماذا عن باقي الـ methods مثل POST أو DELETE؟ فكر قليلًا قبل ان تكمل
عندما نكرر POST /api/users 10 مرات هل سنحصل على على نفس النتيجة في كل مرة ؟
أو عندما نكرر DELETE /api/users/25 10 مرات هل سنحصل على على نفس النتيجة في كل مرة ؟
| method | هل هو idempotent؟ |
|---|---|
| GET | ✔️ |
| POST | ❌ |
| PUT | ✔️ |
| PATCH | ❌ |
| DELETE | ✔️ |
ما رائك باختبار ذكاء أو تحدي تحدي بسيط وهو محاولة استنتاج كل سبب لكل method من الجدول السابق لما هو idempotent أو لا
حسنًا هذا تحدي يمكنك أن تتوقف عن القراءة وتفكر قليلًا أحضر ورقة وقلم وفكر واستنتج كل سبب ثم أرجع وأكمل القراءة الجائزة ستكون ... ااا ممم .. تمرينات ذهنية مجانية .. أو أو تمرين لمعدل الذكاء والاستنتاج لديك 😁
حسنًا لاحظ التالي GET، PUTو DELETE هم idempotent، بينما POSTو PATCH ليست كذلك
حسنًا لنرجع مفهوم الـ idempotent وهو معناه أن تكرار الطلب لن يؤدي إلى تغيير في حالة الشيء المعين
GET: تكرار طلب استحضار البيانات من الـAPIلن يغير البيانات الموجودة في الـServerبل ستظل كما هي وسيتم ارجاع النتيجة نفسها وبالتالي فهوidempotent ✔️POST: تستخدم في الغالب لإنشاء بيانات جديدة في الـServerلذا تكرار طلب إنشاء نفس البيانات10مرات سيتم إنشاء هذه البيانات10مرات
أيضًا الأمر لا نحصر على إنشاء بيانات جديدة بل يمكن أن تقوم عمليات الـPOSTإلى تأثيرات مختلفة قد تتضمن إرسال إشعارات وإجراء تغييرات داخل قاعدة البيانات لبيانات اخرى متعددة أو تنفيذ إجراءات مختلفة في كل مرة
لذا فتكرار عملية الـPOSTعدة مرات لا يؤدي الى نفس النتائج لذا بكل وضوح أنها ليستidempotent ❌PUT: يستخدم لاستبدال بيانات معينة داخل الـServer، لذا تكرار طلب الـPUTعدة مرات سنحصل دائما على البيانات الجديدة الذي استبدلناها
فعلى سبيل المثال عندما نريد استبدال بيانات عنصر معين كررنا لطلب10مرات
في المرة الأولى، سيتم استبدال البيانات القديمة بالبيانات الجديدة ثم في المرة التالية سيقوم باستبدال البيانات الموجودة بنفسها أي بنفس ذات البيانات في كل مرة وهكذا مع باقي المرات
لذا فتكرار عملية الـPUTعدة مرات يؤدي الى نفس النتيجة وهي استبدال الموجود أيً ما كان بالجديد لذا فهيidempotent ✔️PATCH: تستخدم للقيام بتعديلات جزئية على بيانات أو على عنصر معين سواء بتغير حالته او إضافة منتج ما داخل سلة المشتريات أو تطبيق كوبون خصم على المشتريات بالتالي عندما نكرر هذه العمليات عدة مرات فستحصل على نتائج مختلفة- تغير حالة عنصر ما
10مرات قد يؤدي لتغيرات مختلفة أو نتائج مختلفة كإرسال إشعارات أو إجراء تغييرات داخل قاعدة البيانات لبيانات اخرى متعددة - تكرار إضافة منتج جديد في سلة المشتريات
10مرات سيؤدي لتكرار هذا المنتج داخل المنتج في كل مرة - تكرار تطبيق كوبون خصم على المشتريات
10مرات سيؤدي لتغير المجموع الكلي للمشتريات في كل مرة لذا فتكرار عملية الـPATCHعدة مرات لا يؤدي الى نفس النتيجة لذا فهي ليستidempotent ❌
- تغير حالة عنصر ما
DELETE: عند حذف عنصر معين10مرات ففي المرة الأولى سيتم حذف العنصر بنجاح وفي المرة التالية قد تحصل علىNot Found 404بأنه حذف بالفعل لكن لا يوجد تغيير مختلف سيحصل في الـServerلان في كل الاحوال العنصر سيكون محذوفًا في نهاية الأمر
لذا فتكرار عملية الـDELETEعدة مرات يؤدي الى نفس النتيجة بأن العنصر سيكون محذوفًا لذا فهيidempotent ✔️
معرفتنا بأن الـ method المعينة تكون idempotent أم لا تفيدنا باتخاذنا بعض الاحتياطات
فعلى سبيل المثال نعرف الان أن تكرار عمليات مثل POST أو PATCH بنفس البيانات تؤدي الى عواقب وخيمة وتغيرات مختلفة داخل قاعدة البيانات خاصتنا
كالتكرار او خطأ في الحسابات او تغيرات غير مقصودة مثل ما وضحنا سابقًا
لذا فنحن يجب أن نتخذ اجراءات معينة لتعامل مع تكرار هذه العملية فعلى سبيل المثال:
- عندما ننشيء مستخدم جديد نتأكد انه غير موجود مسبقًا
- عندما نضيف منتج جديد نتأكد بأننا لم نضف هذا المنتج مسبقًا
- عندما نطبق كوبون خصم على المشتريات نتأكد بأننا لم نطبق هذا الكوبون مسبقًا
- عندما ... نتأكد ... لكي لا يحدث تكرار أو لبس أو خطأ ما
- ... إلخ
على عكس الـ GET أو DELETE فلا ضرر في التكرار لانهما لن يضر الـ Server بهذا التكرار وليس لهما اي تغيرات جانبية في اي مكان اخر في قاعدة البيانات
فتكرارهما ألف مرة سيحدث نفس الناتج عندما نستدعيهما لأول مرة
توحيد شكل الـ Response
من أهم الأمور التي يجب أن تراعيها وأنت تبني الـ Restful API أن يكون شكل الـ Response الراجع للـ Frontend موحد وثابت ومتناسق
لقد اتفقنا على استخدام الـ JSON لتمثيل البيانات لكن الآن نريد ان نوحد هذا الشكل الذي سيتلقاه الـ Frontend
أنت كـ Backend يمكنك ان تبتكر الشكل الذي يناسبك او يناسب المشروع او يمكنك ان تتفق مع صديقك اللدود الـ Frontend على شكل ثابت او حتى ان تستخدم شكل شائع متعارف عليه
لنفترض أنك قررت ان تستخدم الأشكال البسيطة التالية للـ Response في مشروعك:
- عندما تقوم بإرسال شيء واحد فقط بنجاح على سبيل المثال مقالة واحدة سيكون الشكل كالتالي:
{
"status": "success",
"code": 200,
"message": "Get one article successfully",
"data": {
"id": 1,
"title": "Article title",
"description": "Article description",
"content": "Article content"
}
}
- وعندما تقوم بإرسال مجموعة من الأشياء بنجاح على سبيل المثال مجموعة من المقالات سيكون الشكل كالتالي:
{
"status": "success",
"code": 200,
"message": "Get All articles successfully",
"length": 2,
"data": [
{
"id": 1,
"title": "Article title",
"description": "Article description",
"content": "Article content"
}
{
"id": 2,
"title": "Article title #2",
"description": "Article description :)",
"content": "Article content ..."
}
]
}
- وعندما يحدث خطأ ما على سبيل المثال لم يتم العثور على المقالة المطلوبة سيكون الشكل كالتالي:
{
"status": "error",
"code": 404,
"message": "Article not found"
}
الآن سيقوم صديقك اللدود الـ Frontend بتجهيز نفسه وعمل احتياطاته ليستقبل هذه الاشكال بحالاتها المختلفة
في الغالب سيقوم بعض الكلاسات والدوال الذي سيستخدمها لتحويل الناتج الراجع من الـ Response الذي سيتلقاه، ويحوله من الـ json إلى شكل او object يناسب اللغة الذي يستخدمها ليستطيع التعامل معها او يتوقع الناتج بسهولة
الآن ماذا سيحدث اذا قررت انت كـ Backend تغير الشكل قليلًا ؟ أو تغير كلمة message إلى Message تغير بسيط
لكن صديقك اللدود لا يعرف هذا التغير وستجده يصرخ ويدور حول نفسه ولا يعرف السبب
واذا اخبرته وكان التعديل بسيطًا ربما يكون التغير من عنده بسيطًا كذلك ويفضل ان تخبره قبلها وتتفق معه على كل تغير سيحصل حتى وان كان بسيطًا جدًا
لانه ان كان تغيرًا كبيرًا كتغير في شكل وعناصر المقالة أو تغير جزري في الشكل الذي كنتما قد اتفقتما عليه سيكون هذا له عواقب وخيمة لانك ستضطر لتغير ذلك في الـ Frontend سواء في منصات المشروع على الويب والهواتف وقد يكون تغيرًا كبيرًا ويحتاج وقت قد يصل لعدة ايام لتعديله واختبار كل شيء
لذا يجب ان تتفق معه على شكل ثابت وتقلل هذه التغيرات على قد المستطاع
لانك قد تجده في يوم من الأيام يمسك فأسه الحاد وينظر إليك بنظرات غير مريحة
لذا التزم دائمًا بشكل ثابت وموحد لجميع الـ Responses التي سترجعها
وتقلل من التغيرات عليه خصوصًا التغيرات الجزرية لذا قبل تنفيذ أي شيء يجب ان تدرسه وتتناقش مع الجميع للوصول لشكل ثابت يريحكم ويكون سلس ويخدم الافكار او المشروع الذي تعملون عليها
وأيضًا لكي لا تفقد رأسك
لانه كما قلنا يسهل على المطورين في الـ Frontend فهم الـ Response ومعالجته والتعامل معه
ويقلل من الأخطاء والمشكلات في الـ Frontend إلى حد كبير بالتالي عملية التطوير تكون أسرع وسلسة
التعامل مع الأخطاء بشكل صحيح | Error Handling
في عالم البرمجة، لا تسير الأمور دائمًا وفقًا للمطلوب أو المتوقع، لأنه دايمًا ما ستحدث الأخطاء لأسباب مختلفة، مثل إدخال بيانات غير صالحة أو غير مطلوبة أو مشاكل في الـ Server أو أخطاء في الكود نفسه كتبتها أنت لأنك إنسان وقد تخطيء في بعض الأحيان وهذا شائع وطبيعيًا
لذا يُعدّ معالجة الأخطاء أي الـ Error Handling من أهم الخطوات في عملية تطوير أي API
عندما يحدث أي خطأ لى سبيل المثال قام المستخدم بإدخال بيانات خاطئة يجب عليك كـ Backendأن توفر معلومات مفيدة وواضحة لصديقك الـ Frontend أو للمطورين الذين سيستعملون الـ API
لكي يستطيعوا فهم الخطأ أي كان ويتعاملون معه بحسب نوعه ويعرضون رسالة توضحية للمستخدم تشرح له الخطأ ليصححه أو ليتخذ إجراء معين
وعرض معلومات كافية ذات صلة بالخطأ الذي حدث يساعد على فهم الخطأ والتعامل معه بسرعة
ويجب أن ترجع دائمًا ترجع الـ Status Code المناسب الذي يصف كل خطأ ونوعه
ولقد تكلمنا عن هذه الاكواد والى ماذا ترمز في الجدول السابق يمكنك ان ترجع له وتراجعه
وخلاصة الأمر أنك يجب أن ترجع خطأ بـ 404 Not Found عندما تكون فعليًا لم تجد الشيء الذي يبحث عنه المستخدم
و ترسل 400 Bad Request عندما يقوم المستخدم بإدخال بيانات خاطئة
و 401 Unauthorized عندما يحاول شخص غير مصرح له باستخدام الـ API الخاص بنا
و ... إلخ
تلك التفاصيل الصغيرة توضح للـ Frontend نوعية المشكلة الذي يتعامل معها
وأيضًا لا أظن ان صديقك سيحب أن يرى 200 OK بأن الدنيا جميلة برغم بأن هناك خطأ ولم يتم تنفيذ العملية، تتذكر الفأس؟
وأيضًا أحيانًا يحدث بعض الأخطاء من المستخدم عندما يدخل بعض البيانات وأحيانا يجب على الـ Backend أن يقدم تفصيلًا بالحقول التي وقعت فيها المشكلة
على سبيل المثال:
{
"status": "error",
"code": 400,
"message": "Bad Request ...",
"errors": [
{
"field": "name",
"message": "Name is required"
},
{
"field": "password",
"message": "Password must be at least 8 characters long"
}
]
}
وكما قلنا شكل الـ Response لعرض كيف سيكون الـ json الذي سيستقبله الـ Frontend مسؤوليته تكون عليك أنت كـ Backend أن تتفق معه وتشرح له الشكل كيف سيبدو ويجب أن يكون موحد وثابت والأمور التي ذكرناها سابقًا في توحيد الشكل
تتبع الأخطاء في مكان واحد | Monitoring
هناك شيء يدعى Monitoring وهو أن تراقب ما يحدث ومن الأمور البديهية عندما تصمم الـ API أن تعرف كل شيء يدور فيه وكل صغيرة وكبيرة تحدث عليه
لذا يمكنك نظام معين لكي تراقب وتتبع ما يحصل لذا تقوم بعمل Logger System وهو أنك تقوم بتخزين معلومات كل Request في ملفات داخل الـ Server الخاص بك
بالتالي يمكنك في أي وقت تصفح تلك الملفات لمعرفة كل Request تاريخه وبياناته وهل حدث أي خطأ او مشكلة ويمكنك تتبع المشاكل بسهولة ومتى حدثت ما نوعها
وأنت من تحدد المعلومات والتفاصيل التي تخزنها في الملفات لذا اضمن ان تخزن تفاصيل تساعدك على تتبع ومعرفة المشاكل بسهولة مثل timestamp, method, status code, URL, error type, message, ... etc
وأن من تحدد كيف تقسم وتخزن الملفات وتسميها، يمكنك أن تسميها بحسب تاريخ اليوم ونوع المشكلة
شيء اشبه بهذا
Logs Folder
|
├ errors-2024-03-20.log
├ errors-2024-03-21.log
├ errors-2024-03-22.log
... etc
محتوى الملف كما قلنا يجب أن يكون به معلومات وتفاصيل كافية لمعرفة المشكلة بشكل لحظي
[Project Development] Error 2024-03-20 17:19:42 [Unauthorized] 401 POST /v2/api/auth/login Message: Email or password not correct
[Project Development] Error 2024-03-20 17:19:56 [Expression]
[Project Development] Error 2024-03-20 17:19:56 [Body] {"password":"123456789","email":"example@gmail.com"}
[Project Development] Error 2024-03-20 17:19:56 [StackTrace] UnauthorizedException: Email or password not correct
at callback (Project\file\file\file)
at Project\file\file\file
at Project\file\file\file
at ... etc.
ما الفرق بين Stateless و Stateful
حسنًا لدينا نوعين مختلفين أثناء تعاملنا أو بناء أي API وهو أما أن يكون Stateless أو Stateful
يمكننا القول:
Stateless API: هو أن الـServerلا يقوم بحفظ أي معلومات من أيRequestسابق، بمعنى أن كل طلب يكون مستقلًا ومعزولًا تمامًا عن الآخر، ولا يتشارك أو يحتفظ بأي معلومات عن أي عملية حصلت في أي طلب سابق
الأمر أشبه بأنك لديك ماكينة قهوة في الشركة تقوم بإعداد القهوة بحسب طلب كل شخص
هل هذه المكنة تتأثر بالطلباتك السابقة ؟ أو تحتفظ بأي طلب سابق
هل في يوم من الأيام عندما تطلب قهوة سادة ستجدها فجأه وضعت بعض السكر بمجرد أنك في المرة السابقة طلبت قهوة عليها معلقتين سكر ؟
لا، ماكينة القهوة بطبيعتها كل طلبية تكون مستقلة عن جميع طلباتك الأخرى، بمعنى أنها Stateless
يمكننا أن نتخيل الأمر بشكل مبسط هكذا:
function prepareCoffee(req, res) {
const order = req.body;
// Check if ingredients are available in the database or not
const isAvailable = checkIngredientsAvailability(order);
if (!isAvailable) throw new Error('Ingredients not available');
// Prepare the coffee
const coffee = prepare(order);
res.send(`Coffee prepared: ${coffee}`);
}
لنتخيل أن Request يستدعي تلك الدالة البسيطة
هل ترى أي تداخل قد يحصل بين كل Request أو أي معلومات تم جلبها من Request سابق وبناءًا عليه تم اجراء عملية معينة ؟
لا، فكل ما تراه هو دالة بسيطة تقوم بفحص المكونات ان كانت موجودة في قاعدة البيانات ام لا ثم تجهز لك قهوتك الجميلة
ولا يوجد أي شيء يتعلق بجلب معلومات سابقة من Request سابق أو ما شابه، لذلك تلك الدالة تعد Stateless
Stateful APIهو أن الـServerيقوم بحفظ معلومات من كلRequestسابق داخلSessionأي جلسة وجميع الطلبات التي تحصل في الـServerتتشارك وتحتفظ بمعلومات عن كل عملية سابقة في إطار تلك الجلسة
الأمر اشبه بأنك تقوم بالاشتراك في نادي لتحقيق الأحلام وهذا النادي يحقق لك 10 أحلام في كل جلسة
وطريقة عمل هذا النادي تكون كالأتي:
- تحتاج لتقديم طلب تسجيل لجلسة في هذا النادي
- كل جلسة لها مدة معينة على سبيل المثال أسبوع واحد فقط
- في أثناء الجلسة الخاصة بك في النادي تستطيع طلب
10أحلام فقط من النادي لتحقيقها - عندما تنتهي مدة الجلسة أو عندما تنتهي عدد الطلبات تحتاج للتسجيل مجددًا
بعد تسجيلك في النادي يقوم أعضاء النادي بتعليق صورتك على حائط النادي وكتابة أنك لديك 10 طلبات فقط لمدة أسبوع واحد
لذا أي طلب ستطلبه في النادي سيتم التعرف عليك أولًا من خلال الحائط، هل انت فيه أم لا
ثم سيتم تنفيذ طلبك بتحقيق حلمك الجميل ثم سيتم خصم عدد الطلبات المتاحة لك
هل لاحظت كيف قام النادي بحفظ بيانات الشخص في طلبه الأول على حائط النادي
ثم مع كل طلب يتم النظر إلى الحائط وبناء على المعلومات التي به يتم خصم وعمل عملية ما ؟
بالتالي كل طلبية تعرف ما حصل في الطلبيات السابقة وعدد المحاولات وهل انت سجل سابقًا أم لا
فكل الطلبيات تتشارك المعلومات مع بعضها في هذا الحائط النادي وتحدث البيانات الذي فيه وتتخذ اجراء بحسب حالة الحائط
وهذا النوع بحيث أن كل طلب يتشارك المعلومات التي تحصل في كل الطلبات السابقة في إطار جلسة معينة، فهذا ما نسميه الـ Stateful
لنوضح هذا الكلام بكود يحاكي هذه الخطوات لتتضح الأمور بشكل أفضل:
function subscribeToTheDreamComeTrueClub(req, res) {
// Get the user that wants to subscribe to the club
const user = getUserDate(req);
// Create a temporary session for this user
createSessionForUser(user);
res.send('You have successfully subscribed to the dream come true club');
}
لاحظ هنا أنه تم إنشاء Session للمستخدم الذي سجل في هذا النادي
وهذه الجلسة تخزن في الذاكرة المؤقتة الخاصة بالـ Server وليس في قاعدة البيانات
بالتالي كل Request يستطيع الوصول لكل الجلسات الموجودة ذاكرة الـ Server بسهولة
function makeADreamComeTrue(req, res) {
// Get the session of the requested user
const session = getUserSession(req);
if (!session) throw new Error('You are not subscribed to the club yet');
// Check if the session expired or not
const isSessionExpired = checkIfSessionExpired(session);
if (isSessionExpired)
throw new Error(
'Sorry your session has expired, please subscribe to the club again'
);
// Check if the user has remaining requests or not
const hasRemainingRequests = checkIfUserHasRemainingRequests(session);
if (!hasRemainingRequests)
throw new Error(
'Sorry you have no remaining requests, please subscribe to the club again'
);
// Make the dream come true
makeDreamComeTrue(session);
// Decrease the remaining requests
decreaseUserRemainingRequests(session);
res.send('Your dream has come true!');
}
هنا يتم جلب الـ Session الحالية الموجودة في الذاكرة المؤقتة الخاصة بالـ Server كما قلنا
وهذه الجلسة يتم التعديل عليها والبيانات التي فيها من كل Request مر على هذه الجلسة
فكما ترى فنحن نتحقق أولًا هل هناك جلسة مفتوحة لهذا المستخدم في هذا الـ Server أم لا
ثم هل انتهت صلاحيتها أم لا ثم هل نفدت عدد المحالات أم لا
ثم في النهاية يتم تحقيق الحلم وتعديل البيانات الخاصة بالجلسة
يمكننا توضيح الفروق بشكل افضل في هذا الجدول البسيط:
| Stateless | Stateful |
|---|---|
الـ Server لا يحتفظ بأي معلومات من أي Request ولا ينشيء أي Session |
الـ Server يحتفظ بالمعلومات من كل Request سابق داخل Session |
كل Request يحتوي على كافة المعلومات اللازمة لاستكماله بمفرده، دون الحاجة إلى معرفة سابقة عن Request أو Session معين |
يتم تبادل البيانات بين كل Request ويمكن أن يتأثر تنفيذ Request ما بحالة أو بمعلومات في Request سابق بمعنى أن الـ Server يكون على دراية بالسياق العام لمجموعة من الـ Requests داخل إطار Session معين |
طالما أن كل Request منعزل عن الآخر فستقل المشاكل والتعقيد ويسهل توسيعه وعمل أكثر من Server دون الحاجة إلى مزامنة معلومات المستخدم بين كل Request مع الآخر في كل Server بحكم أن الطلبات او العمليات منعزلة ولا تتداخل مع بعضها البعض |
يصعب تعديله او توسيعه بحكم أن كل Request يتداخل مع الآخر بالتالي قد تحصل مشاكل غير متوقعه ويجب أن يتم مزامنة معلومات المستخدم في كل Session خاصه به بين كل Request مع الآخر في كل Server |
| يستخدم في التطبيقات التي تتعامل مع كل طلب بشكل منعزل ومنفصل عن باقي الطلبات مثل أغلب التطبيقات اليوم مثل مواقع الشراء الالكترونية او مواقع التواصل الاجتماعي او المواقع الخدماتية بشكل عام | يستخدم في الوظائف التي يكون للمستخدمين جلسة معينة لمدة معينة مثل تطبيقات البنكية على سبيل المثال أو عندما تفتح جلسة مع تطبيقات خدمة العملاء وغيرها من تلك الأمور |
في مثال ماكينة القهوة يمكننا ان نضع 20 ماكينة في أماكن مختلفة وكل ماكينة منعزلة على الأخرى ولا تتطلب مزامنة الطلبات بينها |
في مثال نادي تحقيق الاحلام إذا أراد النادي أن يتوسع ويبني فروعًا في عدة أماكن يجب مزامنة المعلومات التي يحصل عليها كل نادي مع كل فرع وكل فرع يجب أن يعرف كل الاعضاء الذي يشتركون بالفروع الأخرى لكي يزامنوا بياناتهم في حالة قرر شخص أن يستكمل تحقيق أحلامه في فرع آخر |
ملحوظة: علينا أن نفرق بين الـSessionوDatabase
- الـ
Sessionهي مجرد معلومات مؤقتة تخزن في الذاكرة المؤقتة للـServerأثناء جلسة المستخدم الحالية وتكون جلسة مؤقتة وأثناء تلك الجلسة كل الـRequestsتتشارك المعلومات الموجودة في هذه الجلسة- واذا حصل مشكلة للـ
Serverوتم اعادة تشغيل الـServerفسيتم حذف جميع الـSessionالموجودة لأننا كانت في الذاكرة المؤقتة - واذا كان لدينا أكثر من
Serverفلن يكون هناك تنسيق بين الـSessionعلى كلServerلان كلServerلديه الذاكرة المؤقتة الخاصة به
- واذا حصل مشكلة للـ
- أما الـ
Databaseفهي تخزين دائم للبيانات على مستوى الـServerككل- إذا حصل أي مشكلة للـ
Serverوتم اعادة تشغيل الـServerفستبقى البيانات المخزنة في الـDatabaseكما هي - وإن كان لدينا أكثر من
Serverفسيتم مزامنة البيانات بينهم بسهولة لانهم متصلون بـDatabaseواحدة
- إذا حصل أي مشكلة للـ
في الـ Restful API يفضل استخدام الـ Stateless
ستجد دائًما ما يفضل استخدام الـ Stateless في الـ Restful API والسبب أن كل عملية او طلب تكون منعزلة تمامًا على باقي العمليات الأخرى لانها لا تخزن أو تستخدم مفهوم الـ Session
بالتالي ستقل المشاكل والتعقيد ويسهل توسيعه كما قلنا ويسهل عمل أكثر من Server دون الحاجة إلى أي معلومات في الذاكرة المؤقتة في كل Server
في مثال ماكينة القهوة عندما نفترض أن ماكينة القهوة تلك هي الـ Server الخاص بنا
قد يخطر ببالكم شيء ما ماذا اذا كان لدينا ماكينة قهوة تحضر بعض انواع القهوة الفاخرة لاشخاص معيين في الشركة بحسب صلاحيتهم ومنصبهم
فهكذا تلك الماكينة ستتطلب من المستخدم تسجيل بياناته لكي تستطيع المكنة التفريق بينهم وبين صلاحيتهم
حسنًا بحكم أن المكنة تلك Stateless بالتالي أن ادخل شخص ما بياناته ثم طلب قهوته ففي المرة القادمة عليه أن يدخل بياناته مجددًا
لأن المكنة لا تحتفظ ببياناتك السابقة في الذاكرة المؤقتة الخاصة بها
قد يكون المستخدمين مسجلين في قاعدة بيانات بالفعل لكن كل شخص عليه ان يدخل بياناته في كل مرة لتستطيع المكنة التحقق منه
فكيف نحل هذه المشكلة مع الحفاظ على كون المكنة Stateless ؟
يمكننا ان نقوم بعمل نوع من انواع بطاقات التعريف لكل شخص بالتالي عندما يطلب أحد ما قهوة من الماكينة
فيمكنه أن يدخل البطاقة التعرفية الخاص به للمكنة والمكنة ستتعرف على هذا الشخص بناءًا على البيانات المكتوبة في البطاقة
مفهوم البطاقة التعرفية نسميها Token وهي مجرد نص مشفر يحمل بيانات وهذا الـ Token لا يتم تخزينه في الـ Server
بل يكون مع المستخدم يرسله مع كل Request ليثبت أو يعرف هويته للـ Server
الـ Token يقوم صديقك الـ Frontend بتخزينه في الـ LocalStorage ويضعه في الـ headers مع كل Request عندما يرسل أي Request أو يمكننا أن نخزنه في الـ Cookies الأمر عائد لكما كـ Frontend و Backend كيف تديران الأمر
فكر بالـ Token على أنها بطاقة الهوية الخاصة بك التي ستستخدمها للحصول على فنجان القهوة الجميل الخاص بك
هيا لنرى أو نحاكي هذه العملية ككود ونشرحها لتتضح الأمور بشكل أفضل:
function prepareCoffee(req, res) {
// Check token is valid or not
const token = checkToken(req);
if (!token) throw new Error('Invalid token');
// Get user info
const user = getUserInfo(token);
// check if user is exist in database or not
if (!user) throw new Error('User not found');
const order = req.body;
// Check if ingredients are available in the database or not
const isAvailable = checkIngredientsAvailability(order);
if (!isAvailable) throw new Error('Ingredients not available');
// Prepare the coffee for specified user
const coffee = prepareOrderForUser(order, user);
res.send(`Coffee prepared: ${coffee}`);
}
لاحظ أن الدالة لم تتغير كثيرا، فقط الشيء الجديد أنها اصبحت تتحقق من صحة الـ Token
وتتعرف على المستخدم هل هو موجود في القاعدة البيانات أم لا
والدالة مازالت Stateless بحيث أن كل Request مازال مستقلًا بذاته
ولا يعتمد على إحضار أي معلومات من أي Request سابق أو من Session معينة
أضف Pagination لتسهل تصفح البيانات وتقليل حجمها
حسنًا عندما تصمم Restful API فمن المهم أن يحتوي على Pagination للبيانات التي لديك
والـ Pagination تعني انك تقسم البيانات خاصتك لاجزاء وتضع فيها شيء كنظام الفهرس لتصفح أي جزء من البيانات بسهولة
تخيل معي أنك لديك بيانات كثيرة على سبيل المثال ألف مستخدم
وكلما أراد الـ Frontend استحضار البيانات يجد نفسه يحصل على الألف مستخدم في كل مرة
فهذا لا يبدو منطقيًا لانه هكذا سيكون حجم البيانات كبير جدًا وغالبا الموقع لن يستطيع التعامل معها
لذا نلجأ لتقييد عدد البيانات الذي نرجعها بمعنى تقليلها بعدد معين
فبدلًا من أن نحصل على ألف مستخدم، نحصل على 10 فقط في كل مرة
ويمكنك جعل صديقك الـ Frontend يقوم بعمل بعض الـ Query ليستطيع التحكم بعدد البيانات التي سترجع له
والـ Query كما تعرف فهو ما نضعه في نهاية أي Endpoint و يكون بعد علامة الاستفهام ?
الأمر يبدو كهذا /endpoint?key=value
وعندما يريد صديقك الـ Frontend بتحديد عدد النتائج الذي يريدها أن ترجع له فيمكنك أن تنشيء متغير لنسميه limit و تجعله يعدله في الـ query
فإن أراد استحضار بيانات أول شخصين فيمكنه أن يقوم الـ query التالي /api/users?limit=2
الآن وإن أراد الحصول على الشخصيين التاليين ؟
كأنك تقول بعد ما قسمت الأشخاص لمجموعات من لشخصين فقط، نريد المجموعة الثانية من هذا التقسيم
هنا يمكنك أن تقوم بعمل متغير آخر لنسميه page وهي بعد ما يتم استخدم limit لتحديد عدد النتاج نريد أن نختار المجموعة التي نريدها
يمكنك أن تتخيل أن limit كأنها تقسم الناتج لصفحات و page تختار لك الصفحة التي تريدها
بمعني إن كان لديك 5 أشخاص فقط في قاعدة البيانات
فبعد ما يتم استخدم limit=2 لتقسيم لمجموعات من شخصين بهذا الشكل (1, 2) (3, 4) (5)
فهكذا يمكنك أن تتخيل إن قام صديقك الـ Frontend بتحديد page=1 بهذا الشكل /api/users?limit=2&page=1 فيتم استرجاع المجموعة الأولى المكونة من شخصين كما ترى (1, 2)
و /api/users?limit=2&page=1&page=2 هي المجموعة الثانية وهي مكونة من شخصين أيضًا (3, 4)
و /api/users?limit=2&page=1&page=3 هي المجموعة الثالثة والتي بها شخص واحد فقط (5)
بالطبع إن قام الـ Frontend بتجاوز عدد البيانات الموجودة وكتب page=50 فيمكننا أن تعطيه بيانات فارغة
هذا الاسلوب الذي يستخدم limit و page يسمى Page-Based Pagination
ستجد اساليب أخرى لكن الـ Page-Based Pagination أكثرهم بساطة وشيوعًا
من الاساليب الأخرى يمكننا عمل متغير offset وهو تحديد من أين تريد أن تبدأ
بمعنى أنك تقول على سبيل المثال أبدأ استحضر البيانات من عند الرقم 100 وأحضر لي 25 فقط
سيكون شكل الـ query هكذا /api/users?limit=25&offset=100 أحضر 25 مستخدم من بعد المستخدم رقم 100
أضف معلومات الـ Pagination في الـ Response
يفضل دائمًا أن تضع Pagination Metadata في الـ Response لتزويد الـ Frontend بكل المعلومات التي قام بها والتي يستطيع القيام بها في الـ query
فعندما يقوم بعمل التالي /api/users?limit=2&page=3، فيمكنك وضع بعض المعلومات الخاصة بالـ Pagination في الـ Response كالتالي
{
"status": "success",
"code": 200,
"message": "Get users successfully",
"data": [
{
"id": 5,
"name": "Omar",
"age": 35,
"email": "omar@domain.com",
"salary": 3000,
"country": "Egypt"
},
{
"id": 6,
"name": "Osama",
"age": 30,
"email": "osama@domain.com",
"salary": 5000,
"country": "Palestine"
}
],
"pagination": {
"total": 10,
"limit": 2,
"current_page": 3,
"total_pages": 5,
"next_page": 4,
"prev_page": 2
}
}
total: عدد العناصر الكلي في مجموعة البياناتlimit: عدد العناصر المعروضة في الصفحة الواحدةcurrent_page: رقم الصفحة الحاليةtotal_pages: عدد الصفحات الكليnext_page: رقم الصفحة التالية (إذا وجدت)prev_page: رقم الصفحة السابقة (إذا وجدت)
هكذا يمكنك أن تسهل على الـ Frontend استخدام هذه المعلومات ليعرف عدد البيانات والصفحات المتاحة وهل يوجد صفحة تالية أم لا
يمكنك تعديل معلومات الـ Pagination حسب احتياجاتك
تنوع الـ Query Parameters
وكما قلنا فإن الـ Query أوامر تكون في نهاية الـ Endpoint بعد علامة الاستفهام لإعطاء الـ Frontend بعض من أنواع التحكم على شكل البيانات الراجعة والتي يطلبها
ليستطيع عمل عمليات مخصصة للبيانات كما يحلو له، على سبيل المثال تريد جميع الأشخاص الذي يعيشون في مصر
أو تريد الأشخاص الذي يزيد راتبهم عن 3000 أو الأشخاص الذي أعمارهم 25
عمليات مخصص ومحددة مثل هذه نسميها Query
لذا يفضل دائمًا أنك كـ Backend أن تصمم بعض من الـ query parameters لتساعد من سيستخدمون الـ API بعمل تلك العمليات المعقدة بطريقة سلسة وبسيطة
البحث عن طريق بيانات معينة
فإذا كانت بيانات المستخدمين عندك تتكون من name، age، email، salaryو country فيمكنك أن تتيح للـ Frontend بعمل query ويبحث عن طريق هذه البيانات
فعلى سبيل المثال تريد الأشخاص الذين يعيشون في مصر سيكون شكل الـ query هكذا /api/users?country=Egypt
هكذا سيتم احضار الأشخاص الذين يعيشون في مصر فقط
مثال آخر أحضر الأشخاص الذي اعمارهم تساوي 30, سيكون الـ query هكذا users?age=30
أو تريد الأشخاص الذين يعيشون في فلسطين الذي يزيد راتبهم عن 3000 سيكون الـ query هكذا /api/users?salary=3000&country=Palestine
هكذا تساعد من سيستخدم الـ API على القيام بعمل عمليات مخصص كما يحلوا له بسلاسة كما ترى
ترتيب النتائج
يمكنك أن تقوم بإضافة المزيد من المتغيرات التي تزيد من سلاسة وسهولة عمل العمليات المعقدة
مثل ترتيب الناتج بناءًا على عنصر أو استبعاد بعض البيانات و غيرها من الأمور
فيمكنك عمل query parameters لنسميها sort وتكون المسؤولة عن ترتيب البيانات تصاعديًا اوتنازليًا بحسب العمر او الراتب اول أي عنصر معين
مثال /api/users?sort=age هكذا سيتم إحضار جميع الأشخاص ويرتبهم تصاعديًا بحسب العمر
ان أردت ترتيب تنازلي ؟ يمكنك ان تجعله يضع ضع علامة - هكذا /api/users?sort=-age هكذا يمكنك أن تحضر جميع الأشخاص ويرتبهم تنازليًا بحسب العمر
أحضر جميع الأشخاص الذي راتبهم يساوي 3000 ورتبهم تنازلي بحسب العمر, الـ query ستكون هكذا /api/users?salary=3000&sort=-age
تحديد واستثناء بيانات معينة
يمكنك أن تضع query parameters وتسميه fields على سبيل المثال وتجعل الـ Frontend يختار خانات معينة ليحضرها فمثلا نحن نريد الإسم فقط ولا نريد باقي البيانات
في هذه الحالة يمكننا متغير ولنسميه fields ثم نجعله يختار الخانات التي يريدها
مثال /api/users?fields=name سيحضر أسماء جميع الأشخاص فقط name وسيستبعد باقي الخانات كـ age، email، salaryو country
نريد الإسم والعمر فقط, تستطيع ان تجعله يختار أكثر من خانة مفصولة بـ , مثال /api/users?fields=name,age سيحضر أسماء وأعمار جميع الأشخاص
نريد كل البيانات ما عادا الراتب والدولة, هكذا - لكي نستثني بعض الخانات يمكنك أن تجعله يضع علامة users?fields=-salary,-country
ملحوظة:الـQueryشكلها وطريقتها وخواصها ومميزاتها أنت كـBackendمن يحددها
قد تقوم بعملAPIلا تدعم فيه أيqueryمن الأساس، أو تدعم بعض الأمور أو تزيد أمورًا يناسب مقتضى المشروع
تخزين البيانات التي عليها طلب مستمر بشكل مؤقت | Caching
الـ Cache من الأمور الموصى بها في بناء الـ Restful API وخصوصًا عندما يكون لديك ضغط كبير على الـ API في جلب بعض البيانات بشكل مستمر
لنفترض أنك تصمم API لموقع متخصص في بيع وعرض منتجات معينة، وعدد الزيارات لهذا الموقع يتخطى المئة ألف يوميًا ويوجد منتجات معينة عدد الزيارات لها تصل لألف زيارة كل ساعة او دقيقة
هل تتخيل أنك في كل مرة تحضر نفس المنتجات من قاعدة البيانات ألف مرة ؟
الإجابة بالتأكيد لا فهذا غير منطقي، لذا فنحن نحتاج لشيء نستطيع فيه تخزين المنتجات التي يتم طلبها بشكل مستمر
وهذا المكان يجب أن يكون جلب البيانات منه أسرع من طلبها من قاعدة البيانات
لأن العمليات على الـ Database قد تكون مكلفة إلى حد ما من ناحية السرعة أو الأداء وغيرها من الأمور
فتخيل معي أن الـ Database مكونة من أكثر من Server وعندما تقوم بعمل عملية معينة قد تحتاج إلى تعديل وعمل عمليات أو تعديلات أخرى في قواعد البيانات المختلفة، عندما يكبر المشروع فستدخل في دوامة الأنظمة الموزعة وتعدد الـ Server في كل مكان
لذا فمن غير المنطقي أن نطلب من قاعدة البيانات نفس البيانات كل ثانية لأننا هكذا نستهلك مصادرنا على شيء يتكرر باستمرار
هنا يظهر لدينا مفهوم الـ Cache وهو أنك تخزن نسخة مؤقتة للبيانات الأكثر طلبًا في ذاكرة الـ Server الأساسي أو في Server آخر وغالبًا من يكون Proxy Server أو باختصار يكون في مكان أقرب وأسرع لك من أنك تطلب البيانات من قواعد البيانات التي لديك
لاحظ هنا بعض الأمور أننا هنا نخزن البيانات بشكل مؤقت لأنها قد يتم تعديلها
بمعنى أنك لديك منتجات في قاعدة البيانات ونفس هذه المنتجات تم تخزينها في الـ Cache
ثم حصلت بعض التعديلات في هذه المنتجات في قاعدة البيانات أو تم حذفها
هكذا يجب علينا تجديد الـ Cache وتزويده بأحدث البيانات والتحديثات سواء تعديل أو حذف
لذا فهنا بعض الأمور التي يجل أن تراعيها وأنت تقوم بعمل Cache للـ Restful API
- بشكل افتراضي، قم بعمل
Cacheلطلبات الـGET، ما لم توجد استثناءات تستدعي عدم فعل هذا - عندما يطلب شخص ما منتج معين بـ
GETقم بالبحث عنه في الـCacheفإن كان غير موجود فأحضره من قاعدة البيانات وخزنه في الـCache- بعد احضار المنتج من قاعدة البيانات يمكنك أن تجعل عملية تخزين المنتج في الـ
Cacheتكونasynchronous
بمعنى أنك بعد ما أحضرت المنتج يمكنك إرسال الـResponseوتجعل عملية تخزينه في الـCacheتكون في الخلفية لكي لا تؤثر على سرعة الرد
- بعد احضار المنتج من قاعدة البيانات يمكنك أن تجعل عملية تخزين المنتج في الـ
- ضع دائما تاريخ صلاحية لكل شيء تقوم بعمل
Cacheولتكن المدة صغيرة جدًا كدقيقة أو دقيقتين - إن كنت ستجعله ينتهي بتاريخ معين فيفضل أن يكون التاريخ
UTCوليس تابع إلى أي تاريخ محلي - لا تقم بعمل
Cacheلطلبات الـPOSTفي غالب الأمر - في طلبات التعديل كـ
PUTوPATCH، البيانات التي قمت بتعديلها أو تغيرها تحتاج إلى أن تحدثها في الـCache - في طلبات الـ
DELETE، البيانات التي تم حذفها تحتاج إلى أن تزيلها من الـCacheكذلك - إن كنت عدلت بيانات منتجات معينة وتريد تحديث الـ
Cache، تأكد أولًا من أن المنتج موجود بالفعل فإن كان غير موجود ولا تقم باضافته وان كان موجود فحدثه
طرق حماية الـ API الخاص بك | API Security
لأكون صريح معك فهذا يعد من أهم وأكبر الأمور التي قد تمر بها وهو من الأمور التي تتغير وتكبر باستمرار
بمعنى أنه لا يوجد شيء يسمى اتبع هذه الطريقة في الحماية وأنت ستكون في أمان بشكل تام
لا توجد طريقة مثالية او ثابتة وهي دايمًا في تغير وأنا لست أفضل من يقول لك كيف تحمي نفسك
لذا في هذا الموضوع بالذات تحتاج لأن تتبع أفضل الطرق المتعارفة حاليا في وقتك الحالي وتسأل المحاربين العظام في المجال وتأخذ الخبرة والنصائح من التنانين التي في قمم جبال المجال في الحماية
وبسبب أنني ليست الشخص المناسب لكي اتكلم عن هذه الحماية حاليًا
سأقوم بعرض بعض النقاط المهمة التي أعرفها، وربما في المستقبل عندما أملك الخبرة الكافية والعلم الكافي سأستفيض في بعض تلك النقاط في مقالات منفصله
أحب أن أكون صريح معكم لاننا في النهاية بشر وأنا كما قلت أحب أن أنشر ما اعرفه وما تعلمته لأستفيد وأفيد الآخرين ولا أدعي الكمال أو المثالية
لذا الفقرة التالية سأقول ما لدي كنوع من رمي افكاري وما اعرفه وأحثكم على قراءة المزيد عن هذا الأمر وجمع نصائح وخبرة من أناس آخرين
API-KEY: أبسط الأمور هو وضع رمز أمان ندعوه بـAPI-KEYوهو رمز تضعه أنت كـBackendبشكل معين لكي لا يتمكن أحد من استخدام الـAPI- من لديهم الرمز هم فقط من يستطيعون استخدام الـ
API - قد يكون هناك
API-KEYواحد يتشاركه كل أعضاء ومطوريين الشركة - أو يوجد
API-KEYلمطورين الويب وآخر لمطورين الهواتف - أو تستخدم
API-KEYلكل بيئة عمل (بيئة التطوير، بيئة الإنتاج، إلخ) - أو يكون هناك أكثر من
API-KEYبصلاحيات مختلفة لمشاركته لمنظمات أو أشخاص معينين - يمكنك وضع تاريخ صلاحية للـ
API-KEYليقوم الشخص بتجديد الـAPI-KEYالخاص به قبل مدة معينة
- من لديهم الرمز هم فقط من يستطيعون استخدام الـ
Authorization/Authentication: بالطبع لا تريد لكل من هب ودب معه الـAPIأن يستخدمه كما يحلوا لهم
لذا يجب أن تطلب من الأشخاص أن يسجلوا في الـAPI- الشائع حاليا هو أن يقوم الشخص بالتسجيل ليحصل على
Token - تستطيع التفريق بين الأشخاص عن طريق الـ
Token - وعن طريق الـ
Tokenيتم معرفة الشخص والصلاحيات التي لديه على حسب وظيفة الشخص الذي يستخدم الـAPIهل هو شخص عادي او شخص مسؤول عن عمليات حساسة ومهمة فنعطيه صلاحيات أكثر وهكذا - يمكنك عمل نوعين الأول يسمى
Access Tokenوهو الذي يقوم بعمل وظيفة البطاقة الشخصية للشخص وصلاحيته تنتهي في وقت قصير جدا قد تصل لاسبوع او يوم او عدة ساعت - الآخر يسمى
Refresh Tokenوهو يستخدم فقط لتجديد الـAccess Tokenوصلاحيته تكون أكثر من الـAccess Tokenبقليل - قد مدة انتهاء صلاحية الـ
Access Tokenو الـRefresh Tokenبحسب كل مشروع قد تستخدم بعض المشاريع الـAccess Tokenفقط وتجعله ينتهي بعد5دقائق ولتجديده تحتاج لتجديده
أو يستغنون عن فكرة الـTokenبالكلية ويقوم بعملSessionوتنتهي الجلسة بعد5دقائق
- الشائع حاليا هو أن يقوم الشخص بالتسجيل ليحصل على
Rate Limiting: من المهم أن تضع حد معين لعدد الطلبات المسموح لها لكلEndpoint
بمعنى أن قام شخص ما بعملRequestللـAPIبشكل لا نهائي على مدار24ساعة، هذا كافي لجعل الـ Server يتوقف عن العمل في ثواني وستجد ان مصادرك استهلكت بشكل كبير في وقت قصير لأن هناك شخص ما يعين في غرفة مظلمة قرر ايقاع الـServerالخاص بك عن طريق عمل طلبات لا نهائية لأنك لم تقم بعمل حد معين لعدد الطلبات المسموح بها- قم بوضع حد معين لعدد الطلبات المسموح بها في كل
Endpoint
على سبيل المثال كلIPيستطيع استحضار المنتجاتGET /api/products25مرة في الساعة فقط - يمكنك حظر أي
IPمريب يقوم بتخطي العدد المسموح به في كل مرة
وضعنا حد لتسجيل الدخولPOST /api/loginوهو5محاولات في الساعة لكلIP
وكل مرة يفشل نفس الـIPبتسجيل الدخول لذا نقوم بحظره - حظر الـ
IPقد يكون يوم او نهائيًا بحسب الحالة - يمكنك اعطاء بعض الفرص قبل حظر الـ
IPالنهائي كجعل الشخص يقوم بنوع من التحديات من ناحية الـFrontend
أو ارسال رسالة لرقم الهاتف البريد الإلكتروني - الأمر يعتمد عليك وعلى فكرة المشروع وادارته لتحديد ووضع عدد المحاولات ومعرفة الـ
Endpointالحساسة لوضع طبقات حماية أكثر لها كالحظر بطرق مختلفة في مثل عمليات التسجيل وإنشاء الحساب أو المعلومات الحساسة
والـEndpointالشائعة والأمنة التي تحتاج لعدد محاولات اكثر ولا داعي للحظر فيها كجلب المنتجات وغيرها
الأمر يعتمد عليك وعلى فكرة المشروع كما قلت
- قم بوضع حد معين لعدد الطلبات المسموح بها في كل
Monitoring: من الأمور البديهية عندما تصمم الـAPIأن تعرف كل شيء يدور فيه وكل صغيرة وكبيرة تحدث عليه- يمكنك عمل
Logger Systemوهو أنك تقوم بتخزين معلومات كلRequestفي ملفات داخل الـServerالخاص بك
بالتالي يمكنك في أي وقت تصفح تلك الملفات لمعرفة كلRequestتاريخه وبياناته وهل حدث أي خطأ او مشكلة ويمكنك تتبع المشاكل بسهولة ومتى حدثت ما نوعها
وأنت من تحدد المعلومات والتفاصيل التي تخزنها في الملفات لذا اضمن ان تخزن تفاصيل تساعدك على تتبع ومعرفة المشاكل بسهولة - يمكنك بناء نظام تنبيه أو اشعارات في حالة حدوث شيء مشبوه اومشكلة متكررة يواجهها المستخدمون بكثرة يمكنك ان ترسل رسالة على بريدك الالكتروني تخبرك بأن هناك مشكلة متكرر تحصل أو أن الـ
Serverتوقف عن العمل
- يمكنك عمل
Serialization: من الأمور المهمة في الحماية هي عمل التحقق من البيانات التي ترسل في الـResponseوعملValidationلها قبل ان تدخل وتنفذ أي شيء داخل الـServer
وهذا المفهوم يعرف بـDTO(Data Transfer Objects) وهي طبقة تقوم بالتحقق أو التعديل على البيانات الداخلة للـServerوالتأكد من أنها مطابقة للشكل الذي نريده
ومفهوم الـSerializationيركز أيضًا على تنقيح وازالة البيانات المشبوهة التي قد تكون داخل الـRequest
على سبيل المثال قد يضع شخص ما كود أوscriptمعينة داخل أحد المدخلات لذا نحتاج لازالة ذلك الـscript
وهذا نوع من انواع الاختراق يدعىXSSيجب ان نراعيه ونحاول تجنبه
ملحوظة: كما قلت سابقًا هذه كلها مجرد عناوين وأمور ذكرتها وأنت مطالب بأن تبحث عنها بتعمق قليلًا ومعرفة أفضل الحلول والأمور الموصى بها في الوقت الحالي
هناك أمور أكثر مما ذكرت وتفاصيل أعمق وكل شيء يجب عمل حساب له ودراسة وغير ثابت ويختلف من مشروع لمشروع