تطوير حلقة InnerLoop باستخدام Java - SpringBoot

1. نظرة عامة

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

ما سوف تتعلمه

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

  • الإعداد والمتطلبات
  • إنشاء تطبيق بدء تشغيل Java جديد
  • التعرّف على عملية التطوير
  • تطوير خدمة CRUD Rest بسيطة
  • تنظيف

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 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. يعمل النص البرمجي للإعداد أدناه على إعداد هذه البنية الأساسية من أجلك. ستستغرق عملية توفير المتطلبات اللازمة أكثر من 10 دقائق. يمكنك مواصلة تنفيذ الخطوات القليلة التالية أثناء معالجة عملية الإعداد.

./setup.sh

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

في هذا القسم، ستنشئ تطبيقًا جديدًا لـ Java Spring Boot من الصفر باستخدام نموذج تطبيق يوفره spring.io

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

  1. إنشاء تطبيق للمبتدئين
curl  https://start.spring.io/starter.zip -d dependencies=web -d type=maven-project -d javaVersion=11 -d packageName=com.example.springboot -o sample-app.zip
  1. فك ضغط التطبيق
unzip sample-app.zip -d sample-app
  1. انتقِل إلى نموذج دليل التطبيق وافتح المجلد في مساحة عمل Cloud Shell IDE.
cd sample-app && cloudshell workspace .

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

لتفعيل أدوات مطوري البرامج لـ Spring Boot، ابحث عن pom.xml وافتحه من المستكشف في المحرِّر. بعد ذلك، الصق الرمز التالي بعد سطر الوصف التالي: <description>مشروع تجريبي لـ 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>

اختَر Always إذا طُلب منك ذلك بشأن تغيير ملف الإصدار.

447a90338f51931f.png

إنشاء ملفات بيانات

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

نفِّذ الأمر أدناه لبدء العملية.

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

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

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

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

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

تفعيل المزامنة السريعة

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

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

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

في الملف 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/java: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 مع Saffold لتبسيط عملية التطوير. عند النشر على GKE باتّباع الخطوات التالية، ستنشئ Cloud Code وSkaffold صورة الحاوية تلقائيًا وترسلها إلى Container Registry ثم تنشر تطبيقك على GKE. يحدث هذا وراء الكواليس في استخلاص التفاصيل بعيدًا عن تدفق المطور. تُحسّن Cloud Code أيضًا عملية التطوير من خلال توفير إمكانات تقليدية لتصحيح الأخطاء والمزامنة من أجل التطوير المستند إلى الحاوية.

النشر على Kubernetes

  1. في الجزء السفلي من "محرِّر Cloud Shell"، اختَر Cloud Code ⁠

fdc797a769040839.png

  1. في اللوحة التي تظهر في أعلى الصفحة، اختَر Debug on Kubernetes. إذا طُلب منك ذلك، اختَر "نعم" لاستخدام سياق Kubernetes الحالي.

cfce0d11ef307087.png

  1. في المرة الأولى التي تشغِّل فيها الأمر، سيظهر لك في أعلى الشاشة يسألك ما إذا كنت تريد الحصول على سياق Kubernetes الحالي، اختَر "نعم". قبول السياق الحالي واستخدامه.

817ee33b5b412ff8.png

  1. بعد ذلك، سيتم عرض طلب يسأل عن قاعدة بيانات المسجّلين في الحاوية المطلوب استخدامها. اضغط على مفتاح Enter لقبول القيمة التلقائية المقدَّمة.

eb4469aed97a25f6.png

  1. اختَر علامة التبويب "الإخراج" في الجزء السفلي لعرض مستوى التقدّم والإشعارات.

f95b620569ba96c5.png

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

94acdcdda6d2108.png

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

سيكون الرد:

Hello from your local environment!

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

  1. افتح التطبيق HelloController.java الموجود على /src/main/java/com/example/springboot/HelloController.java
  2. حدِّد عبارة الإرجاع للمسار الجذر الذي يظهر فيه return String.format("Hello from your %s environment!", target);.
  3. أضف نقطة توقف إلى هذا السطر بالنقر على المساحة الفارغة على يمين رقم السطر. سيظهر مؤشر أحمر للإشارة إلى ضبط نقطة الإيقاف.
  4. أعد تحميل المتصفح ولاحظ أن برنامج تصحيح الأخطاء يوقف العملية عند نقطة الإيقاف ويتيح لك التحقق من حالة الرمال المتغيرة للتطبيق الذي يعمل عن بُعد في GKE
  5. انقر للأسفل في قسم "المتغيّرات" حتى تعثر على الخيار "Target" المتغير.
  6. يجب ملاحظة القيمة الحالية على أنّها "محلية".
  7. انقر مرّتين على اسم المتغيّر "target". وفي النافذة المنبثقة، غيِّر القيمة إلى اسم مختلف مثل "سحابة إلكترونية".
  8. انقر على الزر "متابعة" في لوحة تحكم تصحيح الأخطاء.
  9. يمكنك مراجعة الردّ في متصفّحك الذي يعرض الآن القيمة المعدّلة التي أدخلتها للتو.

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

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

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

