Java を使用したインナーループ開発 - SpringBoot

1. 概要

このラボでは、コンテナ化された環境で Java アプリケーションの開発を担当するソフトウェア エンジニアの開発ワークフローを効率化するために設計された機能について説明します。一般的なコンテナ開発では、コンテナの詳細とコンテナ ビルドプロセスを理解する必要があります。また、デベロッパーは通常、フローを中断して IDE から移動し、リモート環境でアプリケーションをテストしてデバッグする必要があります。このチュートリアルで説明したツールとテクノロジーを使用すると、開発者は IDE を離れることなく、コンテナ化されたアプリケーションを効率的に操作できます。

学習内容

このラボでは、GCP でコンテナを使用して開発する方法について学びます。

  • 設定と要件
  • 新しい Java スターター アプリケーションの作成
  • 開発プロセスを理解する
  • シンプルな CRUD Rest サービスの開発
  • クリーンアップ

2. 設定と要件

セルフペース型の環境設定

  1. Google Cloud Console にログインして、プロジェクトを新規作成するか、既存のプロジェクトを再利用します。Gmail アカウントも Google Workspace アカウントもまだお持ちでない場合は、アカウントを作成してください。

b35bf95b8bf3d5d8.png

a99b7ace416376c4.png

bd84a6d3004737c5.png

  • プロジェクト名は、このプロジェクトの参加者に表示される名称です。Google API では使用されない文字列で、いつでも更新できます。
  • プロジェクト ID は、すべての Google Cloud プロジェクトにおいて一意でなければならず、不変です(設定後は変更できません)。Cloud Console により一意の文字列が自動生成されます(通常は内容を意識する必要はありません)。ほとんどの Codelab では、プロジェクト ID を参照する必要があります(通常、プロジェクト ID は「PROJECT_ID」の形式です)。好みの文字列でない場合は、別のランダムな ID を生成するか、独自の ID を試用して利用可能であるかどうかを確認することができます。プロジェクトの作成後、ID は「フリーズ」されます。
  • 3 つ目の値として、一部の API が使用するプロジェクト番号があります。これら 3 つの値について詳しくは、こちらのドキュメントをご覧ください。
  1. 次に、Cloud のリソースや API を使用するために、Cloud Console で課金を有効にする必要があります。この Codelab の操作をすべて行って、費用が生じたとしても、少額です。このチュートリアルを終了した後に課金が発生しないようにリソースをシャットダウンするには、Codelab の最後にある「クリーンアップ」の手順を行います。Google Cloud の新規ユーザーは、300 米ドル分の無料トライアル プログラムをご利用いただけます。

Cloudshell エディタを起動する

このラボは、Google Cloud Shell エディタで使用するように設計され、テストされています。エディタにアクセスするには、

  1. https://console.cloud.google.com で Google プロジェクトにアクセスします。
  2. 右上にある Cloud Shell エディタ アイコンをクリックします。

8560cc8d45e8c112.png

  1. ウィンドウの下部に新しいペインが開きます。
  2. [エディタを開く] ボタンをクリックします。

