การพัฒนา InnerLoop ด้วย Java - SpringBoot

1. ภาพรวม

แล็บนี้จะสาธิตฟีเจอร์และความสามารถที่ออกแบบมาเพื่อเพิ่มประสิทธิภาพเวิร์กโฟลว์การพัฒนาสำหรับวิศวกรซอฟต์แวร์ที่ได้รับมอบหมายให้พัฒนาแอปพลิเคชัน Java ในสภาพแวดล้อมที่มีคอนเทนเนอร์ การพัฒนาคอนเทนเนอร์โดยทั่วไปกำหนดให้ผู้ใช้ต้องเข้าใจรายละเอียดของคอนเทนเนอร์และกระบวนการบิลด์คอนเทนเนอร์ นอกจากนี้ โดยปกติแล้ว นักพัฒนาแอปจะต้องหยุดโฟลว์การทำงาน ย้ายออกจาก IDE เพื่อทดสอบและแก้ไขข้อบกพร่องของแอปพลิเคชันในสภาพแวดล้อมระยะไกล เครื่องมือและเทคโนโลยีที่กล่าวถึงในบทแนะนำนี้ช่วยให้นักพัฒนาแอปทำงานกับแอปพลิเคชันที่ใช้คอนเทนเนอร์ได้อย่างมีประสิทธิภาพโดยไม่ต้องออกจาก IDE

สิ่งที่คุณจะได้เรียนรู้

ในแล็บนี้ คุณจะได้เรียนรู้วิธีการพัฒนาด้วยคอนเทนเนอร์ใน GCP ซึ่งรวมถึง

  • การตั้งค่าและข้อกำหนด
  • การสร้างแอปพลิเคชันเริ่มต้น Java ใหม่
  • การเดินผ่านกระบวนการพัฒนา
  • การพัฒนาบริการ REST แบบ CRUD อย่างง่าย
  • ล้างข้อมูล

2. การตั้งค่าและข้อกำหนด

การตั้งค่าสภาพแวดล้อมแบบเรียนรู้ด้วยตนเอง

  1. ลงชื่อเข้าใช้ คอนโซล Google Cloud แล้วสร้างโปรเจ็กต์ใหม่หรือใช้โปรเจ็กต์ที่มีอยู่ซ้ำ หากยังไม่มีบัญชี Gmail หรือ Google Workspace คุณต้องสร้างบัญชี

b35bf95b8bf3d5d8.pnga99b7ace416376c4.pngbd84a6d3004737c5.png

  • ชื่อโปรเจ็กต์คือชื่อที่แสดงสำหรับผู้เข้าร่วมโปรเจ็กต์นี้ โดยเป็นสตริงอักขระที่ Google APIs ไม่ได้ใช้ และคุณสามารถอัปเดตได้ทุกเมื่อ
  • รหัสโปรเจ็กต์ต้องไม่ซ้ำกันในโปรเจ็กต์ Google Cloud ทั้งหมดและเปลี่ยนแปลงไม่ได้ (เปลี่ยนไม่ได้หลังจากตั้งค่าแล้ว) Cloud Console จะสร้างสตริงที่ไม่ซ้ำกันโดยอัตโนมัติ ซึ่งโดยปกติแล้วคุณไม่จำเป็นต้องสนใจว่าสตริงนั้นคืออะไร ใน Codelab ส่วนใหญ่ คุณจะต้องอ้างอิงรหัสโปรเจ็กต์ (และโดยทั่วไปจะระบุเป็น PROJECT_ID) ดังนั้นหากไม่ชอบรหัสนี้ ให้สร้างรหัสแบบสุ่มอีกรหัส หรือคุณจะลองใช้รหัสของคุณเองและดูว่ามีรหัสนั้นหรือไม่ก็ได้ จากนั้นระบบจะ "ตรึง" ไว้หลังจากสร้างโปรเจ็กต์แล้ว
  • นอกจากนี้ยังมีค่าที่ 3 คือหมายเลขโปรเจ็กต์ที่ API บางตัวใช้ ดูข้อมูลเพิ่มเติมเกี่ยวกับค่าทั้ง 3 นี้ได้ในเอกสารประกอบ
  1. จากนั้นคุณจะต้องเปิดใช้การเรียกเก็บเงินใน Cloud Console เพื่อใช้ทรัพยากร/API ของ Cloud การทำตาม Codelab นี้ไม่ควรมีค่าใช้จ่ายมากนัก หรืออาจไม่มีเลย หากต้องการปิดแหล่งข้อมูลเพื่อไม่ให้มีการเรียกเก็บเงินนอกเหนือจากบทแนะนำนี้ ให้ทำตามวิธีการ "ล้างข้อมูล" ที่ตอนท้ายของ Codelab ผู้ใช้ Google Cloud รายใหม่มีสิทธิ์เข้าร่วมโปรแกรมช่วงทดลองใช้ฟรีมูลค่า$300 USD

