1. نظرة عامة
يوضّح هذا الدرس التطبيقي الميزات والإمكانات المصمَّمة لتبسيط سير عمل التطوير لمهندسي البرامج المكلّفين بتطوير تطبيقات Java في بيئة تستخدم حاويات. يتطلّب تطوير الحاويات المعتاد أن يفهم المستخدم تفاصيل الحاويات وعملية التصميم. بالإضافة إلى ذلك، على المطوّرين عادةً إيقاف عملية التطوير مؤقتًا والانتقال من بيئة التطوير المتكاملة (IDE) لاختبار تطبيقاتهم وتصحيح الأخطاء فيها في بيئات بعيدة. باستخدام الأدوات والتكنولوجيات المذكورة في هذا البرنامج التعليمي، يمكن للمطوّرين العمل بفعالية مع التطبيقات المستندة إلى حاويات بدون مغادرة بيئة التطوير المتكاملة.
ما ستتعلمه
في هذا الدرس التطبيقي، ستتعرّف على طرق التطوير باستخدام الحاويات في Google Cloud Platform، بما في ذلك:
- تطوير InnerLoop باستخدام Cloud Workstations
- إنشاء تطبيق Java جديد للمبتدئين
- التعرّف على عملية التطوير
- تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها
- تصحيح أخطاء التطبيق على مجموعة GKE
- ربط التطبيق بقاعدة بيانات CloudSQL

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



- اسم المشروع هو الاسم المعروض للمشاركين في هذا المشروع. وهي سلسلة أحرف لا تستخدمها Google APIs. ويمكنك تعديله في أي وقت.
- رقم تعريف المشروع هو معرّف فريد في جميع مشاريع Google Cloud ولا يمكن تغييره بعد ضبطه. تنشئ Cloud Console تلقائيًا سلسلة فريدة، ولا يهمّك عادةً ما هي. في معظم دروس البرمجة، عليك الرجوع إلى رقم تعريف المشروع (يتم تحديده عادةً على أنّه
PROJECT_ID). إذا لم يعجبك رقم التعريف الذي تم إنشاؤه، يمكنك إنشاء رقم تعريف عشوائي آخر. يمكنك بدلاً من ذلك تجربة اسم مستخدم من اختيارك ومعرفة ما إذا كان متاحًا. لا يمكن تغيير هذا الخيار بعد هذه الخطوة وسيظل ساريًا طوال مدة المشروع. - للعلم، هناك قيمة ثالثة، وهي رقم المشروع الذي تستخدمه بعض واجهات برمجة التطبيقات. يمكنك الاطّلاع على مزيد من المعلومات عن كل هذه القيم الثلاث في المستندات.
- بعد ذلك، عليك تفعيل الفوترة في Cloud Console لاستخدام موارد/واجهات برمجة تطبيقات Cloud. لن تكلفك تجربة هذا الدرس البرمجي الكثير من المال، إن لم تكلفك شيئًا على الإطلاق. لإيقاف الموارد كي لا يتم تحصيل رسوم منك بعد هذا البرنامج التعليمي، يمكنك حذف الموارد التي أنشأتها أو حذف المشروع بأكمله. يمكن لمستخدمي Google Cloud الجدد الاستفادة من برنامج الفترة التجريبية المجانية بقيمة 300 دولار أمريكي.
بدء Cloudshell Editor
تم تصميم هذا الدرس التطبيقي واختباره لاستخدامه مع "محرّر Google Cloud Shell". للوصول إلى المحرِّر، اتّبِع الخطوات التالية:
- الوصول إلى مشروعك على Google من خلال https://console.cloud.google.com
- في أعلى يسار الصفحة، انقر على رمز محرر Cloud Shell

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