في هذه المرحلة، تم ضبط تطبيقك بالكامل للتطوير المجمّع، وقد اطّلعت على خطوات سير عمل التطوير الأساسي باستخدام Cloud Code. تتدرب في الأقسام التالية على ما تعلمته عن طريق إضافة نقاط نهاية خدمة غير مكتملة متصلة بقاعدة بيانات مُدارة في 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>

ترميز خدمة الباقي

Quote.java

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

package com.example.springboot;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.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

أنشئ ملفًا يسمى CookieRepository.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 هذه الوظيفة.

أنشئ ملفًا باسم ChartController.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) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            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

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

إنشاء مجلد على src/main/resources/db/migration/

إنشاء ملف 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

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

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

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

curl -v 127.0.0.1:8080/random-quote
  1. إضافة عرض أسعار تقديري

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

curl -v -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 127.0.0.1:8080/quotes
  1. حذف عرض أسعار تقديري

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

curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. حدث خطأ في الخادم.

ظهور حالة خطأ من خلال إعادة تنفيذ الطلب الأخير بعد حذف الإدخال

curl -v -X DELETE 127.0.0.1:8080/quotes/6

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

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

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

  1. افتح src.main.java.com.example.springboot.السعروحدة تحكّم.java
  2. البحث عن طريقة deleteQuote()
  3. ابحث عن السطر الذي يؤدي إلى حذف عنصر من قاعدة البيانات: quoteRepository.deleteById(id);.
  4. حدد نقطة توقف في هذا السطر بالنقر على المساحة الفارغة على يمين رقم السطر.
  5. سيظهر مؤشر أحمر يشير إلى ضبط نقطة الإيقاف.
  6. أعِد تشغيل الأمر delete.
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. التبديل مرة أخرى إلى عرض تصحيح الأخطاء من خلال النقر على الرمز في العمود الأيمن
  2. لاحِظ أنّ سطر تصحيح الأخطاء توقّف في فئة QuestionController.
  3. في برنامج تصحيح الأخطاء، انقر على الرمز step over b814d39b2e5f3d9e.png ولاحظ أنّه قد تم طرح استثناء.
  4. لاحِظ أنّ كلمة RuntimeException was caught. عامة جدًا تؤدي إلى عرض خطأ 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

تعديل الرمز البرمجي

الرمز غير صحيح ويجب إعادة ضبط مجموعة الاستثناء ليتم رصد استثناء EmptyResultDataAccessException وإرسال رمز الحالة "لم يتم العثور على الصفحة" باستخدام HTTP 404.

صحِّح الخطأ.

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

ينبغي أن تبدو الطريقة كما يلي

    public ResponseEntity<HttpStatus> deleteQuote(@PathVariable("id") Integer id) {
        try {
            quoteRepository.deleteById(id);
            return new ResponseEntity<>(HttpStatus.NO_CONTENT);
        } catch(EmptyResultDataAccessException e){
            return new ResponseEntity<HttpStatus>(HttpStatus.NOT_FOUND);
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }
  1. إعادة تشغيل أمر الحذف
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. تنقَّل في برنامج تصحيح الأخطاء وتحقَّق من رصد EmptyResultDataAccessException ورسالة HTTP 404 لم يتم العثور عليها وتم إرجاعها إلى المتصل.
   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. أوقِف جلسة تصحيح الأخطاء بالنقر على المربّع الأحمر في شريط أدوات تصحيح الأخطاء a13d42d726213e6c.png.

6- تنظيف

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

لتنظيف البيانات بعد الانتهاء من التمرين المعملي:

  1. حذف الملفات المستخدمة في التمرين المعملي
cd ~ && rm -rf container-developer-workshop
  1. حذف المشروع لإزالة جميع البنية التحتية والموارد ذات الصلة