إنشاء واجهة REST API باستخدام ‎.NET 5‎ ولغة ‎C#‎ من الصفر

دقائق القراءة: 14

مقدمة عملية إلى بناء REST API باستخدام .NET 5

يُعد إطار العمل .NET من أكثر المنصات شيوعًا لتطوير التطبيقات والخدمات الخلفية، وقد طوّرته شركة Microsoft ليوفّر بيئة قوية ومرنة لبناء برمجيات حديثة عالية الأداء. وفي هذا الدليل، سنتناول بصورة عملية كيفية إنشاء واجهة REST API متكاملة من الصفر باستخدام .NET 5 ولغة C#، مع الاعتماد على Visual Studio Code وأفضل الممارسات الحديثة في تصميم الخدمات البرمجية.

لا يقتصر هذا المسار على إنشاء نقاط نهاية بسيطة فحسب، بل يمتد ليغطي البنية الداخلية للتطبيق، وإدارة الاعتماديات، والتعامل مع قاعدة بيانات MongoDB، والتحويل إلى العمل غير المتزامن، وإدارة الأسرار، وفحوصات الصحة، ثم التغليف باستخدام Docker، وأخيرًا النشر داخل بيئة Kubernetes مع التوسع والاختبارات الوحدوية.

إنشاء واجهة برمجة تطبيقات REST باستخدام دوت نت 5 وسي شارب

ما الذي ستتعلمه في هذا الدليل التقني؟

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

  • إنشاء مشروع Web API باستخدام .NET 5.
  • فهم الملفات التي يولّدها المشروع تلقائيًا.
  • بناء الكيانات والمستودعات ووحدات التحكم.
  • تطبيق العمليات الأساسية GET وPOST وPUT وDELETE.
  • فهم حقن الاعتماديات Dependency Injection واستخدام الواجهات.
  • فصل عقود الاستجابة والطلب عبر DTO.
  • ربط التطبيق مع قاعدة بيانات MongoDB.
  • اعتماد البرمجة غير المتزامنة باستخدام async وawait.
  • إدارة الأسرار وفحوصات الصحة Health Checks.
  • تغليف التطبيق داخل Docker.
  • نشره داخل عنقود Kubernetes.
  • إضافة الاختبارات الوحدوية وتطبيق مفهوم TDD.

ما هي فائدة REST API في التطبيقات الحديثة؟

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

فعلى سبيل المثال، إذا كنت تريد بناء برنامج يسحب بيانات من خدمات مثل Twitter أو Yahoo Finance أو حتى NASA، فأنت في الغالب تتعامل مع واجهات API. والأمر نفسه ينطبق عند بناء خدمة لإدارة منتجات، أو عناصر متجر، أو سجلات مستخدمين، أو أي موارد تحتاج إلى إنشاء وقراءة وتحديث وحذف.

المتطلبات الأساسية قبل البدء

حتى تتابع هذا المسار العملي دون عوائق، ستحتاج إلى تثبيت بعض الأدوات الأساسية:

  • حزمة .NET 5 SDK.
  • محرر Visual Studio Code.
  • فهم أساسي للغة C#.
  • لاحقًا: Docker وPostman وامتدادات مرتبطة بـMongoDB وKubernetes.

سيناريو التطوير: نظام كتالوج لعناصر لعبة

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

الوظائف الأساسية التي نحتاج إليها هي:

  1. إنشاء عنصر جديد داخل الكتالوج.
  2. جلب قائمة العناصر المتاحة.
  3. جلب عنصر محدد بواسطة المعرّف.
  4. تحديث بيانات عنصر موجود.
  5. حذف عنصر من الكتالوج.

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

إنشاء مشروع .NET 5 Web API من الصفر

يمكنك إنشاء المشروع عبر سطر الأوامر باستخدام أداة dotnet CLI، ثم فتحه داخل Visual Studio Code.

dotnet new webapi -n Catalog

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

فهم الملفات الأساسية التي يولدها المشروع

ملف المشروع .csproj

هذا الملف يحدد كيفية بناء التطبيق، وما هو SDK المستخدم، وإطار العمل الهدف مثل net5.0، بالإضافة إلى الحزم الخارجية NuGet.

ملف Program.cs

يمثل نقطة الدخول الرئيسية للتطبيق، وفيه يُبنى المضيف Host الذي يشغّل خدمة الويب ويربطها بإعدادات الإقلاع.

