كيفية تطوير ونشر تطبيقات AWS السحابية محلياً بدون حساب AWS فعلي

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

في مقالات سابقة، ناقشنا بناء ونشر تطبيقات السيرفرلس على AWS باستخدام Chalice و SAM. كانت تلك المشاريع سريعة وممتعة، واستفادت من قوة الحوسبة السيرفرلس لنشر تطبيق في دقائق معدودة. لكن العديد من المطورين قد لا يتمكنون من الاستفادة الكاملة من هذه الدروس إذا لم يكن لديهم حساب AWS.

إن إعداد حساب AWS وتهيئة بيئة التطوير يمكن أن يستغرق وقتًا طويلاً، وقد يؤدي إلى نفقات غير مرغوبة إذا لم يتم تكوينه بشكل صحيح. في هذا المقال، سأرشدك خطوة بخطوة لبناء ونشر تطبيق سيرفرلس دون الحاجة إلى إنشاء أو إعداد حساب AWS فعلي. سنقوم بإنشاء تطبيق نموذجي لـ “متجر الحيوانات الأليفة” باستخدام Amazon API Gateway و AWS Lambda و Amazon DynamoDB. سيتضمن هذا التطبيق واجهات برمجة تطبيقات (APIs) لإضافة حيوان أليف جديد وجلب قائمة الحيوانات الأليفة المتاحة.

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

سنستخدم AWS SAM في هذا الدرس. يمكنك تثبيت وتكوين SAM باتباع الإرشادات في المقال السابق هنا.

كيفية إنشاء مشروع جديد

قم بتشغيل الأمر sam-init لإنشاء مشروع جديد. سيؤدي هذا إلى إنشاء مجلد باسم pet-store في دليلك الحالي.

sam init -r java11 -d maven --app-template pet-store -n pet-store

لمزيد من التفاصيل حول المعاملات (parameters) التي تم تمريرها، يرجى الرجوع إلى المقال السابق.

دعنا نعدل ملف pom.xml لتحديث اسم الوحدة النمطية (module) إلى PetStore واستخدام Java 11 بدلاً من Java 8. الآن، دعنا ننشئ فئة (class) باسم Pet لتحتوي على سمات (attributes) الحيوانات الأليفة. سنبدأ بسمات بسيطة مثل name و age و category. بما أننا سنستخدم Amazon DynamoDB كمخزن للبيانات، دعنا نضيف تبعيات (SDK dependencies) الخاصة به في ملف pom.xml. سيؤدي هذا إلى جلب تبعيات AWS SDK for DynamoDB و Apache HTTP Client، والتي سنستخدمها لإنشاء عميل DynamoDB متزامن (synchronous).

كيفية قراءة وكتابة العناصر في DynamoDB

نحتاج إلى إنشاء فئة وصول للبيانات (data access class) للتفاعل مع Amazon DynamoDB وتشغيل استعلامات القراءة/الكتابة. قم بإنشاء فئة PetStoreClient وأضف التبعية على DynamoDbClient. سنقوم الآن بإنشاء دالتين في فئة PetStoreClient لقراءة وكتابة العناصر من DynamoDB.

كتابة عنصر (Write an Item)

إضافة عنصر واحد في DynamoDB هي طلب من نوع PUT. سنقوم بإنشاء PutItemRequest وتحديد اسم الجدول وسمات العنصر المراد إضافته. ثم سنستخدم DynamoDbClient لوضع هذا العنصر في DynamoDB.

قراءة العناصر (Read Items)

قراءة قائمة من العناصر في DynamoDB هي طلب من نوع SCAN. سنقوم بإنشاء ScanRequest وتحديد اسم الجدول المراد مسحه. ثم سنستخدم DynamoDbClient لمسح الجدول في DynamoDB وإرجاع قائمة بالعناصر.

ملاحظة: يمر طلب المسح (SCAN) عبر جميع العناصر في الجدول، لذا لا أوصي باستخدامه في حالات الاستخدام الواقعية التي تتطلب أداءً عالياً.

كيفية حل التبعيات (Dependencies)