- سيتم فتح المحرِّر مع مستكشف على اليسار ومحرِّر في المنطقة الوسطى
- يجب أن يتوفّر جزء وحدة طرفية أيضًا في أسفل الشاشة
- إذا لم تكن النافذة الطرفية مفتوحة، استخدِم مجموعة المفاتيح `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.
إنشاء إعدادات "محطات عمل لتعديل المحتوى"
إذا تم قطع اتصال جلسة 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 دقيقتَين.

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

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

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

تشغيل Workstation
بدء محطة العمل وإطلاقها

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

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

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

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

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

استنساخ نموذج التطبيق
- إنشاء تطبيق أوّلي
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
انقر على زر "السماح" إذا ظهرت لك هذه الرسالة، لتتمكّن من نسخ المحتوى ولصقه في محطة العمل.

- فكّ ضغط التطبيق
unzip sample-app.zip -d sample-app
- افتح المجلد "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>
- إضافة 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>
- تفعيل 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، ثم تنشئ بيان نشر وبيان خدمة لكل دليل.
نفِّذ الأمر التالي في "الوحدة الطرفية" لبدء العملية.

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

تعديل اسم التطبيق
لا تتطابق القيم التلقائية المُضمَّنة في الإعداد حاليًا مع اسم تطبيقك. عدِّل الملفات للإشارة إلى اسم تطبيقك بدلاً من القيم التلقائية.
- تغيير الإدخالات في ملف إعدادات Skaffold
- فتح "
skaffold.yaml" - اختَر اسم الصورة المضبوطة حاليًا على
pom-xml-image - انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
- اكتب الاسم الجديد باللغة
demo-app
- تغيير الإدخالات في إعدادات Kubernetes
- فتح ملف
deployment.yaml - اختَر اسم الصورة المضبوطة حاليًا على
pom-xml-image - انقر بزر الماوس الأيمن واختَر "تغيير كل التكرارات"
- اكتب الاسم الجديد باللغة
demo-app
تفعيل وضع المزامنة التلقائية
لتسهيل تجربة إعادة التحميل السريع المحسّنة، عليك استخدام ميزة "المزامنة" التي توفّرها Jib. في هذه الخطوة، ستضبط Skaffold لاستخدام هذه الميزة في عملية التصميم.
يُرجى العِلم أنّ ملف الإعداد "sync" الذي تعمل على إعداده في إعدادات Skaffold يستفيد من ملف الإعداد "sync" في Spring الذي أعددته في الخطوة السابقة، حيث فعّلت إمكانية استخدام spring-dev-tools.
- تعديل إعدادات 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/.

الصِق المحتوى التالي في الملف لإنشاء مسار 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":

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

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

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

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

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

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

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

- اختَر Google Kubernetes Engine:

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

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


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

نفِّذ أمر gcloud cli لضبط رقم تعريف المشروع. استبدِل رقم تعريف المشروع النموذجي قبل تنفيذ الأمر.
gcloud config set project qwiklabs-gcp-project-id
مثال على الناتج:

تصحيح الأخطاء على Kubernetes
- في اللوحة اليمنى في أسفل الشاشة، انقر على "رمز السحابة".

- في اللوحة التي تظهر ضمن DEVELOPMENT SESSIONS، اختَر Debug on Kubernetes.
انتقِل للأسفل إذا لم يكن الخيار مرئيًا.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

5- تطوير خدمة REST بسيطة لإنشاء البيانات وقراءتها وتعديلها وحذفها
في هذه المرحلة، يكون تطبيقك قد تم إعداده بالكامل للتطوير ضِمن حاوية، ويكون قد تم إرشادك خلال سير عمل التطوير الأساسي باستخدام Cloud Code. في الأقسام التالية، ستتدرب على ما تعلّمته من خلال إضافة نقاط نهاية لخدمة REST تتصل بقاعدة بيانات مُدارة في Google Cloud.
ضبط التبعيات
يستخدم الرمز البرمجي للتطبيق قاعدة بيانات للاحتفاظ ببيانات خدمة REST. تأكَّد من توفّر التبعيات من خلال إضافة ما يلي في pom.xl
- افتح الملف
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 الخاص بالمثيل.

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

- عند اكتمال عملية الإنشاء والاختبارات، ستظهر الرسالة التالية في علامة التبويب "الإخراج":
Resource deployment/demo-app status completed successfully، وسيتم إدراج عنوان URL: "Forwarded URL from service demo-app: http://localhost:8080". يُرجى العِلم أنّه قد يختلف رقم المنفذ في بعض الأحيان، مثل 8081. إذا كان الأمر كذلك، اضبط القيمة المناسبة. ضبط قيمة عنوان URL في نافذة المحطة الطرفية
export URL=localhost:8080
- عرض اقتباسات عشوائية
من الوحدة الطرفية، شغِّل الأمر أدناه عدة مرات مقابل نقطة نهاية random-quote. مراقبة معاودة الاتصال بشكل متكرّر لعرض عروض أسعار مختلفة
curl $URL/random-quote | jq
- إضافة اقتباس
أنشئ عرض أسعار جديدًا، مع 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
- حذف عرض أسعار
احذف الآن الاقتباس الذي أضفته للتو باستخدام طريقة الحذف، ولاحظ رمز الاستجابة HTTP/1.1 204.
curl -v -X DELETE $URL/quotes/6
- خطأ في الخادم
تجربة حالة الخطأ من خلال تنفيذ الطلب الأخير مرة أخرى بعد حذف الإدخال
curl -v -X DELETE $URL/quotes/6
لاحظ أنّ الردّ يعرض HTTP:500 Internal Server Error.
تصحيح أخطاء التطبيق
في القسم السابق، عثرت على حالة خطأ في التطبيق عند محاولة حذف إدخال غير موجود في قاعدة البيانات. في هذا القسم، ستضبط نقطة توقّف لتحديد المشكلة. حدث الخطأ في عملية DELETE، لذا ستعمل مع فئة QuoteController.
- فتح "
src/main/java/com/example/springboot/QuoteController.java" - العثور على طريقة
deleteQuote() - ابحث عن السطر:
Optional<Quote> quote = quoteRepository.findById(id); - اضبط نقطة توقّف على هذا السطر من خلال النقر على المساحة الفارغة على يمين رقم السطر.
- سيظهر مؤشر أحمر يشير إلى ضبط نقطة الإيقاف
- نفِّذ الأمر
deleteمرة أخرى
curl -v -X DELETE $URL/quotes/6
- الرجوع إلى "طريقة عرض تصحيح الأخطاء" من خلال النقر على الرمز في العمود الأيمن
- لاحظ أنّ سطر تصحيح الأخطاء توقّف في فئة QuoteController.
- في أداة تصحيح الأخطاء، انقر على الرمز
step over
- لاحظ أنّ الرمز يعرض خطأ في الخادم الداخلي 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 لم يتم العثور على الصفحة.
صحِّح الخطأ.
- مع استمرار تشغيل "جلسة تصحيح الأخطاء"، أكمل الطلب من خلال النقر على الزر "متابعة" في لوحة التحكّم في تصحيح الأخطاء.
- بعد ذلك، غيِّروا كتلة
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);
}
}
- أعِد تشغيل أمر الحذف
curl -v -X DELETE $URL/quotes/6
- انتقِل خطوة بخطوة خلال أداة تصحيح الأخطاء ولاحظ رسالة الخطأ 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
- أوقِف جلسة تصحيح الأخطاء من خلال النقر على المربّع الأحمر في شريط أدوات تصحيح الأخطاء.


6. تهانينا
تهانينا! في هذا الدرس التطبيقي، أنشأت تطبيق Java جديدًا من البداية وأعددته للعمل بفعالية مع الحاويات. بعد ذلك، نشرت تطبيقك وأصلحت أخطاءه في مجموعة GKE بعيدة باتّباع مسار المطوّر نفسه المتوفّر في حِزم التطبيقات التقليدية.
ما تعلّمته
- تطوير InnerLoop باستخدام Cloud Workstations
- إنشاء تطبيق Java جديد للمبتدئين
- التعرّف على عملية التطوير
- تطوير خدمة REST بسيطة لإنشاء وقراءة وتعديل وحذف البيانات
- تصحيح أخطاء التطبيق على مجموعة GKE
- ربط التطبيق بقاعدة بيانات CloudSQL