ملف Startup.cs

من أهم ملفات المشروع، ويحتوي عادة على جزأين محوريين:

  • ConfigureServices(): لتسجيل الخدمات والاعتماديات.
  • Configure(): لبناء خط معالجة الطلبات Request Pipeline.

ملفات الإعدادات appsettings.json

تُستخدم لتخزين الإعدادات العامة، مثل السجلات أو إعدادات الاتصال أو أي قيم لا تريد تثبيتها داخل الشيفرة. ويمكنك أيضًا استخدام ملفات بيئية مثل appsettings.Development.json لتجاوز بعض الإعدادات أثناء التطوير.

ملفات .vscode

مثل tasks.json وlaunch.json وlaunchSettings.json، وهي تسهّل البناء والتشغيل والتصحيح من داخل المحرر.

الثقة في شهادة التطوير المحلية واستخدام Swagger UI

عند تشغيل مشروع .NET 5 لأول مرة باستخدام HTTPS، قد تظهر لك رسالة مرتبطة بالشهادة المحلية غير الموثوقة. يمكن حل ذلك بالأمر التالي:

dotnet dev-certs https --trust

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

تهيئة بيئة العمل في Visual Studio Code

من الممارسات المفيدة أثناء التطوير:

  • منع فتح نافذة متصفح جديدة في كل تشغيل عبر تعديل إعدادات launch.json.
  • تسهيل تنفيذ البناء من خلال جعل مهمة build هي الافتراضية داخل tasks.json.
  • الاعتماد على الاختصارات مثل F5 للتشغيل وCtrl + Shift + B للبناء.

هذه التحسينات لا تغيّر منطق التطبيق، لكنها توفر وقتًا كبيرًا عند التكرار اليومي.

بناء الكيان الأساسي: عنصر الكتالوج

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

الخصائص المنطقية لهذا الكيان تشمل:

  • المعرّف Id.
  • الاسم Name.
  • الوصف Description.
  • السعر Price.
  • تاريخ الإنشاء CreatedDate.

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

إنشاء مستودع مبدئي داخل الذاكرة In-Memory Repository

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

العمليات الأساسية التي يدعمها المستودع عادةً:

  • جلب كل العناصر.
  • جلب عنصر واحد بواسطة المعرّف.
  • إضافة عنصر جديد.
  • تحديث عنصر موجود.
  • حذف عنصر.

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

إنشاء Controller للتعامل مع الطلبات

وحدة التحكم Controller هي الطبقة التي تستقبل طلبات HTTP القادمة من العميل، ثم توجهها إلى المنطق المناسب داخل التطبيق. في ASP.NET Core يمكنك تحويل الصنف إلى متحكم برمجي عبر الوراثة من ControllerBase وإضافة السمة [ApiController].

ثم تحدد المسار الأساسي باستخدام [Route("items")] مثلًا، لتصبح جميع العمليات التابعة له مرتبطة بمورد العناصر.

تنفيذ أول عملية GET لجلب جميع العناصر

أول مسار مفيد هو جلب جميع العناصر من المستودع. عند استدعاء هذا المسار عبر HTTP GET على /items، يعيد التطبيق قائمة الموارد المتاحة.

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

  • كيفية ربط الفعل GET بالدالة المناسبة.
  • كيفية تمرير البيانات من المستودع إلى وحدة التحكم.
  • كيفية ظهور العقد تلقائيًا داخل Swagger.

تنفيذ GET لجلب عنصر واحد ومعالجة حالة 404

بعد جلب القائمة الكاملة، نحتاج لمسار يعيد عنصرًا واحدًا عبر المعرّف. هنا تظهر أهمية التعامل مع الحالات غير الناجحة. فإذا طلب العميل عنصرًا غير موجود، فلا يكفي إرجاع null، بل يجب إرسال استجابة 404 Not Found.

ولهذا تُستخدم عادةً النتيجة ActionResult<T>، لأنها تمنحك المرونة لإرجاع نوع البيانات عند النجاح، أو حالة HTTP مناسبة عند الفشل.

فهم حقن الاعتماديات Dependency Injection

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

هنا يأتي دور Dependency Injection، حيث تجعل المتحكم يعتمد على واجهة مثل IItemsRepository بدلًا من صنف تنفيذي محدد. ثم تسجل التنفيذ الفعلي داخل الحاوية الخدمية في ConfigureServices().

