بناء RESTful API موافق للمبادئ
السلام عليكم ورحمة الله وبركاته
الفهرس
- المقدمة
- ما هو الـ RESTful API ؟
- تمثيل وإرسال البيانات على هيئة JSON
- اختيار الـ Methods / Actions الصحيح في التعبير عن معناه
- إرسال Status Code يناسب الحدث أو النتيجة
- توحيد مسميات الـ Endpoints
- الـ Endpoints يجب أن تعبر عن أسماء وليس أفعال
- تسمية الــ Resources المتداخلة
- فصل الكلمات في الجملة بـ -
- وضع أو عدم وضع / في نهاية الـ Endpoint
- وضع اصدار للـ API الخاص بك | API Versioning
- Idempotent Methods
- توحيد شكل الـ Response
- التعامل مع الأخطاء بشكل صحيح | Error Handling
- تتبع الأخطاء في مكان واحد | Monitoring
- ما الفرق بين Stateless و Stateful
- أضف Pagination لتسهل تصفح البيانات وتقليل حجمها
- تنوع الـ Query Parameters
- تخزين البيانات التي عليها طلب مستمر بشكل مؤقت | Caching
- طرق حماية الـ API الخاص بك | API Security
المقدمة
في مقالة كيفية التعامل مع الـ 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": "[email protected]",
"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
توحيد مسميات الـ 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":"[email protected]"}
[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": "[email protected]",
"salary": 3000,
"country": "Egypt"
},
{
"id": 6,
"name": "Osama",
"age": 30,
"email": "[email protected]",
"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/products
25
مرة في الساعة فقط - يمكنك حظر أي
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
يجب ان نراعيه ونحاول تجنبه
ملحوظة
: كما قلت سابقًا هذه كلها مجرد عناوين وأمور ذكرتها وأنت مطالب بأن تبحث عنها بتعمق قليلًا ومعرفة أفضل الحلول والأمور الموصى بها في الوقت الحالي
هناك أمور أكثر مما ذكرت وتفاصيل أعمق وكل شيء يجب عمل حساب له ودراسة وغير ثابت ويختلف من مشروع لمشروع