เริ่มโปรแกรมแก้ไข Cloud Shell

แล็บนี้ออกแบบและทดสอบเพื่อใช้กับ Google Cloud Shell Editor วิธีเข้าถึงเอดิเตอร์

  1. เข้าถึงโปรเจ็กต์ Google ที่ https://console.cloud.google.com
  2. คลิกไอคอนโปรแกรมแก้ไข Cloud Shell ที่มุมขวาบน

8560cc8d45e8c112.png

  1. แผงใหม่จะเปิดขึ้นที่ด้านล่างของหน้าต่าง
  2. คลิกปุ่ม "เปิดเครื่องมือแก้ไข"

9e504cb98a6a8005.png

  1. เครื่องมือแก้ไขจะเปิดขึ้นพร้อมกับ Explorer ทางด้านขวาและเครื่องมือแก้ไขในพื้นที่ส่วนกลาง
  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)')

รับซอร์สโค้ด

ซอร์สโค้ดสำหรับแล็บนี้อยู่ใน container-developer-workshop ใน GoogleCloudPlatform บน GitHub โคลนด้วยคำสั่งด้านล่าง แล้วเปลี่ยนเป็นไดเรกทอรี

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

จัดสรรโครงสร้างพื้นฐานที่ใช้ในแล็บนี้

ในแล็บนี้ คุณจะได้ติดตั้งใช้งานโค้ดใน GKE และเข้าถึงข้อมูลที่จัดเก็บไว้ในฐานข้อมูล Cloud SQL สคริปต์การตั้งค่าด้านล่างจะเตรียมโครงสร้างพื้นฐานนี้ให้คุณ กระบวนการจัดสรรจะใช้เวลามากกว่า 10 นาที คุณดำเนินการใน 2-3 ขั้นตอนถัดไปได้ในขณะที่ระบบกำลังตั้งค่า

./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. เปลี่ยนเป็นไดเรกทอรี sample-app แล้วเปิดโฟลเดอร์ในพื้นที่ทำงานของ Cloud Shell IDE
cd sample-app && cloudshell workspace .

เพิ่ม spring-boot-devtools และ Jib

หากต้องการเปิดใช้ Spring Boot DevTools ให้ค้นหาและเปิด pom.xml จาก Explorer ในโปรแกรมแก้ไข จากนั้นวางโค้ดต่อไปนี้หลังบรรทัดรายละเอียดที่ระบุว่า <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 ใน 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

สร้างไฟล์ Manifest

Skaffold มีเครื่องมือแบบผสานรวมที่จะช่วยลดความซับซ้อนในการพัฒนาคอนเทนเนอร์ ในขั้นตอนนี้ คุณจะเริ่มต้น skaffold ซึ่งจะสร้างไฟล์ YAML ของ Kubernetes ฐานโดยอัตโนมัติ กระบวนการนี้จะพยายามระบุไดเรกทอรีที่มีคำจำกัดความของอิมเมจคอนเทนเนอร์ เช่น Dockerfile จากนั้นจะสร้างไฟล์ Manifest การทำให้ใช้งานได้และบริการสำหรับแต่ละรายการ

เรียกใช้คำสั่งด้านล่างเพื่อเริ่มกระบวนการ

  1. เรียกใช้คำสั่งต่อไปนี้ในเทอร์มินัล