هذه المقاربة تحقق فوائد مهمة:

  • تقليل الترابط بين الطبقات.
  • سهولة استبدال التنفيذ لاحقًا.
  • تحسين قابلية الاختبار.
  • إدارة دورة حياة الخدمات مثل Singleton.

استخدام DTO لبناء عقد واضح مع العملاء

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

لهذا السبب نستخدم كائنات نقل البيانات DTO، وهي عقود مستقلة تمثل ما يراه العميل، لا ما تحتفظ به قاعدة البيانات بالضرورة.

في هذا المثال يمكن تعريف عدة أنواع من DTO:

  • ItemDto للاستجابة العامة.
  • CreateItemDto لطلبات الإنشاء.
  • UpdateItemDto لطلبات التحديث.

هذا الفصل يمنحك حرية أكبر في تطوير النموذج الداخلي دون كسر العقود العامة.

تحويل الكيانات إلى DTO بطريقة نظيفة

بدل تكرار تحويل الخصائص يدويًا في كل مكان، من الأفضل إنشاء دالة مساعدة أو Extension Method مثل AsDto(). بهذه الطريقة يصبح تحويل الكيان أكثر وضوحًا واتساقًا، كما يقل احتمال نسيان خاصية مهمة أثناء التطوير.

تنفيذ عملية POST لإنشاء عنصر جديد

إنشاء الموارد يتم عبر HTTP POST. عند وصول طلب جديد، يُنشأ كائن داخلي جديد يضم:

  • معرّفًا جديدًا.
  • الاسم والوصف والسعر القادمين من العميل.
  • تاريخ إنشاء يحدده الخادم.

بعد حفظ العنصر، من الجيد إرجاع الاستجابة باستخدام CreatedAtAction()، لأنها تعيد حالة 201 Created مع عنوان المورد الجديد في الترويسة Location.

التحقق من صحة المدخلات عبر Data Annotations

لا ينبغي قبول بيانات ناقصة أو غير منطقية. لذلك يمكن تزيين خصائص DTO بسمات مثل:

  • [Required] للحقل الإجباري.
  • [Range(1, 1000)] لتقييد القيم العددية.

وبفضل السمة [ApiController]، سيتكفل الإطار بإرجاع 400 Bad Request تلقائيًا عندما تخفق عملية التحقق، مع تفاصيل واضحة تساعد العميل على تصحيح الطلب.

تنفيذ عمليتي PUT وDELETE

التحديث عبر PUT

عند تحديث عنصر، تُجلب النسخة الحالية أولًا. فإذا لم توجد، يُعاد 404. وإذا وُجدت، تُعدل الخصائص المطلوبة ثم يُحفظ العنصر وتُعاد استجابة 204 No Content.

الحذف عبر DELETE

ينطبق المنطق نفسه تقريبًا. نتحقق أولًا من وجود العنصر، ثم نحذفه من المستودع ونرجع 204 No Content.

بهذا تكون قد بنيت مجموعة العمليات الأساسية الكاملة CRUD.

الانتقال من الذاكرة إلى قاعدة بيانات MongoDB

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

  • قاعدة NoSQL شائعة وعملية.
  • لا تحتاج إلى مخطط صارم في البداية.
  • تتعامل جيدًا مع المستندات القريبة من تنسيق JSON.
  • تتكامل بسهولة مع نماذج الكائنات في C#.

تجربة الواجهة باستخدام Postman

رغم أن Swagger UI رائع للتجربة السريعة، فإن Postman يمنحك مرونة أكبر، خاصة عندما تريد:

  • إنشاء طلبات متعددة محفوظة.
  • إرسال أجسام JSON مخصصة.
  • اختبار خدمات خارجية لا توفر Swagger.
  • مراقبة الرؤوس Headers والاستجابات بدقة.

ولهذا يُعد أداة أساسية في أي سير عمل خلفي احترافي.

إنشاء مستودع MongoDB واستخدام MongoDB.Driver

للربط مع MongoDB نحتاج إلى الحزمة الرسمية MongoDB.Driver. بعدها يُنشأ مستودع جديد يطبق الواجهة نفسها IItemsRepository، لكنه يستخدم عميل IMongoClient ومجموعة Collection بدل القائمة المحلية.

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

تشغيل MongoDB داخل Docker

