۱. مرور کلی
این آزمایشگاه ویژگیها و قابلیتهایی را نشان میدهد که برای سادهسازی گردش کار توسعه برای مهندسان نرمافزاری که وظیفه توسعه برنامههای جاوا در یک محیط کانتینری را بر عهده دارند، طراحی شدهاند. توسعه کانتینر معمولاً مستلزم آن است که کاربر جزئیات کانتینرها و فرآیند ساخت کانتینر را درک کند. علاوه بر این، توسعهدهندگان معمولاً باید جریان خود را بشکنند و از IDE خود خارج شوند تا برنامههای خود را در محیطهای از راه دور آزمایش و اشکالزدایی کنند. با ابزارها و فناوریهای ذکر شده در این آموزش، توسعهدهندگان میتوانند بدون ترک IDE خود، به طور مؤثر با برنامههای کانتینری کار کنند.
آنچه یاد خواهید گرفت
در این آزمایشگاه روشهای توسعه با کانتینرها در GCP را خواهید آموخت، از جمله:
- توسعه InnerLoop با ایستگاههای کاری ابری
- ایجاد یک برنامه شروع کننده جدید جاوا
- قدم زدن در فرآیند توسعه
- توسعه یک سرویس استراحت CRUD ساده
- اشکالزدایی برنامه در کلاستر GKE
- اتصال برنامه به پایگاه داده CloudSQL

۲. تنظیمات و الزامات
تنظیم محیط خودتنظیم
- وارد کنسول گوگل کلود شوید و یک پروژه جدید ایجاد کنید یا از یک پروژه موجود دوباره استفاده کنید. اگر از قبل حساب جیمیل یا گوگل ورک اسپیس ندارید، باید یکی ایجاد کنید .



- نام پروژه ، نام نمایشی برای شرکتکنندگان این پروژه است. این یک رشته کاراکتری است که توسط APIهای گوگل استفاده نمیشود. میتوانید آن را در هر زمانی بهروزرسانی کنید.
- شناسه پروژه در تمام پروژههای گوگل کلود منحصر به فرد است و تغییرناپذیر است (پس از تنظیم، قابل تغییر نیست). کنسول کلود به طور خودکار یک رشته منحصر به فرد تولید میکند؛ معمولاً برای شما مهم نیست که چیست. در اکثر آزمایشگاههای کد، باید به شناسه پروژه ارجاع دهید (که معمولاً با عنوان
PROJECT_IDشناخته میشود). اگر شناسه تولید شده را دوست ندارید، میتوانید یک شناسه تصادفی دیگر ایجاد کنید. به عنوان یک جایگزین، میتوانید شناسه خودتان را امتحان کنید و ببینید که آیا در دسترس است یا خیر. پس از این مرحله قابل تغییر نیست و در طول پروژه باقی خواهد ماند. - برای اطلاع شما، یک مقدار سوم، شماره پروژه ، وجود دارد که برخی از APIها از آن استفاده میکنند. برای کسب اطلاعات بیشتر در مورد هر سه این مقادیر، به مستندات مراجعه کنید.
- در مرحله بعد، برای استفاده از منابع/API های ابری، باید پرداخت صورتحساب را در کنسول ابری فعال کنید . اجرای این آزمایشگاه کد، اگر اصلاً هزینهای نداشته باشد، هزینه زیادی نخواهد داشت. برای خاموش کردن منابع به طوری که پس از این آموزش متحمل پرداخت صورتحساب نشوید، میتوانید منابعی را که ایجاد کردهاید یا کل پروژه را حذف کنید. کاربران جدید Google Cloud واجد شرایط برنامه آزمایشی رایگان ۳۰۰ دلاری هستند.
ویرایشگر Cloudshell را شروع کنید
این آزمایشگاه برای استفاده با ویرایشگر پوسته ابری گوگل (Google Cloud Shell Editor) طراحی و آزمایش شده است. برای دسترسی به ویرایشگر،
- برای دسترسی به پروژه گوگل خود به آدرس https://console.cloud.google.com مراجعه کنید.
- در گوشه بالا سمت راست، روی آیکون ویرایشگر پوسته ابری کلیک کنید.

- یک پنل جدید در پایین پنجره شما باز خواهد شد
- روی دکمه باز کردن ویرایشگر کلیک کنید