skaffold init --generate-manifests
  1. เมื่อได้รับข้อความแจ้ง ให้ทำดังนี้
  • ใช้ลูกศรเพื่อเลื่อนเคอร์เซอร์ไปที่ Jib Maven Plugin
  • กดแป้นเว้นวรรคเพื่อเลือกตัวเลือก
  • กด Enter เพื่อดำเนินการต่อ
  1. ป้อน 8080 สำหรับพอร์ต
  2. ป้อน y เพื่อบันทึกการกำหนดค่า

ระบบจะเพิ่มไฟล์ 2 ไฟล์ลงในพื้นที่ทำงาน ได้แก่ skaffold.yaml และ deployment.yaml

อัปเดตชื่อแอป

ปัจจุบันค่าเริ่มต้นที่รวมอยู่ในการกำหนดค่าไม่ตรงกับชื่อแอปพลิเคชันของคุณ อัปเดตไฟล์เพื่ออ้างอิงชื่อแอปพลิเคชันแทนค่าเริ่มต้น

  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/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. การเดินผ่านกระบวนการพัฒนา

ในส่วนนี้ คุณจะได้ทำตามขั้นตอน 2-3 ขั้นตอนโดยใช้ปลั๊กอิน Cloud Code เพื่อเรียนรู้กระบวนการพื้นฐาน รวมถึงตรวจสอบการกำหนดค่าและการตั้งค่าของแอปพลิเคชันเริ่มต้น

Cloud Code ผสานรวมกับ Skaffold เพื่อเพิ่มประสิทธิภาพกระบวนการพัฒนา เมื่อคุณทําการติดตั้งใช้งานใน GKE ในขั้นตอนต่อไปนี้ Cloud Code และ Skaffold จะสร้างอิมเมจคอนเทนเนอร์ พุชไปยัง Container Registry แล้วจึงทําการติดตั้งใช้งานแอปพลิเคชันใน GKE โดยอัตโนมัติ ซึ่งจะเกิดขึ้นเบื้องหลังโดยซ่อนรายละเอียดจากขั้นตอนการทำงานของนักพัฒนาแอป นอกจากนี้ Cloud Code ยังช่วยเพิ่มประสิทธิภาพกระบวนการพัฒนาด้วยการมอบความสามารถในการแก้ไขข้อบกพร่องและการซิงค์ด่วนแบบเดิมให้กับการพัฒนาที่อิงตามคอนเทนเนอร์

ทําให้ใช้งานได้ใน Kubernetes

  1. เลือก Cloud Code  ในบานหน้าต่างที่ด้านล่างของ Cloud Shell Editor

fdc797a769040839.png

  1. เลือก "แก้ไขข้อบกพร่องใน Kubernetes" ในแผงที่ปรากฏขึ้นที่ด้านบน หากได้รับข้อความแจ้ง ให้เลือก "ใช่" เพื่อใช้บริบท Kubernetes ปัจจุบัน

cfce0d11ef307087.png

  1. เมื่อเรียกใช้คำสั่งเป็นครั้งแรก ข้อความแจ้งจะปรากฏที่ด้านบนของหน้าจอเพื่อถามว่าคุณต้องการใช้บริบท Kubernetes ปัจจุบันหรือไม่ ให้เลือก "ใช่" เพื่อยอมรับและใช้บริบทปัจจุบัน

817ee33b5b412ff8.png

  1. จากนั้นระบบจะแสดงข้อความแจ้งเพื่อถามว่าควรใช้รีจิสทรีคอนเทนเนอร์ใด กด Enter เพื่อยอมรับค่าเริ่มต้นที่ระบุ

eb4469aed97a25f6.png

  1. เลือกแท็บเอาต์พุตในบานหน้าต่างด้านล่างเพื่อดูความคืบหน้าและการแจ้งเตือน

f95b620569ba96c5.png

  1. เลือก "Kubernetes: เรียกใช้/แก้ไขข้อบกพร่อง - โดยละเอียด" ในเมนูแบบเลื่อนลงของช่องทางทางด้านขวาเพื่อดูรายละเอียดเพิ่มเติมและบันทึกที่สตรีมแบบสดจากคอนเทนเนอร์