بدل تثبيت MongoDB يدويًا، يمكن تشغيله بسهولة كحاوية Docker. هذه المقاربة مريحة، نظيفة، وقابلة للإزالة والإعادة بسرعة.

docker run -d --rm --name mongo -p 27017:27017 -v mongodbdata:/data/db mongo

يمكن أيضًا إضافة اسم مستخدم وكلمة مرور عبر متغيرات البيئة عند الحاجة إلى تفعيل المصادقة.

تهيئة إعدادات الاتصال بقاعدة البيانات

من الأفضل تعريف صنف إعدادات مثل MongoDbSettings يحتوي على:

  • المضيف Host.
  • المنفذ Port.
  • اسم المستخدم User.
  • كلمة المرور Password.
  • سلسلة الاتصال ConnectionString.

ثم تُقرأ هذه القيم من appsettings.json وتُستخدم عند تسجيل MongoClient داخل الحاوية الخدمية.

تنفيذ عمليات MongoDB CRUD

بعد تجهيز المستودع، تُنفذ العمليات الأساسية على النحو التالي:

  • InsertOne أو InsertOneAsync للإنشاء.
  • Find أو FindAsync للاستعلام.
  • ReplaceOne للتحديث.
  • DeleteOne للحذف.

كما يمكن استخدام مرشحات FilterDefinitionBuilder عند البحث عن عنصر محدد بالمعرّف.

فحص البيانات المخزنة مباشرة من داخل VS Code

من المفيد تثبيت امتداد MongoDB في Visual Studio Code لرؤية قواعد البيانات والمجموعات والمستندات مباشرة. هذه الخطوة تساعدك في التأكد من أن ما ترسله عبر Postman يُخزَّن فعليًا بالشكل المتوقع.

الانتقال إلى البرمجة غير المتزامنة باستخدام Task وasync وawait

التعامل مع قاعدة البيانات يمثل عملية إدخال وإخراج I/O، وهي بطبيعتها بطيئة مقارنة بالتعليمات داخل الذاكرة. لذلك من الأفضل جعل السلسلة كلها غير متزامنة.

وهذا يعني:

  • أن تُرجع واجهات المستودع أنواعًا مثل Task<T>.
  • أن تُضاف اللاحقة Async إلى أسماء الدوال.
  • أن تستخدم عمليات MongoDB غير المتزامنة مثل InsertOneAsync() وToListAsync().
  • أن تُحدَّث وحدات التحكم لتستخدم await على طول المسار.

هذا الأسلوب يحسن استهلاك الموارد ويزيد كفاءة التطبيق تحت الضغط.

إدارة الأسرار باستخدام .NET Secret Manager

عند تفعيل المصادقة في MongoDB، لا يجب حفظ كلمات المرور مباشرة داخل appsettings.json. الحل الصحيح أثناء التطوير هو استخدام .NET Secret Manager.

يمكن تهيئته ثم حفظ سر جديد عبر سطر الأوامر:

dotnet user-secrets init
dotnet user-secrets set "MongoDbSettings:Password" "pass@word1"

بهذه الطريقة تبقى كلمة المرور خارج ملفات المشروع، ومع ذلك يستطيع التطبيق قراءتها كأي إعداد آخر.

إضافة فحوصات الصحة Health Checks

فحوصات الصحة جزء مهم في أي خدمة إنتاجية. فهي تجيب عن أسئلة مثل:

  • هل الخدمة نفسها تعمل؟
  • هل هي جاهزة لاستقبال الطلبات؟
  • هل تستطيع الوصول إلى قاعدة البيانات؟

يمكن تسجيل الخدمة بسهولة ثم تعيين مسارات مثل:

  • /health/live للتحقق من أن التطبيق حي.
  • /health/ready للتحقق من الجاهزية الكاملة بما في ذلك التبعيات.

ومن خلال حزمة إضافية يمكن أيضًا دمج فحص خاص بـMongoDB، بحيث تصبح حالة قاعدة البيانات جزءًا من تقييم صحة الخدمة.

تخصيص استجابة فحوصات الصحة

بدل الاكتفاء بكلمة واحدة مثل Healthy، يمكنك إعادة استجابة JSON تتضمن:

  • الحالة العامة.
  • اسم كل فحص.
  • حالته التفصيلية.
  • مدة التنفيذ.
  • رسالة الاستثناء إن وُجدت.

هذه البيانات مفيدة جدًا في التشخيص والتكامل مع أدوات المراقبة.

