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

1. 概要

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

学習内容

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

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

2. 設定と要件

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

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

b35bf95b8bf3d5d8.pnga99b7ace416376c4.pngbd84a6d3004737c5.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 米ドル分の無料トライアル プログラムをご利用いただけます。

Cloud Shell エディタを起動する

このラボは、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 にコードをデプロイし、Cloud SQL データベースに保存されているデータにアクセスします。次の設定スクリプトを実行して、このインフラストラクチャを準備します。プロビジョニング プロセスには 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 は、Java デベロッパーが使い慣れた Java ツールを使用してコンテナをビルドできる、Google のオープンソース 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 には、コンテナ開発を簡素化する統合ツールが用意されています。 このステップでは、基本の Kubernetes YAML ファイルを自動的に作成する skaffold を初期化します。このプロセスでは、Dockerfile などのコンテナイメージ定義を含むディレクトリを特定し、それぞれにデプロイとサービスのマニフェストを作成します。

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

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

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

アプリ名を更新する

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

  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 ファイルで、ファイルのビルド セクション全体を次の仕様に置き換えます。ファイルの他のセクションは変更しないでください。

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 上でデバッグする] を選択します。プロンプトが表示されたら、[はい] を選択して現在の 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)にカーソルを合わせ、表示されたツールチップで [Open Web Preview] を選択します。

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

Hello from your local environment!

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

  1. /src/main/java/com/example/springboot/HelloController.java にある HelloController.java アプリケーションを開きます。
  2. ルートパスの return 文を見つけます。return String.format("Hello from your %s environment!", target);
  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 オブジェクトの Entity モデルが定義されます。

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 構成

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

  • TARGET - アプリが実行される環境を示すように変数を構成します。
  • SPRING_PROFILES_ACTIVE - アクティブな Spring プロファイルを表示します。これは cloud-dev に構成されます。
  • DB_HOST - データベースのプライベート IP。データベース インスタンスの作成時、または Google Cloud コンソールのナビゲーション メニューで SQL をクリックしたときに記録されます。値を変更してください。
  • DB_USER と DB_PASS - Cloud SQL インスタンス構成で設定され、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] を選択し、画面の上部で [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. 引用を削除する

delete メソッドで追加した引用を削除し、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. delete コマンドを再実行します。
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. プロジェクトを削除して、関連するインフラストラクチャとリソースをすべて削除する