التطوير باستخدام Cloud Workstations وCloud Code

1. نظرة عامة

يوضّح هذا الدرس التطبيقي الميزات والإمكانات المصمَّمة لتبسيط سير عمل التطوير لمهندسي البرامج المكلّفين بتطوير تطبيقات Java في بيئة تستخدم حاويات. يتطلّب تطوير الحاويات المعتاد أن يفهم المستخدم تفاصيل الحاويات وعملية التصميم. بالإضافة إلى ذلك، على المطوّرين عادةً إيقاف عملية التطوير مؤقتًا والانتقال من بيئة التطوير المتكاملة (IDE) لاختبار تطبيقاتهم وتصحيح الأخطاء فيها في بيئات بعيدة. باستخدام الأدوات والتكنولوجيات المذكورة في هذا البرنامج التعليمي، يمكن للمطوّرين العمل بفعالية مع التطبيقات المستندة إلى حاويات بدون مغادرة بيئة التطوير المتكاملة.

ما ستتعلمه

في هذا الدرس التطبيقي، ستتعرّف على طرق التطوير باستخدام الحاويات في Google Cloud Platform، بما في ذلك:

  • تطوير InnerLoop باستخدام Cloud Workstations
  • إنشاء تطبيق Java جديد للمبتدئين
  • التعرّف على عملية التطوير
  • تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها
  • تصحيح أخطاء التطبيق على مجموعة GKE
  • ربط التطبيق بقاعدة بيانات CloudSQL

58a4cdd3ed7a123a.png

2. الإعداد والمتطلبات

إعداد البيئة بوتيرة ذاتية

  1. سجِّل الدخول إلى Google Cloud Console وأنشِئ مشروعًا جديدًا أو أعِد استخدام مشروع حالي. إذا لم يكن لديك حساب على Gmail أو Google Workspace، عليك إنشاء حساب.

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • اسم المشروع هو الاسم المعروض للمشاركين في هذا المشروع. وهي سلسلة أحرف لا تستخدمها Google APIs. ويمكنك تعديله في أي وقت.
  • رقم تعريف المشروع هو معرّف فريد في جميع مشاريع Google Cloud ولا يمكن تغييره بعد ضبطه. تنشئ Cloud Console تلقائيًا سلسلة فريدة، ولا يهمّك عادةً ما هي. في معظم دروس البرمجة، عليك الرجوع إلى رقم تعريف المشروع (يتم تحديده عادةً على أنّه PROJECT_ID). إذا لم يعجبك رقم التعريف الذي تم إنشاؤه، يمكنك إنشاء رقم تعريف عشوائي آخر. يمكنك بدلاً من ذلك تجربة اسم مستخدم من اختيارك ومعرفة ما إذا كان متاحًا. لا يمكن تغيير هذا الخيار بعد هذه الخطوة وسيظل ساريًا طوال مدة المشروع.
  • للعلم، هناك قيمة ثالثة، وهي رقم المشروع الذي تستخدمه بعض واجهات برمجة التطبيقات. يمكنك الاطّلاع على مزيد من المعلومات عن كل هذه القيم الثلاث في المستندات.
  1. بعد ذلك، عليك تفعيل الفوترة في Cloud Console لاستخدام موارد/واجهات برمجة تطبيقات Cloud. لن تكلفك تجربة هذا الدرس البرمجي الكثير من المال، إن لم تكلفك شيئًا على الإطلاق. لإيقاف الموارد كي لا يتم تحصيل رسوم منك بعد هذا البرنامج التعليمي، يمكنك حذف الموارد التي أنشأتها أو حذف المشروع بأكمله. يمكن لمستخدمي Google Cloud الجدد الاستفادة من برنامج الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.

بدء Cloudshell Editor

تم تصميم هذا الدرس التطبيقي واختباره لاستخدامه مع "محرّر Google Cloud Shell". للوصول إلى المحرِّر، اتّبِع الخطوات التالية:

  1. الوصول إلى مشروعك على Google من خلال https://console.cloud.google.com
  2. في أعلى يسار الصفحة، انقر على رمز محرر Cloud Shell

8560cc8d45e8c112.png

  1. سيتم فتح لوحة جديدة في أسفل النافذة
  2. انقر على الزر "فتح المحرِّر"