فهم دور Docker في النشر

عند الانتقال من جهاز التطوير إلى بيئة الإنتاج، تبدأ تحديات التوافق والإعدادات والاعتماديات. وهنا يأتي Docker ليغلف التطبيق داخل صورة واحدة قابلة للتشغيل في أي مكان يدعم محرك Docker.

فوائد هذا النهج تشمل:

  • عزل التطبيق عن اختلافات الأنظمة.
  • توحيد بيئة التشغيل بين التطوير والإنتاج.
  • سهولة إعادة البناء والنقل.
  • التوسع السريع وتشغيل نسخ متعددة.

إنشاء ملف Dockerfile للتطبيق

يمكن إنشاء ملف Dockerfile يدويًا أو عبر امتداد Docker في VS Code. وغالبًا ستستخدم بناء متعدد المراحل Multi-stage Build لتقليل حجم الصورة النهائية.

يتضمن هذا الملف عادةً:

  • صورة أساسية من ASP.NET للتشغيل.
  • صورة من .NET SDK للبناء والنشر.
  • نسخ ملفات المشروع.
  • تنفيذ restore وpublish.
  • تحديد نقطة الدخول عبر ENTRYPOINT.

بناء الصورة وتشغيل الحاوية

بعد إنشاء الملف، يمكنك بناء الصورة بالأمر:

docker build -t catalog:v1 .

ثم تشغيلها مع تمرير الإعدادات اللازمة مثل مضيف قاعدة البيانات وكلمة المرور وربط الشبكة المناسبة مع حاوية MongoDB.

كما يمكنك رفع الصورة إلى سجل مثل Docker Hub لمشاركتها أو استخدامها في بيئات أخرى.

لماذا نحتاج إلى Kubernetes؟

تشغيل حاوية واحدة أمر بسيط، لكن إدارة عدد كبير من الحاويات والخدمات تصبح مهمة معقدة. من هنا تظهر قيمة Kubernetes كمنصة تنسيق للحاويات.

يوفر Kubernetes إمكانات مهمة مثل:

  • النشر التلقائي وتوزيع الحاويات.
  • التوسع الأفقي.
  • إعادة التشغيل الذاتي عند الفشل.
  • اكتشاف الخدمات والموازنة بينها.
  • إدارة الأسرار والإعدادات.
  • التحديثات التدريجية دون توقف.

تشغيل عنقود Kubernetes محليًا

إذا كنت تستخدم Docker Desktop، فيمكنك تفعيل عنقود Kubernetes محلي من الإعدادات. وبعدها يصبح لديك بيئة مناسبة لتجربة النشر والتوسع دون الحاجة إلى خادم خارجي.

كما يفيدك امتداد Kubernetes في VS Code لتسهيل كتابة ملفات YAML ومراقبة الموارد.

نشر واجهة REST API على Kubernetes

لنشر التطبيق ستحتاج غالبًا إلى موردين أساسيين:

  • Deployment لتحديد الصورة وعدد النسخ والموارد والمنافذ.
  • Service لكشف التطبيق داخل العنقود أو خارجه.

كما يمكن تمرير متغيرات البيئة، وقراءة كلمة المرور من Secret خاص داخل Kubernetes، ثم استخدام فحوصات liveness وreadiness لتمكين الاسترداد الذاتي وإدارة الجاهزية بدقة.

تشغيل MongoDB على Kubernetes باستخدام StatefulSet

لأن قاعدة البيانات حالة Stateful وليست مؤقتة، فمن الأفضل تشغيلها عبر StatefulSet بدل Deployment. هذا يسمح بالحفاظ على الهوية الثابتة للأجزاء وربطها بوحدات تخزين دائمة.

وغالبًا ستحتاج إلى:

  • StatefulSet لتشغيل القاعدة.
  • Service من نوع مناسب لاكتشافها.
  • PersistentVolumeClaim لتخزين ملفات البيانات.

بهذا تبقى البيانات محفوظة حتى لو أُعيد تشغيل الحاوية.

الاستفادة من قدرات Kubernetes في الشفاء الذاتي والتوسع

بعد النشر، يمكنك اختبار حذف إحدى الوحدات Pods يدويًا وملاحظة كيف يعيدها Kubernetes تلقائيًا للحالة المرغوبة. كما يمكنك زيادة عدد النسخ يدويًا ومشاهدة التوسع الفوري للخدمة، وهو ما يمنحك مرونة كبيرة في بيئات الإنتاج.