94acdcdda6d2108.png

  1. กลับไปที่มุมมองแบบง่ายโดยเลือก "Kubernetes: เรียกใช้/แก้ไขข้อบกพร่อง" จากเมนูแบบเลื่อนลง
  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) จากนั้นเลือก "เปิดตัวอย่างเว็บ" ในเคล็ดลับเครื่องมือที่ปรากฏขึ้น

คำตอบจะเป็นดังนี้

Hello from your local environment!

ใช้เบรกพอยท์

  1. เปิดแอปพลิเคชัน HelloController.java ที่ /src/main/java/com/example/springboot/HelloController.java
  2. ค้นหาคำสั่ง "return" สำหรับเส้นทางรูทซึ่งอ่านว่า return String.format("Hello from your %s environment!", target);
  3. เพิ่มเบรกพอยต์ในบรรทัดนั้นโดยคลิกพื้นที่ว่างทางด้านซ้ายของหมายเลขบรรทัด ตัวบ่งชี้สีแดงจะแสดงขึ้นเพื่อระบุว่ามีการตั้งค่าเบรกพอยต์
  4. โหลดเบราว์เซอร์ซ้ำและสังเกตว่าดีบักเกอร์จะหยุดกระบวนการที่เบรกพอยต์และช่วยให้คุณตรวจสอบตัวแปรและสถานะของแอปพลิเคชันที่ทำงานจากระยะไกลใน GKE ได้
  5. คลิกส่วนตัวแปรลงไปจนกว่าจะพบตัวแปร "เป้าหมาย"
  6. สังเกตค่าปัจจุบันเป็น "local"
  7. ดับเบิลคลิกชื่อตัวแปร "target" แล้วเปลี่ยนค่าในป๊อปอัปเป็นค่าอื่น เช่น "Cloud"
  8. คลิกปุ่ม "ดำเนินการต่อ" ในแผงควบคุมการแก้ไขข้อบกพร่อง
  9. ตรวจสอบการตอบกลับในเบราว์เซอร์ ซึ่งตอนนี้จะแสดงค่าที่อัปเดตที่คุณเพิ่งป้อน

Hot Reload

  1. เปลี่ยนข้อความเพื่อแสดงค่าอื่น เช่น "สวัสดีจาก %s Code"
  2. ระบบจะบันทึกและซิงค์ไฟล์ไปยังคอนเทนเนอร์ระยะไกลใน GKE โดยอัตโนมัติ
  3. โปรดรีเฟรชเบราว์เซอร์เพื่อดูผลลัพธ์ที่อัปเดต
  4. หยุดเซสชันการแก้ไขข้อบกพร่องโดยคลิกสี่เหลี่ยมสีแดงในแถบเครื่องมือการแก้ไขข้อบกพร่อง a13d42d726213e6c.png

5. การพัฒนาบริการ REST แบบ CRUD อย่างง่าย

ตอนนี้แอปพลิเคชันของคุณได้รับการกำหนดค่าอย่างสมบูรณ์สำหรับการพัฒนาแบบคอนเทนเนอร์แล้ว และคุณได้ทำตามเวิร์กโฟลว์การพัฒนาพื้นฐานด้วย Cloud Code แล้ว ในส่วนต่อไปนี้ คุณจะได้ฝึกฝนสิ่งที่ได้เรียนรู้โดยการเพิ่มปลายทางของบริการ REST ที่เชื่อมต่อกับฐานข้อมูลที่มีการจัดการใน Google Cloud

กำหนดค่าการอ้างอิง

โค้ดของแอปพลิเคชันใช้ฐานข้อมูลเพื่อจัดเก็บข้อมูลบริการ REST ตรวจสอบว่าทรัพยากร Dependency พร้อมใช้งานโดยเพิ่มข้อมูลต่อไปนี้ใน 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>

เขียนโค้ดบริการ REST

Quote.java

สร้างไฟล์ชื่อ Quote.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