9e504cb98a6a8005.png

  1. エディタが開き、右側にエクスプローラ、中央にエディタが表示されます。
  2. 画面の下部にはターミナル ペインも表示されます
  3. ターミナルが開いていない場合は、`ctrl+`` のキーの組み合わせを使用して新しいターミナル ウィンドウを開きます。

gcloud を設定する

Cloud Shell で、プロジェクト ID と、アプリケーションのデプロイ先にするリージョンを設定します。これらの情報は、PROJECT_ID 変数と REGION 変数として保存します。

export PROJECT_ID=$(gcloud config get-value project)
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projectNumber)')

ソースコードを取得する

このラボのソースコードは、GitHub の GoogleCloudPlatform の container-developer-workshop にあります。次のコマンドでクローンを作成し、ディレクトリに移動します。

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

このラボで使用するインフラストラクチャをプロビジョニングする

このラボでは、GKE にコードをデプロイし、CloudSql データベースに保存されているデータにアクセスします。次の設定スクリプトは、このインフラストラクチャを準備します。プロビジョニング プロセスには 10 分以上かかります。設定の処理中に、次の手順に進むことができます。

./setup.sh

3. 新しい Java スターター アプリケーションの作成

このセクションでは、spring.io が提供するサンプル アプリケーションを利用して、新しい Java Spring Boot アプリケーションをゼロから作成します。

サンプル アプリケーションのクローンを作成する

  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 を見つけて開きます。次に、<description>Demo project for Spring Boot</description> という説明行の後に次のコードを貼り付けます。

  1. pom.xml に spring-boot-devtools を追加する

プロジェクトのルートにある 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. pom.xml で jib-maven-plugin を有効にする

Jib は、Google のオープンソースの Java コンテナ化ツールです。Java デベロッパーは、使い慣れた Java ツールを使用してコンテナをビルドできます。Jib は、アプリケーションをコンテナ イメージにパッケージ化するすべてのステップを処理する、高速でシンプルなコンテナ イメージ ビルダーです。Dockerfile の作成や Docker のインストールは不要で、Maven と Gradle に直接統合されています。

pom.xml ファイルを下にスクロールし、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>

ビルドファイルの変更について確認されたら、Always を選択します。

447a90338f51931f.png

マニフェストを生成する

Skaffold は、コンテナ開発を簡素化する統合ツールを提供します。このステップでは、skaffold を初期化します。これにより、ベースの Kubernetes YAML ファイルが自動的に作成されます。このプロセスは、Dockerfile などのコンテナ イメージ定義を含むディレクトリを特定し、それぞれにデプロイとサービスのマニフェストを作成しようとします。

次のコマンドを実行して、プロセスを開始します。

  1. ターミナルで次のコマンドを実行します。
skaffold init --generate-manifests
  1. プロンプトが表示されたら、次の操作を行います。
  • 矢印キーを使用して、カーソルを Jib Maven Plugin に移動します。
  • Space キーを押してオプションを選択します。
  • 続行するには Enter キーを押してください
  1. ポートに 8080 と入力します。
  2. y」と入力して構成を保存します

ワークスペースに 2 つのファイル(skaffold.yamldeployment.yaml)が追加されます。

アプリ名を更新する

構成に含まれるデフォルト値が、現在のところアプリケーションの名前と一致していません。デフォルト値ではなくアプリケーション名を参照するようにファイルを更新します。

  1. Skaffold 構成のエントリを変更する
  • skaffold.yaml を開く
  • 現在 pom-xml-image として設定されているイメージ名を選択します。
  • 右クリックして [すべての出現箇所を変更] を選択します。
  • 新しい名前を demo-app として入力します。
  1. Kubernetes 構成のエントリを変更する
  • deployment.yaml ファイルを開く
  • 現在 pom-xml-image として設定されているイメージ名を選択します。
  • 右クリックして [すべての出現箇所を変更] を選択します。
  • 新しい名前を demo-app として入力します。

ホット同期を有効にする

最適化されたホットリロード エクスペリエンスを実現するには、Jib が提供する同期機能を使用します。このステップでは、ビルドプロセスでその機能を利用するように Skaffold を構成します。

skaffold 構成で構成する「sync」プロファイルは、前の手順で構成した Spring の「sync」プロファイルを利用します。この手順では、spring-dev-tools のサポートを有効にしています。

  1. skaffold 構成を更新する

skaffold.yaml ファイルで、ファイルの build セクション全体を次の仕様に置き換えます。ファイルの他のセクションは変更しないでください。

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

デフォルト ルートを追加する

/src/main/java/com/example/springboot/ に HelloController.java というファイルを作成します。

次の内容をファイルに貼り付けて、デフォルトの http ルートを作成します。

HelloController.java

package com.example.springboot;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Value;

@RestController
public class HelloController {

    @Value("${target:local}")
    String target;

    @GetMapping("/") 
    public String hello()
    {
        return String.format("Hello from your %s environment!", target);
    }
}

4. 開発プロセスを理解する

このセクションでは、Cloud Code プラグインを使用していくつかの手順を行い、基本的なプロセスを学習し、スターター アプリケーションの構成と設定を検証します。

Cloud Code は skaffold と統合して、開発プロセスを効率化します。次の手順で GKE にデプロイすると、Cloud Code と Skaffold がコンテナ イメージを自動的にビルドして Container Registry に push し、アプリケーションを GKE にデプロイします。これはバックグラウンドで実行され、デベロッパー フローから詳細が抽象化されます。Cloud Code は、コンテナベースの開発に従来のデバッグ機能とホットシンク機能を提供することで、開発プロセスを強化します。

Kubernetes へのデプロイ

  1. Cloud Shell エディタの下部にあるペインで、Cloud Code  を選択します。

fdc797a769040839.png

  1. 上部に表示されるパネルで、[Kubernetes 上でデバッグする] を選択します。プロンプトが表示されたら、[Yes] を選択して現在の Kubernetes コンテキストを使用します。

cfce0d11ef307087.png

  1. コマンドを初めて実行すると、画面上部に現在の Kubernetes コンテキストを使用するかどうかを尋ねるプロンプトが表示されます。[Yes] を選択して、現在のコンテキストを受け入れて使用します。

817ee33b5b412ff8.png

  1. 次に、使用するコンテナ レジストリを尋ねるプロンプトが表示されます。Enter キーを押して、指定されたデフォルト値を受け入れます。

eb4469aed97a25f6.png

  1. 下部ペインの [出力] タブを選択して、進行状況と通知を表示する

f95b620569ba96c5.png

  1. 右側のチャンネル プルダウンで [Kubernetes: Run/Debug - Detailed] を選択すると、追加の詳細とコンテナからライブ ストリーミングされるログが表示されます。

94acdcdda6d2108.png

  1. プルダウンから [Kubernetes: Run/Debug] を選択して、簡略ビューに戻ります。
  2. ビルドとテストが完了すると、[出力] タブに Resource deployment/demo-app status completed successfully と URL「Forwarded URL from service demo-app: http://localhost:8080」が表示されます。
  3. Cloud Code ターミナルで、出力(http://localhost:8080)の URL にカーソルを合わせ、表示されたツールチップで [Open Web Preview] を選択します。

レスポンスは次のようになります。

Hello from your local environment!

ブレークポイントを活用する

  1. /src/main/java/com/example/springboot/HelloController.java にある HelloController.java アプリケーションを開きます。
  2. return String.format("Hello from your %s environment!", target); と書かれたルートパスの return 文を見つけます。
  3. 行番号の左側にある余白をクリックして、その行にブレークポイントを追加します。ブレークポイントが設定されたことを示す赤いインジケーターが表示されます。
  4. ブラウザを再読み込みします。デバッガがブレークポイントでプロセスを停止し、GKE でリモートで実行されているアプリケーションの変数と状態を調査できることを確認します。
  5. [Target] 変数が見つかるまで、[変数] セクションをクリックして下に移動します。
  6. 現在の値が「local」であることを確認します。
  7. 変数名「target」をダブルクリックし、ポップアップで値を「Cloud」などの別の値に変更します。
  8. デバッグ コントロール パネルの [続行] ボタンをクリックします。
  9. ブラウザでレスポンスを確認します。入力した更新後の値が表示されます。

ホットリロード

  1. ステートメントを変更して、「Hello from %s Code」などの別の値を返すようにします。
  2. ファイルは自動的に保存され、GKE のリモート コンテナに同期されます。
  3. ブラウザを更新して、更新された結果を表示します。
  4. デバッグ ツールバーの赤い四角形 a13d42d726213e6c.png をクリックして、デバッグ セッションを停止します。

5. シンプルな CRUD Rest サービスの開発

これで、コンテナ化された開発用にアプリケーションが完全に構成され、Cloud Code を使用した基本的な開発ワークフローを完了しました。以降のセクションでは、Google Cloud のマネージド データベースに接続する REST サービス エンドポイントを追加して、学習した内容を実践します。

依存関係を構成する

アプリケーション コードは、データベースを使用して REST サービスデータを永続化します。pom.xl に以下を追加して、依存関係が使用可能であることを確認します。

  1. pom.xml ファイルを開き、構成の dependencies セクションに以下を追加します。

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

/src/main/java/com/example/springboot/ に Quote.java というファイルを作成し、次のコードをコピーします。これは、アプリケーションで使用される Quote オブジェクトのエンティティ モデルを定義します。

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

src/main/java/com/example/springboot に QuoteRepository.java というファイルを作成し、次のコードをコピーします。

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 クラスがこの機能を提供します。

src/main/java/com/example/springboot に QuoteController.java というファイルを作成し、次の内容をコピーします。

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

サービスがアクセスするバックエンド データベースの構成を追加します。src/main/resources の下の application.yaml という名前のファイルを編集(存在しない場合は作成)し、バックエンドのパラメータ化された 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 Config

deployment.yaml ファイルに次の追加を行うと、アプリケーションが CloudSQL インスタンスに接続できるようになります。

  • TARGET - アプリが実行される環境を示すように変数を構成します
  • SPRING_PROFILES_ACTIVE - アクティブな Spring プロファイルを表示します。これは cloud-dev に構成されます。
  • DB_HOST - データベースのプライベート IP。データベース インスタンスの作成時にメモしたか、Google Cloud コンソールのナビゲーション メニューで SQL をクリックして確認します。値を変更してください。
  • DB_USER と DB_PASS - CloudSQL インスタンスの構成で設定され、GCP の Secret として保存されます。

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 エディタの下部にあるペインで、[Cloud Code] を選択し、画面上部の [Debug on 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. 見積もりを追加する

次のコマンドを使用して、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 オペレーションで発生したため、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. が返され、クライアントに Internal Server Error 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 Not Found ステータス コードを返す必要があります。

エラーを修正します。

  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. プロジェクトを削除して、関連するインフラストラクチャとリソースをすべて削除する