لقد أضفنا DynamoDbClient كاعتماد في فئة PetStoreClient الخاصة بنا. كأفضل ممارسة عامة، يجب حل جميع هذه التبعيات في التعليمات البرمجية الخاصة بك باستخدام حقن التبعية (Dependency Injection – DI). عندما تسمع عن DI، فمن المحتمل أن يتبادر إلى ذهنك اسم Spring. لكن نظام Spring البيئي ضخم، وسيتعين عليك جلب الكثير من أطر عمله حتى لو كنت ترغب فقط في استخدام جزء DI. بالإضافة إلى ذلك، يتم الحقن في وقت التشغيل (runtime)، مما يجعل وقت بدء تشغيل Lambda البارد (cold start time) أطول.

يعد Guice إطار عمل آخر لطيف لحقن التبعية وهو أخف بكثير من Spring. ولكن تمامًا مثل Spring، فإنه يقوم بالحقن في وقت التشغيل، لذا فهو ليس مرشحًا جيدًا لـ DI أيضًا. ثم هناك Dagger، وهو إطار عمل مخصص لـ DI فقط يقوم بحقن التبعيات أثناء وقت التجميع (compile time)! حجمه الصغير وحقنه في وقت التجميع يجعله الخيار الأمثل لتطبيق DI في Lambdas. سأتعمق في تفاصيل DI وتغطية استخدام Dagger في مقال آخر.

في هذا المقال، سنستخدم الأسلوب التقليدي لدوال المصنع الثابتة (static factory methods) لتوفير التبعيات. دعنا ننشئ فئة تسمى DependencyModule ونعلن جميع تبعياتنا فيها. في هذه الفئة، نقوم بإنشاء مثيل جديد من DynamoDbClient وحقنه في PetStoreClient الخاص بنا. نقوم أيضًا بإنشاء مثيل من ObjectMapper لمساعدتنا في التعامل مع تسلسل وإلغاء تسلسل كائنات JSON.

كيفية تحديث Lambda ونقاط نهاية API

بعد ذلك، نحتاج إلى تحديث نقطة الدخول (entry point) لدالة Lambda وإضافة نقاط النهاية المحددة الخاصة بنا لإضافة واسترداد الحيوانات الأليفة. أضف المقتطف التالي إلى قسم Resources في ملف template.yaml. هذا يحدث دالتنا لاستخدام طريقة handleRequest من فئة App. كما يضيف نقطتي نهاية API لإضافة واسترداد الحيوانات الأليفة. قم بتحديث قسم Outputs أيضًا ليعكس اسم الدالة الجديد.

كيفية دمج العميل (Integrate the Client)

الآن بعد أن أصبح لدينا الكود الخاص بالتفاعل مع DynamoDB جاهزًا وتم فرز التبعيات، نحتاج إلى إجراء تغييرات في معالج Lambda لاستدعاء هذا الكود. قم بتحديث الكود في App.java لاستدعاء الدوال في PetStoreClient وتنفيذ الإجراءات وفقًا لطلب API.

بما أننا استخدمنا مصانع ثابتة لحقن التبعية، فلن نتمكن من اختبار الكود الخاص بنا بفعالية. سأغطي اختبار الوحدات (unit testing) لتطبيقات السحابة في مقال آخر. في الوقت الحالي، سنحتاج إلى حذف اختبارات الوحدات لبناء المشروع.

كيفية بناء المشروع

من مجلد pet-store، قم بتشغيل الأمر sam build.

لقطة شاشة توضح تنفيذ الأمر sam build بنجاح

يقوم هذا الأمر بتجميع التعليمات البرمجية المصدر الخاصة بك وبناء أي تبعيات لديك في التطبيق. ثم ينقل جميع الملفات إلى مجلد .aws-sam/build بحيث تكون جاهزة للتعبئة والنشر.

كيفية الاختبار محلياً (الجزء الأول)

في المقال السابق، ناقشنا كيف يوفر SAM CLI الأمر sam local لتشغيل تطبيقك محليًا. يستخدم هذا داخليًا Docker لمحاكاة بيئة تنفيذ Lambda. إذا لم يكن لديك Docker مثبتًا، يمكنك الحصول عليه من هنا.

