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

1. ภาพรวม

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

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

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

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

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

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

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

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.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

Lab นี้ออกแบบและทดสอบเพื่อใช้กับ 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

จัดสรรโครงสร้างพื้นฐานที่ใช้ใน Lab นี้

ในแล็บนี้ คุณจะได้ติดตั้งใช้งานโค้ดใน GKE และเข้าถึงข้อมูลที่จัดเก็บไว้ในฐานข้อมูล Cloud SQL สคริปต์การตั้งค่าด้านล่างจะเตรียมโครงสร้างพื้นฐานนี้ให้คุณ กระบวนการจัดสรรจะใช้เวลามากกว่า 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. เปลี่ยนเป็นไดเรกทอรี 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 ให้เรียกใช้คำสั่งด้านล่างหลายครั้งกับปลายทาง random-quote สังเกตการเรียกซ้ำที่แสดงราคาที่แตกต่างกัน

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. นี้จะแสดงข้อผิดพลาดภายในเซิร์ฟเวอร์ 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. ขณะที่เซสชันการแก้ไขข้อบกพร่องยังทำงานอยู่ ให้ดำเนินการตามคำขอให้เสร็จสมบูรณ์โดยกดปุ่ม "ดำเนินการต่อ" ในแผงควบคุมการแก้ไขข้อบกพร่อง
  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. ลบโปรเจ็กต์เพื่อนำโครงสร้างพื้นฐานและทรัพยากรที่เกี่ยวข้องทั้งหมดออก