9e504cb98a6a8005.png

  1. سيتم فتح المحرِّر مع مستكشف على اليسار ومحرِّر في المنطقة الوسطى
  2. يجب أن يتوفّر جزء وحدة طرفية أيضًا في أسفل الشاشة
  3. إذا لم تكن النافذة الطرفية مفتوحة، استخدِم مجموعة المفاتيح `ctrl+`` لفتح نافذة طرفية جديدة.

إعداد gcloud

في Cloud Shell، اضبط رقم تعريف مشروعك والمنطقة التي تريد نشر تطبيقك فيها. احفظها كمتغيرات PROJECT_ID وREGION.

export REGION=us-central1
export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

استنساخ رمز المصدر

يمكنك العثور على الرمز المصدري لهذا المختبر في حاوية container-developer-workshop في GoogleCloudPlatform على GitHub. استنسِخه باستخدام الأمر أدناه، ثم انتقِل إلى الدليل.

git clone https://github.com/GoogleCloudPlatform/container-developer-workshop.git
cd container-developer-workshop/labs/spring-boot

توفير البنية الأساسية المستخدَمة في هذا الدرس التطبيقي

في هذا التمرين العملي، ستنشر رمزًا برمجيًا على GKE وتصل إلى البيانات المخزّنة في قاعدة بيانات CloudSQL. يُعدّ برنامج الإعداد النصي أدناه هذه البنية الأساسية لك. ستستغرق عملية التوفير أكثر من 25 دقيقة. انتظِر حتى يكتمل النص البرمجي قبل الانتقال إلى القسم التالي.

./setup_with_cw.sh &

مجموعة Cloud Workstations

افتح محطات عمل Cloud في Cloud Console. انتظِر إلى أن تصبح حالة المجموعة READY.

305e1a3d63ac7ff6.png

إنشاء إعدادات "محطات عمل لتعديل المحتوى"

إذا تم قطع اتصال جلسة Cloud Shell، انقر على "إعادة الاتصال" ثم نفِّذ أمر gcloud cli لضبط رقم تعريف المشروع. استبدِل رقم تعريف المشروع النموذجي أدناه برقم تعريف مشروعك على Qwiklabs قبل تنفيذ الأمر.

gcloud config set project qwiklabs-gcp-project-id

نفِّذ النص البرمجي أدناه في الوحدة الطرفية لإنشاء إعدادات Cloud Workstations.

cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh

تحقَّق من النتائج ضمن قسم "الإعدادات". سيستغرق الانتقال إلى الحالة READY دقيقتَين.

7a6af5aa2807a5f2.png

افتح محطات عمل Cloud في "وحدة التحكّم" وأنشئ مثيلاً جديدًا.

a53adeeac81a78c8.png

غيِّر الاسم إلى my-workstation واختَر الإعداد الحالي: codeoss-java.

f21c216997746097.png

تحقَّق من النتائج ضمن قسم "محطات العمل".

66a9fc8b20543e32.png

تشغيل Workstation

بدء محطة العمل وإطلاقها

c91bb69b61ec8635.png

السماح بملفات تعريف الارتباط التابعة لجهات خارجية من خلال النقر على الرمز في شريط العناوين 1b8923e2943f9bc4.png

fcf9405b6957b7d7.png

انقر على "هل تواجهك مشكلة في الموقع الإلكتروني؟".

36a84c0e2e3b85b.png

انقر على "السماح بملفات تعريف الارتباط".

2259694328628fba.png

بعد تشغيل محطة العمل، سيظهر لك بيئة التطوير المتكاملة Code OSS. انقر على "وضع علامة اكتمال" في صفحة "البدء" في بيئة التطوير المتكاملة لمحطة العمل

94874fba9b74cc22.png

3- إنشاء تطبيق Java جديد للمبتدئين

في هذا القسم، ستنشئ تطبيقًا جديدًا باستخدام Java Spring Boot من البداية باستخدام تطبيق نموذجي مقدَّم من spring.io. افتح نافذة Terminal جديدة.

c31d48f2e4938c38.png

استنساخ نموذج التطبيق

  1. إنشاء تطبيق أوّلي
curl  https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=17 -d packageName=com.example.springboot -o sample-app.zip

انقر على زر "السماح" إذا ظهرت لك هذه الرسالة، لتتمكّن من نسخ المحتوى ولصقه في محطة العمل.

58149777e5cc350a.png

  1. فكّ ضغط التطبيق
unzip sample-app.zip -d sample-app
  1. افتح المجلد "sample-app"
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

إضافة spring-boot-devtools وJib

لتفعيل Spring Boot DevTools، ابحث عن pom.xml وافتحه من "المستكشف" في المحرّر. بعد ذلك، ألصِق الرمز التالي بعد سطر الوصف الذي يظهر فيه <description>Demo project for Spring Boot</description>

  1. إضافة spring-boot-devtools في pom.xml

افتح ملف pom.xml في جذر المشروع. أضِف الإعدادات التالية بعد إدخال Description.

pom.xml

  <!--  Spring profiles-->
  <profiles>
    <profile>
      <id>sync</id>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
        </dependency>
      </dependencies>
    </profile>
  </profiles>
  1. تفعيل jib-maven-plugin في ملف pom.xml

‫Jib هي أداة مفتوحة المصدر من Google لوضع تطبيقات Java في حاويات، وتتيح لمطوّري Java إنشاء حاويات باستخدام أدوات Java التي يعرفونها. ‫Jib هي أداة سريعة وبسيطة لإنشاء صور الحاويات، وتتعامل مع جميع خطوات تجميع تطبيقك في صورة حاوية. لا يتطلّب منك كتابة ملف Dockerfile أو تثبيت Docker، كما أنّه مدمج مباشرةً في Maven وGradle.

انتقِل للأسفل في ملف pom.xml وعدِّل قسم Build لتضمين المكوّن الإضافي Jib. يجب أن يتطابق قسم الإصدار مع ما يلي عند اكتماله.

pom.xml

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <!--  Jib Plugin-->
      <plugin>
        <groupId>com.google.cloud.tools</groupId>
        <artifactId>jib-maven-plugin</artifactId>
        <version>3.2.0</version>
      </plugin>
       <!--  Maven Resources Plugin-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <version>3.1.0</version>
      </plugin>
    </plugins>
  </build>

إنشاء بيانات

توفّر Skaffold أدوات متكاملة لتسهيل عملية تطوير الحاويات. في هذه الخطوة، ستُهيّئ Skaffold التي ستنشئ تلقائيًا ملفات YAML الأساسية في Kubernetes. تحاول العملية تحديد الأدلة التي تتضمّن تعريفات صور الحاويات، مثل Dockerfile، ثم تنشئ بيان نشر وبيان خدمة لكل دليل.

نفِّذ الأمر التالي في "الوحدة الطرفية" لبدء العملية.

d869e0cd38e983d7.png

  1. نفِّذ الأمر التالي في الوحدة الطرفية
skaffold init --generate-manifests
  1. عندما يُطلب منك ذلك:
  • استخدِم الأسهم لتحريك المؤشر إلى Jib Maven Plugin
  • اضغط على مفتاح المسافة لاختيار الخيار.
  • اضغط على Enter للمتابعة
  1. أدخِل 8080 للمنفذ
  2. أدخِل y لحفظ الإعدادات

تمت إضافة ملفَين إلى مساحة العمل skaffold.yaml وdeployment.yaml

نتائج Skaffold:

b33cc1e0c2077ab8.png

تعديل اسم التطبيق

لا تتطابق القيم التلقائية المُضمَّنة في الإعداد حاليًا مع اسم تطبيقك. عدِّل الملفات للإشارة إلى اسم تطبيقك بدلاً من القيم التلقائية.

  1. تغيير الإدخالات في ملف إعدادات Skaffold
  • فتح "skaffold.yaml"
  • اختَر اسم الصورة المضبوطة حاليًا على pom-xml-image
  • انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
  • اكتب الاسم الجديد باللغة demo-app
  1. تغيير الإدخالات في إعدادات Kubernetes
  • فتح ملف deployment.yaml
  • اختَر اسم الصورة المضبوطة حاليًا على pom-xml-image
  • انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
  • اكتب الاسم الجديد باللغة demo-app

تفعيل وضع المزامنة التلقائية

لتسهيل تجربة إعادة التحميل السريع المحسّنة، عليك استخدام ميزة "المزامنة" التي توفّرها Jib. في هذه الخطوة، ستضبط Skaffold لاستخدام هذه الميزة في عملية التصميم.

يُرجى العِلم أنّ ملف الإعداد "sync" الذي تعمل على إعداده في إعدادات Skaffold يستفيد من ملف الإعداد "sync" في Spring الذي أعددته في الخطوة السابقة، حيث فعّلت إمكانية استخدام spring-dev-tools.

  1. تعديل إعدادات Skaffold

في ملف skaffold.yaml، استبدِل قسم الإنشاء بالكامل بالمواصفات التالية. لا تعدِّل الأقسام الأخرى من الملف.

skaffold.yaml

build:
  artifacts:
  - image: demo-app
    jib:
      project: com.example:demo
      type: maven
      args: 
      - --no-transfer-progress
      - -Psync
      fromImage: gcr.io/distroless/java17-debian11:debug
    sync:
      auto: true

إضافة مسار تلقائي

أنشئ ملفًا باسم HelloController.java في المجلد /src/main/java/com/example/springboot/.

a624f5dd0c477c09.png

الصِق المحتوى التالي في الملف لإنشاء مسار http تلقائي.

HelloController.java

package com.example.springboot;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;

@RestController
public class HelloController {

    @Value("${target:local}")
    String target;

    @GetMapping("/") 
    public String hello()
    {
        return String.format("Hello from your %s environment!", target);
    }
}

4. التعرّف على عملية التطوير

في هذا القسم، ستتّبع بضع خطوات باستخدام مكوّن Cloud Code الإضافي للتعرّف على العمليات الأساسية والتأكّد من صحة إعدادات تطبيقك الأوّلي.

يتكامل Cloud Code مع Skaffold لتبسيط عملية التطوير. عند النشر إلى GKE في الخطوات التالية، ستنشئ Cloud Code وSkaffold تلقائيًا صورة الحاوية، وتدفعها إلى Container Registry، ثم تنشر تطبيقك إلى GKE. يحدث ذلك وراء الكواليس، حيث يتم تجريد التفاصيل بعيدًا عن مسار المطوّر. تعمل Cloud Code أيضًا على تحسين عملية التطوير من خلال توفير إمكانات تصحيح الأخطاء والمزامنة السريعة التقليدية للتطوير المستند إلى الحاويات.

تسجيل الدخول إلى Google Cloud

انقر على رمز Cloud Code واختَر "تسجيل الدخول إلى Google Cloud":

1769afd39be372ff.png

انقر على "متابعة لتسجيل الدخول".

923bb1c8f63160f9.png

تحقَّق من الناتج في "نافذة الأوامر" وافتح الرابط:

517fdd579c34aa21.png

سجِّل الدخول باستخدام بيانات اعتماد الطلاب في Qwiklabs.

db99b345f7a8e72c.png

اختَر "السماح":

a5376553c430ac84.png

انسخ رمز التحقّق وارجع إلى علامة التبويب "محطة العمل".

6719421277b92eac.png

ألصِق رمز التحقّق واضغط على Enter.

e9847cfe3fa8a2ce.png

إضافة مجموعة Kubernetes

  1. إضافة مجموعة

62a3b97bdbb427e5.png

  1. اختَر Google Kubernetes Engine:

9577de423568bbaa.png

  1. اختَر مشروعًا.

c5202fcbeebcd41c.png

  1. اختَر "مجموعة الاقتباسات" التي تم إنشاؤها في عملية الإعداد الأولية.

366cfd8bc27cd3ed.png

9d68532c9bc4a89b.png

ضبط رقم تعريف المشروع الحالي باستخدام gcloud cli

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

fcff2d10007ec5bc.png

نفِّذ أمر gcloud cli لضبط رقم تعريف المشروع. استبدِل رقم تعريف المشروع النموذجي قبل تنفيذ الأمر.

gcloud config set project qwiklabs-gcp-project-id

مثال على الناتج:

f1c03d01b7ac112c.png

تصحيح الأخطاء على Kubernetes

  1. في اللوحة اليمنى في أسفل الشاشة، انقر على "رمز السحابة".

60b8e4e95868b561.png

  1. في اللوحة التي تظهر ضمن DEVELOPMENT SESSIONS، اختَر Debug on Kubernetes.

انتقِل للأسفل إذا لم يكن الخيار مرئيًا.

7d30833d96632ca0.png

  1. انقر على "نعم" لاستخدام السياق الحالي.

a024a69b64de7e9e.png

  1. اختَر "مجموعة الاقتباسات" التي تم إنشاؤها أثناء عملية الإعداد الأولية.

faebabf372e3caf0.png

  1. اختَر "مستودع الحاويات".

fabc6dce48bae1b4.png

  1. انقر على علامة التبويب "الإخراج" (Output) في اللوحة السفلية للاطّلاع على مستوى التقدّم والإشعارات
  2. اختَر "Kubernetes: Run/Debug - Detailed" (Kubernetes: تشغيل/تصحيح الأخطاء - تفصيلي) في القائمة المنسدلة للقناة على اليسار لعرض تفاصيل إضافية وسجلات يتم بثها مباشرةً من الحاويات.

86b44c59db58f8f3.png

انتظِر إلى أن يتم نشر التطبيق.

9f37706a752829fe.png

  1. راجِع التطبيق الذي تم نشره على GKE في Cloud Console.

6ad220e5d1980756.png

  1. يمكنك الرجوع إلى العرض المبسَّط من خلال اختيار "Kubernetes: تشغيل/تصحيح الأخطاء" من القائمة المنسدلة في علامة التبويب OUTPUT.
  2. عند اكتمال عملية الإنشاء والاختبارات، ستظهر الرسالة Resource deployment/demo-app status completed successfully في علامة التبويب "الإخراج"، وسيتم إدراج عنوان URL: "Forwarded URL from service demo-app: http://localhost:8080"
  3. في نافذة Cloud Code الطرفية، مرِّر مؤشر الماوس فوق عنوان URL في الناتج (http://localhost:8080)، ثم انقر على "الانتقال إلى الرابط" (Follow link) في تلميح الأداة الذي يظهر.

28c5539880194a8e.png

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

d67253ca16238f49.png

استخدام نقاط الإيقاف

  1. افتح تطبيق HelloController.java في /src/main/java/com/example/springboot/HelloController.java
  2. ابحث عن عبارة return للمسار الجذر الذي يقرأ return String.format("Hello from your %s environment!", target);
  3. أضِف نقطة توقّف إلى هذا السطر من خلال النقر على المساحة الفارغة على يمين رقم السطر. سيظهر مؤشر أحمر للإشارة إلى أنّه تم ضبط نقطة الإيقاف.

5027dc6da2618a39.png

  1. أعِد تحميل المتصفّح ولاحظ أنّ أداة تصحيح الأخطاء توقف العملية عند نقطة التوقف وتتيح لك التحقّق من المتغيرات وحالة التطبيق الذي يتم تشغيله عن بُعد في GKE.

71acfb426623cec2.png

  1. انقر على قسم المتغيّرات إلى أن تجد المتغيّر "الهدف".
  2. مراقبة القيمة الحالية على أنّها "محلية"

a1160d2ed2bb5c82.png

  1. انقر نقرًا مزدوجًا على اسم المتغيّر "target" (الهدف)، وفي النافذة المنبثقة،

غيِّر القيمة إلى "محطات عمل Cloud".

e597a556a5c53f32.png

  1. انقر على الزر "متابعة" في لوحة التحكّم الخاصة بتصحيح الأخطاء

ec17086191770d0d.png

  1. راجِع الردّ في المتصفّح الذي يعرض الآن القيمة المعدَّلة التي أدخلتها للتو.

6698a9db9e729925.png

  1. أزِل نقطة الإيقاف بالنقر على المؤشر الأحمر على يمين رقم السطر. سيمنع ذلك الرمز من إيقاف التنفيذ في هذا السطر أثناء تقدُّمك في هذا المختبر.

إعادة التحميل السريع

  1. غيِّر العبارة لعرض قيمة مختلفة، مثل "Hello from %s Code"
  2. يتم حفظ الملف ومزامنته تلقائيًا في الحاويات البعيدة في GKE
  3. يُرجى إعادة تحميل المتصفّح للاطّلاع على النتائج المعدَّلة.
  4. أوقِف جلسة تصحيح الأخطاء من خلال النقر على المربّع الأحمر في شريط أدوات تصحيح الأخطاء.

a541f928ec8f430e.png c2752bb28d82af86.png

اختَر "نعم، أريد التنظيف بعد كل عملية تشغيل".

984eb2fa34867d70.png

5- تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها

في هذه المرحلة، يكون تطبيقك قد تم إعداده بالكامل للتطوير ضِمن حاوية، ويكون قد تم إرشادك خلال سير عمل التطوير الأساسي باستخدام Cloud Code. في الأقسام التالية، ستتدرب على ما تعلّمته من خلال إضافة نقاط نهاية لخدمة REST تتصل بقاعدة بيانات مُدارة في Google Cloud.

ضبط التبعيات

يستخدم الرمز البرمجي للتطبيق قاعدة بيانات للاحتفاظ ببيانات خدمة REST. تأكَّد من توفّر التبعيات من خلال إضافة ما يلي في pom.xl

  1. افتح الملف pom.xml وأضِف ما يلي إلى قسم التبعيات في ملف الإعداد

pom.xml

    <!--  Database dependencies-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.postgresql</groupId>
      <artifactId>postgresql</artifactId>
      <scope>runtime</scope>
    </dependency>
    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core</artifactId>
    </dependency>
    <dependency>
      <groupId>javax.persistence</groupId>
      <artifactId>javax.persistence-api</artifactId>
      <version>2.2</version>
    </dependency>

خدمة REST للرموز

Quote.java

أنشئ ملفًا باسم Quote.java في /src/main/java/com/example/springboot/ وانسخ الرمز أدناه. تحدّد هذه السمة نموذج الكيان الخاص بكائن "عرض الأسعار" المستخدَم في التطبيق.

package com.example.springboot;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;

import java.util.Objects;

@Entity
@Table(name = "quotes")
public class Quote
{
    @Id
    @Column(name = "id")
    private Integer id;

    @Column(name="quote")
    private String quote;

    @Column(name="author")
    private String author;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getQuote() {
        return quote;
    }

    public void setQuote(String quote) {
        this.quote = quote;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) {
        return true;
      }
      if (o == null || getClass() != o.getClass()) {
        return false;
      }
        Quote quote1 = (Quote) o;
        return Objects.equals(id, quote1.id) &&
                Objects.equals(quote, quote1.quote) &&
                Objects.equals(author, quote1.author);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, quote, author);
    }
}

QuoteRepository.java

أنشئ ملفًا باسم QuoteRepository.java في src/main/java/com/example/springboot وانسخ فيه الرمز البرمجي التالي

package com.example.springboot;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;

public interface QuoteRepository extends JpaRepository<Quote,Integer> {

    @Query( nativeQuery = true, value =
            "SELECT id,quote,author FROM quotes ORDER BY RANDOM() LIMIT 1")
    Quote findRandomQuote();
}

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

QuoteController.java

لعرض نقطة نهاية الخدمة، سيوفّر الصف QuoteController هذه الوظيفة.

أنشئ ملفًا باسم QuoteController.java في src/main/java/com/example/springboot وانسخ المحتوى التالي فيه

package com.example.springboot;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QuoteController {

    private final QuoteRepository quoteRepository;

    public QuoteController(QuoteRepository quoteRepository) {
        this.quoteRepository = quoteRepository;
    }

    @GetMapping("/random-quote") 
    public Quote randomQuote()
    {
        return quoteRepository.findRandomQuote();  
    }

    @GetMapping("/quotes") 
    public ResponseEntity<List<Quote>> allQuotes()
    {
        try {
            List<Quote> quotes = new ArrayList<Quote>();
            
            quoteRepository.findAll().forEach(quotes::add);

            if (quotes.size()==0 || quotes.isEmpty()) 
                return new ResponseEntity<List<Quote>>(HttpStatus.NO_CONTENT);
                
            return new ResponseEntity<List<Quote>>(quotes, HttpStatus.OK);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<List<Quote>>(HttpStatus.INTERNAL_SERVER_ERROR);
        }        
    }

    @PostMapping("/quotes")
    public ResponseEntity<Quote> createQuote(@RequestBody Quote quote) {
        try {
            Quote saved = quoteRepository.save(quote);
            return new ResponseEntity<Quote>(saved, HttpStatus.CREATED);
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @PutMapping("/quotes/{id}")
    public ResponseEntity<Quote> updateQuote(@PathVariable("id") Integer id, @RequestBody Quote quote) {
        try {
            Optional<Quote> existingQuote = quoteRepository.findById(id);
            
            if(existingQuote.isPresent()){
                Quote updatedQuote = existingQuote.get();
                updatedQuote.setAuthor(quote.getAuthor());
                updatedQuote.setQuote(quote.getQuote());

                return new ResponseEntity<Quote>(updatedQuote, HttpStatus.OK);
            } else {
                return new ResponseEntity<Quote>(HttpStatus.NOT_FOUND);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<Quote>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }     

    @DeleteMapping("/quotes/{id}")
    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        Optional<Quote> quote = quoteRepository.findById(id);
        if (quote.isPresent()) {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

}

إضافة إعدادات قاعدة البيانات

application.yaml

أضِف إعدادات لقاعدة البيانات الخلفية التي يمكن للخدمة الوصول إليها. عدِّل الملف المسمّى application.yaml ضمن src/main/resources (أو أنشئه إذا لم يكن متوفّرًا) وأضِف إعدادات Spring ذات المَعلمات للخادم الخلفي.

target: local

spring:
  config:
    activate:
      on-profile: cloud-dev
  datasource:
    url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_NAME:quote_db}'
    username: '${DB_USER:user}'
    password: '${DB_PASS:password}'
  jpa:
    properties:
      hibernate:
        jdbc:
          lob:
            non_contextual_creation: true
        dialect: org.hibernate.dialect.PostgreSQLDialect
    hibernate:
      ddl-auto: update

إضافة ترحيل قاعدة البيانات

إنشاء مجلدات db/migration ضمن src/main/resources

أنشئ ملف SQL: V1__create_quotes_table.sql

الصِق المحتوى التالي في الملف

V1__create_quotes_table.sql

CREATE TABLE quotes(
   id INTEGER PRIMARY KEY,
   quote VARCHAR(1024),
   author VARCHAR(256)
);

INSERT INTO quotes (id,quote,author) VALUES (1,'Never, never, never give up','Winston Churchill');
INSERT INTO quotes (id,quote,author) VALUES (2,'While there''s life, there''s hope','Marcus Tullius Cicero');
INSERT INTO quotes (id,quote,author) VALUES (3,'Failure is success in progress','Anonymous');
INSERT INTO quotes (id,quote,author) VALUES (4,'Success demands singleness of purpose','Vincent Lombardi');
INSERT INTO quotes (id,quote,author) VALUES (5,'The shortest answer is doing','Lord Herbert');

إعدادات Kubernetes

تسمح الإضافات التالية إلى ملف deployment.yaml للتطبيق بالاتصال بمثيلات CloudSQL.

  • TARGET: يضبط المتغيّر للإشارة إلى البيئة التي يتم فيها تنفيذ التطبيق
  • SPRING_PROFILES_ACTIVE - تعرض ملف Spring النشط الذي سيتم ضبطه على cloud-dev
  • DB_HOST - عنوان IP الخاص بقاعدة البيانات، والذي تم تدوينه عند إنشاء مثيل قاعدة البيانات أو بالنقر على SQL في قائمة التنقّل في Google Cloud Console - يُرجى تغيير القيمة.
  • ‫DB_USER وDB_PASS: كما تم ضبطهما في إعدادات مثيل CloudSQL، ويتم تخزينهما كبيانات سرية في Google Cloud Platform

عدِّل ملف deployment.yaml باستخدام المحتوى أدناه.

deployment.yaml

apiVersion: v1
kind: Service
metadata:
  name: demo-app
  labels:
    app: demo-app
spec:
  ports:
  - port: 8080
    protocol: TCP
  clusterIP: None
  selector:
    app: demo-app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-app
  labels:
    app: demo-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: demo-app
  template:
    metadata:
      labels:
        app: demo-app
    spec:
      containers:
      - name: demo-app
        image: demo-app
        env:
          - name: PORT
            value: "8080"
          - name: TARGET
            value: "Local Dev - CloudSQL Database - K8s Cluster"
          - name: SPRING_PROFILES_ACTIVE
            value: cloud-dev
          - name: DB_HOST
            value: ${DB_INSTANCE_IP}   
          - name: DB_PORT
            value: "5432"  
          - name: DB_USER
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: username
          - name: DB_PASS
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: password
          - name: DB_NAME
            valueFrom:
              secretKeyRef:
                name: gke-cloud-sql-secrets
                key: database

استبدِل قيمة DB_HOST بعنوان قاعدة البيانات من خلال تنفيذ الأوامر أدناه في الوحدة الطرفية:

export DB_INSTANCE_IP=$(gcloud sql instances describe quote-db-instance \
    --format=json | jq \
    --raw-output ".ipAddresses[].ipAddress")

envsubst < deployment.yaml > deployment.new && mv deployment.new deployment.yaml

افتح ملف deployment.yaml وتأكَّد من تعديل قيمة DB_HOST باستخدام عنوان IP الخاص بالمثيل.

fd63c0aede14beba.png

نشر التطبيق والتحقّق من صحته

  1. في اللوحة أسفل "محرّر Cloud Shell"، اختَر "Cloud Code"، ثمّ اختَر "تصحيح الأخطاء على Kubernetes" في أعلى الشاشة.

33a5cf41aae91adb.png

  1. عند اكتمال عملية الإنشاء والاختبارات، ستظهر الرسالة التالية في علامة التبويب "الإخراج": Resource deployment/demo-app status completed successfully، وسيتم إدراج عنوان URL: "Forwarded URL from service demo-app: http://localhost:8080". يُرجى العِلم أنّه قد يختلف رقم المنفذ في بعض الأحيان، مثل 8081. إذا كان الأمر كذلك، اضبط القيمة المناسبة. ضبط قيمة عنوان URL في نافذة المحطة الطرفية
export URL=localhost:8080
  1. عرض اقتباسات عشوائية

من الوحدة الطرفية، شغِّل الأمر أدناه عدة مرات مقابل نقطة نهاية random-quote. مراقبة معاودة الاتصال بشكل متكرّر لعرض عروض أسعار مختلفة

curl $URL/random-quote | jq
  1. إضافة اقتباس

أنشئ عرض أسعار جديدًا، مع id=6 باستخدام الأمر المدرَج أدناه، ولاحظ أنّه يتم عرض الطلب مرة أخرى

curl -H 'Content-Type: application/json' -d '{"id":"6","author":"Henry David Thoreau","quote":"Go confidently in the direction of your dreams! Live the life you have imagined"}' -X POST $URL/quotes
  1. حذف عرض أسعار

احذف الآن الاقتباس الذي أضفته للتو باستخدام طريقة الحذف، ولاحظ رمز الاستجابة HTTP/1.1 204.

curl -v -X DELETE $URL/quotes/6
  1. خطأ في الخادم

تجربة حالة الخطأ من خلال تنفيذ الطلب الأخير مرة أخرى بعد حذف الإدخال

curl -v -X DELETE $URL/quotes/6

لاحظ أنّ الردّ يعرض HTTP:500 Internal Server Error.

تصحيح أخطاء التطبيق

في القسم السابق، عثرت على حالة خطأ في التطبيق عند محاولة حذف إدخال غير موجود في قاعدة البيانات. في هذا القسم، ستضبط نقطة توقّف لتحديد المشكلة. حدث الخطأ في عملية DELETE، لذا ستعمل مع فئة QuoteController.

  1. فتح "src/main/java/com/example/springboot/QuoteController.java"
  2. العثور على طريقة deleteQuote()
  3. ابحث عن السطر: Optional<Quote> quote = quoteRepository.findById(id);
  4. اضبط نقطة توقّف على هذا السطر من خلال النقر على المساحة الفارغة على يمين رقم السطر.
  5. سيظهر مؤشر أحمر يشير إلى ضبط نقطة الإيقاف
  6. نفِّذ الأمر delete مرة أخرى
curl -v -X DELETE $URL/quotes/6
  1. الرجوع إلى "طريقة عرض تصحيح الأخطاء" من خلال النقر على الرمز في العمود الأيمن
  2. لاحظ أنّ سطر تصحيح الأخطاء توقّف في فئة QuoteController.
  3. في أداة تصحيح الأخطاء، انقر على الرمز step over b814d39b2e5f3d9e.png
  4. لاحظ أنّ الرمز يعرض خطأ في الخادم الداخلي HTTP 500 للعميل، وهو أمر غير مثالي.
   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> DELETE /quotes/6 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 500
< Content-Length: 0
< Date: 
<
* Connection #0 to host 127.0.0.1 left intact

تعديل الرمز

الرمز غير صحيح ويجب إعادة تصميم كتلة else لإرسال رمز حالة HTTP 404 لم يتم العثور على الصفحة.

صحِّح الخطأ.

  1. مع استمرار تشغيل "جلسة تصحيح الأخطاء"، أكمل الطلب من خلال النقر على الزر "متابعة" في لوحة التحكّم في تصحيح الأخطاء.
  2. بعد ذلك، غيِّروا كتلة else إلى الرمز التالي:
       else {
                return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
            }

يجب أن تبدو الطريقة على النحو التالي

@DeleteMapping("/quotes/{id}")
public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        Optional<Quote> quote = quoteRepository.findById(id);
        if (quote.isPresent()) {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } else {
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        }
    }
  1. أعِد تشغيل أمر الحذف
curl -v -X DELETE $URL/quotes/6
  1. انتقِل خطوة بخطوة خلال أداة تصحيح الأخطاء ولاحظ رسالة الخطأ HTTP 404 Not Found التي تم إرجاعها إلى المتصل.
   Trying 127.0.0.1:8080...
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> DELETE /quotes/6 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.74.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404
< Content-Length: 0
< Date: 
<
* Connection #0 to host 127.0.0.1 left intact
  1. أوقِف جلسة تصحيح الأخطاء من خلال النقر على المربّع الأحمر في شريط أدوات تصحيح الأخطاء.

12bc3c82f63dcd8a.png

6f19c0f855832407.png

6. تهانينا

تهانينا! في هذا الدرس التطبيقي، أنشأت تطبيق Java جديدًا من البداية وأعددته للعمل بفعالية مع الحاويات. بعد ذلك، نشرت تطبيقك وأصلحت أخطاءه في مجموعة GKE بعيدة باتّباع مسار المطوّر نفسه المتوفّر في حِزم التطبيقات التقليدية.

ما تعلّمته

  • تطوير InnerLoop باستخدام Cloud Workstations
  • إنشاء تطبيق Java جديد للمبتدئين
  • التعرّف على عملية التطوير
  • تطوير خدمة REST بسيطة لإنشاء وقراءة وتعديل وحذف البيانات
  • تصحيح أخطاء التطبيق على مجموعة GKE
  • ربط التطبيق بقاعدة بيانات CloudSQL