إضافة السجلات باستخدام ILogger

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

ما هي الاختبارات الوحدوية ولماذا تحتاجها؟

الاختبار الوحدوي Unit Testing هو وسيلة لاختبار أجزاء صغيرة من الكود في عزلة عن الاعتماديات الخارجية. الهدف هو التأكد من أن كل وحدة برمجية تؤدي وظيفتها بشكل صحيح قبل دمجها مع بقية النظام.

أهم الفوائد العملية:

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

فهم مفهوم TDD

التطوير بالاختبار أولًا TDD يعتمد على دورة بسيطة:

  1. أحمر: اكتب اختبارًا يفشل.
  2. أخضر: اكتب أقل قدر من الكود لنجاح الاختبار.
  3. إعادة هيكلة: نظّف الكود مع الحفاظ على نجاح الاختبارات.

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

إنشاء مشروع اختبارات باستخدام xUnit

يمكن إنشاء مشروع اختبارات منفصل ثم ربطه بمشروع API. ومع إضافة حزم مثل Moq وFluentAssertions، يصبح من السهل محاكاة الاعتماديات وكتابة اختبارات واضحة القراءة.

الأدوات الشائعة في هذا الجزء:

  • xUnit لإطار الاختبارات.
  • Moq لمحاكاة الواجهات.
  • FluentAssertions لكتابة تأكيدات أوضح.

اختبار ItemsController عمليًا

يمكنك اختبار عدة حالات أساسية مثل:

  • استدعاء GetItemAsync عندما لا يوجد العنصر، فيجب أن تعود NotFound.
  • استدعاؤه عندما يوجد العنصر، فيجب أن تعود بيانات DTO المتوقعة.
  • اختبار GetItemsAsync للتأكد من إعادة القائمة بالشكل الصحيح.
  • اختبار CreateItemAsync للتأكد من إنشاء المورد وإرجاع قيم مثل Id وCreatedDate.
  • اختبار UpdateItemAsync وDeleteItemAsync للتحقق من استجابة NoContent.

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

إعادة الهيكلة بأمان بفضل الاختبارات

بعد امتلاك مجموعة اختبارات جيدة، يمكنك تعديل بنية الكود بثقة أكبر. على سبيل المثال، يمكن التحول من نوع record إلى class، أو نقل خصائص، أو دمج ملفات DTO، ثم تشغيل الاختبارات للتحقق من أن السلوك العام لم ينكسر.

وهذه نقطة عملية شديدة الأهمية في المشاريع الواقعية، لأن التطوير لا يتوقف عند النسخة الأولى من التصميم.

إضافة ميزة جديدة عبر TDD: التصفية بالاسم

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

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

هذه الطريقة لا تنتج كودًا يعمل فقط، بل تنتج كودًا موثقًا باختبارات تمثل المتطلبات الحقيقية.

أفضل الممارسات المستخلصة من هذا المسار

  • افصل بين الكيانات الداخلية وعقود API باستخدام DTO.
  • اعتمد على الواجهات بدل التنفيذات المباشرة.
  • اجعل العمليات التي تتعامل مع قاعدة البيانات غير متزامنة.
  • لا تخزن الأسرار داخل ملفات المشروع.
  • فعّل فحوصات الصحة مبكرًا.
  • استخدم Docker لتوحيد بيئة التشغيل.
  • استخدم Kubernetes عندما تتجاوز الحاجة تشغيل حاوية واحدة.
  • اكتب اختبارات وحدوية قبل أن يصبح المشروع كبيرًا وصعب التغيير.

الخلاصة التقنية

يبين هذا المسار أن بناء REST API احترافية لا يقتصر على تعريف بضع مسارات داخل وحدة تحكم، بل يشمل تصميمًا معماريًا واعيًا، وفصلًا واضحًا للمسؤوليات، وإدارة صحيحة للتبعيات والبيانات والأسرار، ثم تغليفًا ونشرًا قابلين للتوسع والمراقبة. تقنيًا، الجمع بين .NET 5 وC# وMongoDB وDocker وKubernetes يوفر أساسًا قويًا لبناء خدمات خلفية حديثة، بينما تمنحك الاختبارات الوحدوية وTDD طبقة أمان حقيقية تسمح لك بتطوير المشروع بثقة واستمرارية.

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *