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

1. نظرة عامة

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

ما سوف تتعلمه

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

  • تطوير InnerLoop باستخدام Cloud Workstations
  • إنشاء تطبيق بدء تشغيل Java جديد
  • التعرّف على عملية التطوير
  • تطوير خدمة CRUD 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

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

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

8560cc8d45e8c112.png

  1. سيتم فتح جزء جديد في أسفل النافذة.
  2. انقر على الزر Open Editor (فتح المحرر).

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)')

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

يمكنك العثور على رمز المصدر لهذا الدرس في ورشة عمل خاصة بمطوّري الحاويات على 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 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

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

7a6af5aa2807a5f2.png

افتح Cloud Workstations في Console وأنشئ مثيلاً جديدًا.

a53adeeac81a78c8.png

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

f21c216997746097.png

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

66a9fc8b20543e32.png

تشغيل محطة العمل

ابدأ تشغيل محطة العمل وشغِّلها.

c91bb69b61ec8635.png

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

fcf9405b6957b7d7.png

انقر على "الموقع الإلكتروني لا يعمل؟".

36a84c0e2e3b85b.png

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

2259694328628fba.png

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

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. افتح "نموذج التطبيق" مجلد
cd sample-app && code-oss-cloud-workstations -r --folder-uri="$PWD"

إضافة أدوات مطوّري برامج Spring-boot-devtools جيب

لتفعيل أدوات مطوري البرامج لـ Spring Boot، ابحث عن 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 هي أداة مفتوحة المصدر لحاويات Java من Google تتيح لمطوّري البرامج في 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.

ناتج سقالة:

b33cc1e0c2077ab8.png

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

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

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

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

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

لاحظ أن "مزامنة" الذي تقوم بتهيئته في تهيئة Skaffold الذي يستفيد من "مزامنة" Spring الملف الشخصي الذي أعددته في الخطوة السابقة، حيث فعّلت دعم أدوات spring-dev.

  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

انقر على رمز رمز السحابة الإلكترونية واختَر "تسجيل الدخول إلى 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. في الجزء الأيمن في الجزء السفلي، حدد Cloud Code.

60b8e4e95868b561.png

  1. في اللوحة التي تظهر ضمن "جلسات التطوير"، اختَر Debug on Kubernetes.

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

7d30833d96632ca0.png

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

a024a69b64de7e9e.png

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

faebabf372e3caf0.png

  1. حدد مستودع الحاوية.

fabc6dce48bae1b4.png

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

86b44c59db58f8f3.png

انتظر حتى يتم نشر التطبيق.

9f37706a752829fe.png

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

6ad220e5d1980756.png

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

28c5539880194a8e.png

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

d67253ca16238f49.png

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

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

5027dc6da2618a39.png

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

71acfb426623cec2.png

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

a1160d2ed2bb5c82.png

  1. انقر مرّتين على اسم المتغيّر "target". وفي النافذة المنبثقة،

تغيير القيمة إلى "Cloud Workstations"

e597a556a5c53f32.png

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

ec17086191770d0d.png

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

6698a9db9e729925.png

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

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

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

a541f928ec8f430e.png c2752bb28d82af86.png

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

984eb2fa34867d70.png

5- تطوير خدمة CRUD Rest بسيطة

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

إعداد التبعيات

يستخدم رمز التطبيق قاعدة بيانات للاحتفاظ بباقي بيانات الخدمة. تأكَّد من توفُّر التبعيات من خلال إضافة ما يلي في 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>

خدمة Code 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 - يُرجى تغيير القيمة !
  • DB_USER وDB_PASS - كما تم ضبطه في إعداد مثيل CloudSQL، ويتم تخزينه كسر في GCP

يُرجى تعديل ملف Publishing.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

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

fd63c0aede14beba.png

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

  1. في الجزء السفلي من "محرِّر Cloud Shell"، اختَر Cloud Code ثم اختَر Debug on Kubernetes في أعلى الشاشة.

33a5cf41aae91adb.png

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

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

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، ولذلك ستعمل مع فئة SurveyController.

  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. لاحِظ أنّ سطر تصحيح الأخطاء توقّف في فئة QuestionController.
  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. بينما لا تزال جلسة تصحيح الأخطاء قيد التشغيل، أكمل الطلب بالضغط على زر "continue" في لوحة تحكم تصحيح الأخطاء.
  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 جديد
  • التعرّف على عملية التطوير
  • تطوير خدمة CRUD REST بسيطة
  • تصحيح أخطاء التطبيق في مجموعة GKE
  • ربط التطبيق بقاعدة بيانات CloudSQL