كان هذا مناسبًا لتطبيق Daily News API لأنه جلب البيانات من الإنترنت ولم يعتمد على أي مكون آخر من مكونات AWS. ومع ذلك، في مشروعنا الحالي، نعتمد على Amazon DynamoDB كمخزن للبيانات، ونحتاج إلى الوصول إليه حتى نتمكن من تشغيل تطبيقنا بنجاح. بشكل أساسي، نحتاج إلى طريقة لمحاكاة الخدمات التي توفرها AWS على جهازنا المحلي حتى نتمكن من اختبارها محليًا دون استخدام حساب AWS فعلي.

كيفية تشغيل خدمات AWS محلياً باستخدام LocalStack

تم إنشاء LocalStack خصيصًا لحل هذه المشكلة. وفقًا لوصفه الخاص:

LocalStack يوفر إطار عمل سهل الاستخدام للاختبار/المحاكاة لتطوير تطبيقات السحابة. يقوم بإنشاء بيئة اختبار على جهازك المحلي توفر نفس الوظائف وواجهات API مثل بيئة سحابة AWS الحقيقية.

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

ماذا يعني هذا بالنسبة لك كمطور؟

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

كيفية إعداد LocalStack

يعد إعداد وبدء استخدام LocalStack أمرًا سهلاً حقًا. سنستخدم Docker لسحب أحدث صورة لـ LocalStack وبدء حاوية (container) تشغل نسخة وهمية من Amazon DynamoDB.

قم بإنشاء ملف docker-compose.yaml في مجلد pet-store وأضف المحتوى التالي:

version: '3.8'

services:
  localstack:
    container_name: "localstack_main"
    image: localstack/localstack
    ports:
      - "4510-4559:4510-4559"
      - "4566:4566" # Edge port for LocalStack
    environment:
      - SERVICES=dynamodb
      - DEFAULT_REGION=us-west-1
      - LAMBDA_EXECUTOR=local
      - DATA_DIR=/tmp/localstack/data
      - DOCKER_HOST=unix:///var/run/docker.sock
    volumes:
      - "./.localstack:/tmp/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

دعنا نلقي نظرة على بعض التكوينات التي نستخدمها:

  • SERVICES: بما أن Amazon DynamoDB هو اعتمادنا الوحيد، فسنقوم بتمكين هذه الخدمة المحددة فقط.
  • DEFAULT_REGION: سنستخدم us-west-1 كمنطقة AWS الخاصة بنا.
  • LAMBDA_EXECUTOR: تعيين هذا إلى local يعني أن جميع دوال Lambda الخاصة بنا ستعمل في دليل مؤقت على الجهاز المحلي.
  • DATA_DIR: موقع لحفظ البيانات المستمرة لخدمات مثل Amazon DynamoDB.

ملاحظة: يتم عرض جميع خدمات LocalStack عبر خدمة الحافة (edge service) على المنفذ 4566. هذا هو المنفذ الوحيد الذي نحتاج إلى استخدامه.

الآن، يمكننا استخدام docker-compose لبدء نسختنا المحلية من Amazon DynamoDB في حاويتها الخاصة.

لقطة شاشة توضح بدء تشغيل LocalStack باستخدام docker-compose

كيفية إنشاء جدول في DynamoDB المحلي

الآن بعد أن أصبح لدينا إعداد محلي لـ Amazon DynamoDB يعمل، يجب أن نكون قادرين على إنشاء جدول لتطبيقنا. لقد استخدمنا pet-store كاسم للجدول في الكود الخاص بنا، لذا دعنا نمضي قدمًا وننشئه.

سنستخدم AWS CLI للوصول إلى Amazon DynamoDB الذي يعمل على جهازنا المحلي وإنشاء الجدول المطلوب. قم بتشغيل الأمر التالي لإنشاء جدول باسم pet-store مع سمة id كمفتاح أساسي (primary key) له.

aws --endpoint-url "http://localhost:4566" dynamodb create-table \
 --table-name pet-store \
 --attribute-definitions AttributeName=id,AttributeType=S \
 --key-schema AttributeName=id,KeyType=HASH \
 --billing-mode PAY_PER_REQUEST