- ویرایشگر با یک کاوشگر در سمت راست و ویرایشگر در ناحیه مرکزی باز خواهد شد.
- یک پنجره ترمینال نیز باید در پایین صفحه نمایش موجود باشد
- اگر ترمینال باز نیست، از ترکیب کلیدهای `ctrl+`` برای باز کردن یک پنجره ترمینال جدید استفاده کنید.
جی کلود را تنظیم کنید
در 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 دسترسی خواهید داشت. اسکریپت راهاندازی زیر این زیرساخت را برای شما آماده میکند. فرآیند آمادهسازی بیش از ۲۵ دقیقه طول خواهد کشید. قبل از رفتن به بخش بعدی، منتظر بمانید تا اسکریپت تکمیل شود.
./setup_with_cw.sh &
خوشه ایستگاههای کاری ابری
ایستگاههای کاری ابری را در کنسول ابری باز کنید. منتظر بمانید تا کلاستر در وضعیت READY قرار گیرد.
ایجاد پیکربندی ایستگاههای کاری
اگر اتصال Cloud Shell شما قطع شده است، روی "Reconnect" کلیک کنید و سپس دستور gcloud cli را برای تنظیم شناسه پروژه اجرا کنید. قبل از اجرای دستور، شناسه نمونه پروژه زیر را با شناسه پروژه qwiklabs خود جایگزین کنید.
gcloud config set project qwiklabs-gcp-project-id
اسکریپت زیر را در ترمینال اجرا کنید تا پیکربندی Cloud Workstations ایجاد شود.
cd ~/container-developer-workshop/labs/spring-boot
./workstation_config_setup.sh
نتایج را در بخش تنظیمات تأیید کنید. 2 دقیقه طول میکشد تا به وضعیت آماده (READY) برسید.

Cloud Workstations را در کنسول باز کنید و یک نمونه جدید ایجاد کنید.

نام را به my-workstation تغییر دهید و پیکربندی موجود را انتخاب کنید: codeoss-java .

نتایج را در بخش Workstations تأیید کنید.

ایستگاه کاری را راه اندازی کنید
ایستگاه کاری را شروع و راهاندازی کنید.

با کلیک روی نماد در نوار آدرس، کوکیهای شخص ثالث را مجاز کنید. 

روی «سایت کار نمیکند؟» کلیک کنید.

روی «اجازه دادن به کوکیها» کلیک کنید.

پس از راهاندازی ایستگاه کاری، خواهید دید که Code OSS IDE ظاهر میشود. در صفحه شروع به کار، روی «Mark Done» در صفحه اول IDE ایستگاه کاری کلیک کنید.

۳. ایجاد یک برنامهی آغازین جاوای جدید
در این بخش، شما یک برنامهی Java Spring Boot جدید را از ابتدا با استفاده از یک برنامهی نمونه که توسط spring.io ارائه شده است، ایجاد خواهید کرد. یک ترمینال جدید باز کنید.

برنامه نمونه را کلون کنید
- یک برنامهی آغازین ایجاد کنید
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، فایل 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 یک ابزار کانتینرسازی جاوای متنباز از گوگل است که به توسعهدهندگان جاوا اجازه میدهد کانتینرها را با استفاده از ابزارهای جاوایی که میشناسند، بسازند. Jib یک سازندهی سریع و سادهی تصویر کانتینر است که تمام مراحل بستهبندی برنامهی شما در یک تصویر کانتینر را مدیریت میکند. این ابزار نیازی به نوشتن Dockerfile یا نصب Docker ندارد و مستقیماً با Maven و Gradle ادغام میشود.
در فایل pom.xml به پایین اسکرول کنید و بخش Build را بهروزرسانی کنید تا افزونه Jib را نیز شامل شود. بخش build پس از تکمیل باید مطابق با موارد زیر باشد.
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استفاده کنید. - برای انتخاب گزینه مورد نظر، کلید فاصله (spacebar) را فشار دهید.
- برای ادامه، اینتر را فشار دهید
- برای پورت، عدد ۸۰۸۰ را وارد کنید.
- برای ذخیره تنظیمات، y را وارد کنید.
دو فایل به فضای کاری skaffold.yaml و deployment.yaml اضافه میشوند.
خروجی اسکافلد:

نام برنامه را بهروزرسانی کنید
مقادیر پیشفرض موجود در پیکربندی در حال حاضر با نام برنامه شما مطابقت ندارند. فایلها را بهروزرسانی کنید تا به جای مقادیر پیشفرض، به نام برنامه شما ارجاع دهند.
- تغییر ورودیها در پیکربندی Skaffold
-
skaffold.yamlرا باز کنید. - نام تصویری که در حال حاضر به عنوان
pom-xml-imageتنظیم شده است را انتخاب کنید. - کلیک راست کرده و گزینه Change All Occurrences را انتخاب کنید.
- نام جدید را به صورت
demo-appتایپ کنید.
- تغییر ورودیها در پیکربندی Kubernetes
- فایل
deployment.yamlرا باز کنید - نام تصویری که در حال حاضر به عنوان
pom-xml-imageتنظیم شده است را انتخاب کنید. - کلیک راست کرده و گزینه Change All Occurrences را انتخاب کنید.
- نام جدید را به صورت
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);
}
}
۴. گام برداشتن در مسیر توسعه
در این بخش، چند مرحله را با استفاده از افزونه Cloud Code طی خواهید کرد تا فرآیندهای اولیه را بیاموزید و پیکربندی و راهاندازی برنامه اولیه خود را تأیید کنید.
Cloud Code با Skaffold ادغام میشود تا فرآیند توسعه شما را سادهتر کند. وقتی در مراحل بعدی در GKE مستقر میشوید، Cloud Code و Skaffold به طور خودکار تصویر کانتینر شما را میسازند، آن را به یک رجیستری کانتینر منتقل میکنند و سپس برنامه شما را در GKE مستقر میکنند. این اتفاق در پشت صحنه رخ میدهد و جزئیات را از جریان توسعهدهنده جدا میکند. Cloud Code همچنین با ارائه قابلیتهای اشکالزدایی سنتی و همگامسازی سریع به توسعه مبتنی بر کانتینر، فرآیند توسعه شما را بهبود میبخشد.
وارد فضای ابری گوگل شوید
روی آیکون Cloud Code کلیک کنید و گزینه «ورود به سیستم Google Cloud» را انتخاب کنید:

روی «ادامه برای ورود» کلیک کنید.

خروجی را در ترمینال بررسی کنید و لینک را باز کنید:

با اعتبارنامههای دانشجویی Qwiklabs خود وارد شوید.

گزینه "مجاز" را انتخاب کنید:

کد تأیید را کپی کنید و به برگه Workstation برگردید.

کد تأیید را جایگذاری کنید و Enter را بزنید.

اضافه کردن کلاستر Kubernetes
- اضافه کردن یک خوشه

- موتور گوگل کوبرنتیز را انتخاب کنید:

- انتخاب پروژه.

- "quote-cluster" که در تنظیمات اولیه ایجاد شده بود را انتخاب کنید.


تنظیم شناسه پروژه فعلی با استفاده از gcloud cli
شناسه پروژه این آزمایشگاه را از صفحه qwiklabs کپی کنید.

دستور gcloud cli را برای تنظیم شناسه پروژه اجرا کنید. قبل از اجرای دستور، شناسه نمونه پروژه را جایگزین کنید.
gcloud config set project qwiklabs-gcp-project-id
خروجی نمونه:

اشکال زدایی در Kubernetes
- در پنل سمت چپ در پایین، Cloud Code را انتخاب کنید.

- در پنلی که در بخش DEVELOPMENT SESSIONS ظاهر میشود، گزینه Debug on Kubernetes را انتخاب کنید.
اگر گزینه قابل مشاهده نیست، به پایین اسکرول کنید.

- برای استفاده از متن فعلی، «بله» را انتخاب کنید.

- گزینه "quote-cluster" که در هنگام راهاندازی اولیه ایجاد شده بود را انتخاب کنید.

- مخزن کانتینر را انتخاب کنید.

- برای مشاهده پیشرفت و اعلانها، برگه خروجی را در پنل پایین انتخاب کنید.
- برای مشاهده جزئیات بیشتر و گزارشهای زنده از کانتینرها، در منوی کشویی سمت راست، گزینه "Kubernetes: Run/Debug - Detailed" را انتخاب کنید.

منتظر بمانید تا برنامه مستقر شود.

- برنامهی مستقر شده روی GKE را در Cloud Console بررسی کنید.

- با انتخاب "Kubernetes: Run/Debug" از منوی کشویی تب OUTPUT، به نمای سادهشده برگردید.
- وقتی ساخت و آزمایشها انجام شد، در برگه خروجی نوشته میشود:
Resource deployment/demo-app status completed successfullyو یک آدرس اینترنتی فهرست شده است: "URL ارسال شده از سرویس نسخه آزمایشی: 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" را پیدا کنید.
- مقدار فعلی را به عنوان "محلی" مشاهده کنید

- روی نام متغیر "target" دوبار کلیک کنید و در پنجره باز شده،
مقدار را به "ایستگاههای کاری ابری" تغییر دهید

- روی دکمه ادامه در پنل کنترل اشکالزدایی کلیک کنید

- پاسخ را در مرورگر خود بررسی کنید که اکنون مقدار بهروزرسانیشدهای را که وارد کردهاید نشان میدهد.

- با کلیک روی نشانگر قرمز رنگ در سمت چپ شماره خط، نقطه توقف را حذف کنید. این کار مانع از توقف اجرای کد شما در این خط در ادامه این تمرین میشود.
بارگیری مجدد داغ
- عبارت را طوری تغییر دهید که مقدار متفاوتی مانند "سلام از %s کد" را برگرداند.
- فایل به طور خودکار ذخیره و در کانتینرهای راه دور در GKE همگامسازی میشود.
- برای مشاهده نتایج بهروزرسانیشده، مرورگر خود را بهروزرسانی کنید.
- با کلیک روی مربع قرمز در نوار ابزار اشکالزدایی، جلسه اشکالزدایی را متوقف کنید.


گزینه «بله، بعد از هر اجرا تمیز میشود» را انتخاب کنید.

۵. توسعه یک سرویس ساده CRUD Rest
در این مرحله، برنامه شما به طور کامل برای توسعه کانتینری پیکربندی شده است و شما گردش کار اولیه توسعه را با Cloud Code طی کردهاید. در بخشهای بعدی، آموختههای خود را با اضافه کردن نقاط پایانی سرویس REST که به یک پایگاه داده مدیریتشده در Google Cloud متصل میشوند، تمرین خواهید کرد.
پیکربندی وابستگیها
کد برنامه از یک پایگاه داده برای ذخیره دادههای سرویس باقیمانده استفاده میکند. با اضافه کردن موارد زیر در 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
نقل قول.java
فایلی به نام Quote.java در /src/main/java/com/example/springboot/ ایجاد کنید و کد زیر را در آن کپی کنید. این فایل، مدل Entity را برای شیء Quote مورد استفاده در برنامه تعریف میکند.
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 برای ذخیره دادهها استفاده میکند. این کلاس از رابط JPARepository در Spring ارثبری میکند و امکان ایجاد کد سفارشی را فراهم میکند. در این کد، شما یک متد سفارشی 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);
}
}
}
اضافه کردن تنظیمات پایگاه داده
برنامه.yaml
پیکربندی مربوط به پایگاه داده backend که توسط سرویس قابل دسترسی است را اضافه کنید. فایلی به نام application.yaml را در مسیر src/main/resources ویرایش کنید (یا در صورت وجود نداشتن، آن را ایجاد کنید) و یک پیکربندی Spring پارامتری برای backend اضافه کنید.
target: local
spring:
config:
activate:
on-profile: cloud-dev
datasource:
url: 'jdbc:postgresql://${DB_HOST:127.0.0.1}/${DB_NAME:quote_db}'
username: '${DB_USER:user}'
password: '${DB_PASS:password}'
jpa:
properties:
hibernate:
jdbc:
lob:
non_contextual_creation: true
dialect: org.hibernate.dialect.PostgreSQLDialect
hibernate:
ddl-auto: update
اضافه کردن انتقال پایگاه داده
پوشههای db/migration در مسیر src/main/resources ایجاد کنید.
یک فایل SQL ایجاد کنید: V1__create_quotes_table.sql
محتویات زیر را در فایل قرار دهید
V1__create_quotes_table.sql
CREATE TABLE quotes(
id INTEGER PRIMARY KEY,
quote VARCHAR(1024),
author VARCHAR(256)
);
INSERT INTO quotes (id,quote,author) VALUES (1,'Never, never, never give up','Winston Churchill');
INSERT INTO quotes (id,quote,author) VALUES (2,'While there''s life, there''s hope','Marcus Tullius Cicero');
INSERT INTO quotes (id,quote,author) VALUES (3,'Failure is success in progress','Anonymous');
INSERT INTO quotes (id,quote,author) VALUES (4,'Success demands singleness of purpose','Vincent Lombardi');
INSERT INTO quotes (id,quote,author) VALUES (5,'The shortest answer is doing','Lord Herbert');
پیکربندی Kubernetes
موارد اضافه شده به فایل deployment.yaml به برنامه اجازه میدهد تا به نمونههای CloudSQL متصل شود.
- TARGET - متغیر را طوری پیکربندی میکند که محیطی را که برنامه در آن اجرا میشود، نشان دهد.
- SPRING_PROFILES_ACTIVE - پروفایل فعال Spring را نشان میدهد که برای
cloud-devپیکربندی خواهد شد. - DB_HOST - IP خصوصی برای پایگاه داده، که هنگام ایجاد نمونه پایگاه داده یا با کلیک روی
SQLدر منوی ناوبری کنسول Google Cloud مشخص شده است - لطفاً مقدار را تغییر دهید! - DB_USER و DB_PASS - همانطور که در پیکربندی نمونه CloudSQL تنظیم شده است، به عنوان یک راز در GCP ذخیره میشود.
فایل deployment.yaml خود را با محتویات زیر بهروزرسانی کنید.
استقرار.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 با Instance IP بهروزرسانی شده باشد.

استقرار و اعتبارسنجی برنامه
- در پنل پایین ویرایشگر Cloud Shell، گزینه Cloud Code و سپس Debug on Kubernetes را در بالای صفحه انتخاب کنید.

- وقتی ساخت و آزمایشها انجام شد، در برگه خروجی عبارت زیر نمایش داده میشود:
Resource deployment/demo-app status completed successfullyو یک آدرس اینترنتی نیز فهرست شده است: "URL ارسال شده از سرویس نسخه آزمایشی: http://localhost:8080 ". توجه داشته باشید که گاهی اوقات ممکن است پورت متفاوت باشد، مثلاً 8081. در این صورت، مقدار مناسب را تنظیم کنید. مقدار URL را در ترمینال تنظیم کنید.
export URL=localhost:8080
- مشاهده نقل قول های تصادفی
از ترمینال، دستور زیر را چندین بار در مقابل نقطه پایانی نقل قول تصادفی اجرا کنید. مشاهده کنید که فراخوانی مکرر، نقل قولهای متفاوتی را برمیگرداند.
curl $URL/random-quote | jq
- اضافه کردن نقل قول
با استفاده از دستور زیر، یک نقل قول جدید با شناسه ۶ ایجاد کنید و مشاهده کنید که درخواست چگونه بازتاب میشود.
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
- حذف نقل قول
حالا نقل قولی که با متد delete اضافه کردید را حذف کنید و کد پاسخ 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 not found را برگرداند.
خطا را اصلاح کنید.
- در حالی که جلسه اشکالزدایی هنوز در حال اجرا است، با فشردن دکمه «ادامه» در پنل کنترل اشکالزدایی، درخواست را تکمیل کنید.
- سپس بلوک
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
- با کلیک روی مربع قرمز در نوار ابزار اشکالزدایی، جلسه اشکالزدایی را متوقف کنید.


۶. تبریک
تبریک! در این آزمایش شما یک برنامه جاوا جدید را از ابتدا ایجاد و آن را برای کار مؤثر با کانتینرها پیکربندی کردهاید. سپس برنامه خود را طبق همان جریان توسعهدهندهای که در پشتههای برنامه سنتی یافت میشود، در یک خوشه GKE از راه دور مستقر و اشکالزدایی کردهاید.
آنچه آموختهاید
- توسعه InnerLoop با ایستگاههای کاری ابری
- ایجاد یک برنامه شروع کننده جدید جاوا
- قدم زدن در فرآیند توسعه
- توسعه یک سرویس ساده CRUD REST
- اشکالزدایی برنامه در کلاستر GKE
- اتصال برنامه به پایگاه داده CloudSQL