สร้างไฟล์ชื่อ 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) {
        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 ซึ่งจัดเก็บเป็น Secret ใน GCP

อัปเดต 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

ทำให้แอปพลิเคชันใช้งานได้และตรวจสอบ

  1. ในแผงที่ด้านล่างของ Cloud Shell Editor ให้เลือก Cloud Code แล้วเลือกแก้ไขข้อบกพร่องใน Kubernetes ที่ด้านบนของหน้าจอ
  2. เมื่อสร้างและทดสอบเสร็จแล้ว แท็บเอาต์พุตจะแสดงข้อความ Resource deployment/demo-app status completed successfully และแสดง URL "Forwarded URL from service demo-app: http://localhost:8080"
  3. ดูคำคมแบบสุ่ม

จากเทอร์มินัล Cloud Shell ให้เรียกใช้คำสั่งด้านล่างหลายครั้งกับปลายทางคำพูดแบบสุ่ม สังเกตการเรียกซ้ำที่แสดงราคาที่แตกต่างกัน

curl -v 127.0.0.1:8080/random-quote
  1. เพิ่มคำพูด

สร้างใบเสนอราคาใหม่โดยมีรหัส=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 ดังนั้นคุณจะต้องทำงานกับคลาส QuoteController

  1. เปิด src.main.java.com.example.springboot.QuoteController.java
  2. ค้นหาdeleteQuote()วิธี
  3. ค้นหาบรรทัดที่ลบรายการออกจากฐานข้อมูล: quoteRepository.deleteById(id);
  4. ตั้งค่าเบรกพอยต์ในบรรทัดนั้นโดยคลิกพื้นที่ว่างทางด้านซ้ายของหมายเลขบรรทัด
  5. ตัวบ่งชี้สีแดงจะปรากฏขึ้นเพื่อระบุว่าได้ตั้งค่าเบรกพอยต์แล้ว
  6. เรียกใช้คำสั่ง delete อีกครั้ง
curl -v -X DELETE 127.0.0.1:8080/quotes/6
  1. เปลี่ยนกลับไปใช้มุมมองการแก้ไขข้อบกพร่องโดยคลิกไอคอนในคอลัมน์ด้านซ้าย
  2. สังเกตว่าบรรทัดการแก้ไขข้อบกพร่องหยุดทำงานในคลาส QuoteController
  3. ในดีบักเกอร์ ให้คลิกไอคอน step over b814d39b2e5f3d9e.png แล้วสังเกตว่ามีการส่งข้อยกเว้น
  4. สังเกตว่า RuntimeException was caught. This returns an Internal Server Error HTTP 500 to the client which is not ideal.
   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. ขณะที่เซสชันการแก้ไขข้อบกพร่องยังทำงานอยู่ ให้ดำเนินการตามคำขอให้เสร็จสิ้นโดยกดปุ่ม "ดำเนินการต่อ" ในแผงควบคุมการแก้ไขข้อบกพร่อง
  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 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. หยุดเซสชันการแก้ไขข้อบกพร่องโดยคลิกสี่เหลี่ยมสีแดงในแถบเครื่องมือการแก้ไขข้อบกพร่อง a13d42d726213e6c.png

6. ล้างข้อมูล

ยินดีด้วย ในแล็บนี้ คุณได้สร้างแอปพลิเคชัน Java ใหม่ตั้งแต่ต้นและกำหนดค่าให้ทำงานกับคอนเทนเนอร์ได้อย่างมีประสิทธิภาพ จากนั้นคุณได้ทําการติดตั้งใช้งานและแก้ไขข้อบกพร่องของแอปพลิเคชันไปยังคลัสเตอร์ GKE ระยะไกลตามขั้นตอนการพัฒนาเดียวกันกับที่พบในสแต็กแอปพลิเคชันแบบเดิม

วิธีล้างข้อมูลหลังจากทำแล็บเสร็จแล้ว

  1. ลบไฟล์ที่ใช้ในห้องทดลอง
cd ~ && rm -rf container-developer-workshop
  1. ลบโปรเจ็กต์เพื่อนำโครงสร้างพื้นฐานและทรัพยากรที่เกี่ยวข้องทั้งหมดออก