لاحظ أننا استخدمنا المعامل endpoint-url لتحديد أننا نشير إلى مثيل AWS الذي يعمل محليًا بدلاً من المثيل الفعلي.

كيفية الاختبار محلياً (الجزء الثاني)

قم بإجراء التغيير التالي على كود DynamoDbClient لجعله يشير إلى مثيل Amazon DynamoDB الذي يعمل محليًا:

// في DynamoDbClient.java
// ...
// قم بتحديث طريقة بناء العميل لتشير إلى LocalStack
public DynamoDbClient getDynamoDbClient() {
    return DynamoDbClient.builder()
            .endpointOverride(URI.create("http://localhost:4566"))
            .region(Region.US_WEST_1) // أو المنطقة التي حددتها في docker-compose.yaml
            .build();
}
// ...

بعد ذلك، استخدم sam build لبناء المشروع وقم بتشغيل الأمر التالي لبدء API محليًا:

sam local start-api

ينشئ هذا داخليًا خادمًا محليًا ويعرض نقطة نهاية محلية تحاكي REST API الخاص بك.

لقطة شاشة توضح بدء تشغيل API محلياً باستخدام sam local start-api

الآن، دعنا نختبر تطبيقنا عن طريق إضافة حيوان أليف جديد. قم بتشغيل الأمر التالي لإضافة حيوان أليف جديد عن طريق استدعاء نقطة النهاية /pet التي حددناها سابقًا.

curl --location --request PUT 'http://127.0.0.1:3000/pet' \
 --header 'Content-Type: application/json' \
 --data-raw '{ "name": "Rocket", "age": 2, "category": "Dog" }'

ينشئ هذا سجل Pet جديدًا، ويضيفه إلى Amazon DynamoDB المحلي الخاص بنا، ويعيد معرف UUID الذي تم إنشاؤه في الاستجابة. دعنا نضيف حيوانًا أليفًا آخر إلى متجرنا.

curl --location --request PUT 'http://127.0.0.1:3000/pet' \
 --header 'Content-Type: application/json' \
 --data-raw '{ "name": "Candle", "age": 1, "category": "Pig" }'

الآن، دعنا نستدعي API الخاص بنا /pets للحصول على قائمة بالحيوانات الأليفة المتاحة في مخزن البيانات الخاص بنا. يجب أن نتوقع الحصول على قائمة بالحيوانات الأليفة تحتوي على Rocket و Candle.

curl --location --request GET 'http://127.0.0.1:3000/pets'

لقطة شاشة توضح استدعاء API /pets وعرض قائمة الحيوانات الأليفة

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

تهانينا! لقد قمت للتو ببناء ونشر تطبيق سيرفرلس يستخدم AWS DynamoDB بالكامل على جهازك المحلي. يمكنك الآن المضي قدمًا وإجراء أي تعديلات على ملف App.java الخاص بك. أعد تشغيل sam build لإعادة بناء التغييرات، ثم sam local start-api لبدء الخادم المحلي واختبار التغييرات.

بمجرد أن تكون جاهزًا للنشر الفعلي على AWS، ما عليك سوى إزالة تجاوزات نقطة النهاية (endpoint overrides) في الكود الخاص بك (مثل endpointOverride(URI.create("http://localhost:4566")))، وستكون جاهزًا للانطلاق. في الوضع المثالي، سيتم التحكم في هذا بواسطة متغيرات البيئة (environment variables) ولن يتطلب أي تغييرات في الكود لجعله جاهزًا للإنتاج. يمثل هذا النهج باستخدام LocalStack قفزة نوعية في تسريع دورة حياة تطوير تطبيقات السحابة، مما يقلل من الحواجز أمام المطورين الجدد ويخفض التكاليف التشغيلية لمراحل التطوير والاختبار. إنه حل لا غنى عنه لأي مطور جاد في بناء تطبيقات AWS بكفاءة.

يمكن العثور على الكود المصدري الكامل لهذا الدرس هنا. شكرًا لك على متابعتك حتى الآن. أتمنى أن يكون المقال قد نال إعجابك.

اترك تعليقاً

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