Spring Cloud GCP を使ったアプリケーション開発入門

Yanagihara Shinya
google-cloud-jp
Published in
30 min readDec 21, 2020

この記事は Google Cloud Japan Customer Engineer Advent Calendar 2020 の 21日目の記事です。

みなさん、こんにちは、Google Cloud の柳原 (@yanashin18618)です。
2020 年の今年は Java が公開されてから 25 周年というアニバーサリー・イヤーで盛り上がりを見せた年でした。Java に馴染みのない人も 今年は Java というキーワードを目にしたりしたのではないでしょうか。
私はと言えば、日夜 Google Cloud 上で Java アプリケーションをどう組み上げていくかと想いを馳せた1年でした。

TL;DR

これから Google Cloud 上で Java アプリケーションを作っていこうとしている方のために参考になるような話を綴っていきます。
特に開発フレームワークの Spring が提供している Google Cloud 向けの機能になる Spring Cloud GCP について紹介します。

Java 事始め

Java 開発の経験がある方ならすぐに思いつく事なのですが、Java 開発にあると便利な三種の神器といえば何でしょうか?

もしかすると”書籍”や”支援ツール”なども思い浮かべた方もいるかも知れないですが、下記の3つが重要かなと思います。

  • JDK
  • 開発エディタ
  • フレームワーク

JDK

JDK は あると便利ではなく、必須アイテムですね。コンパイルなど開発する時に必要なコンポーネントが一式入っているのが JDK です。JDK の一部で Java アプリケーションを実行するために必要になるのが JRE ですね。
さて、この JDK ですが複数の選択肢があるのはご存知でしょうか?

  • Oracle JDK
  • IBM J9
  • Red Hat OpenJDK
  • AdoptOpenJDK
  • Azul Zulu
  • BellSoft Liberica JDK

他にも SAP や Amazon などからも提供されていたりしますね。
それぞれの JDK の違いや、何を選択基準とするかは別の機会にお話したいと思います。基本的には 提供している API には差がないので、いずれかの JDK をインストールしておきましょう。
ちなみに、Maven や Gradle の Spring Boot プラグイン を使ってコンテナビルドをする場合は、 BellSoft Liberica JDK がデフォルトで選択されます。

$ mvn spring-boot:build-image$ gradle bootBuildImage

もう1つの選ぶ際のポイントは、JDK のバージョンです。バージョン毎に新しい機能が追加されているので最新版にしたり、長期サポートバージョンの Java 8 や Java 11 を選ぶという考えもあると思います。

  • 最新バージョン: Java 15 (2020/12 時点)
  • LTS バージョン: Java 8 / Java 11

開発エディター

開発エディターは文字通りソースコードを書き上げるためのツールです。vi や emacs を使って ターミナル上でソースコードを書く強者もいると思いますが、多くの人は統合開発環境 (IDE) を使って開発をするのではないでしょうか。人それぞれ開発エディターに求める内容は違うかもしれません。例えば、

  • コード補完
  • リファクタリング
  • テスト支援
  • プラグインの充実さ
  • ショートカットによる利便性
  • 軽量さ

他にも、チーム開発支援機能や外部環境との統合機能などといったように様々な観点があると思います。これらの全ては開発に容易さを求めるために開発エディターを選んでいると言えます。

そのような代表的な Java の開発エディターは次のようなものがあります。

Eclipse Theia は クラウド環境で使用できるオンラインエディタ環境を構成することができ、Google Cloud で提供している Cloud Shell Editor もTheia をベースとしています。(https://cloud.google.com/shell/docs/editor-overview)

フレームワーク

フレームワークは複雑だった Java の仕様をラップしてシンプルにしたり、開発生産性を上げるために登場してきました。ただし使わなくてもアプリケーションを組み上げることは可能です。
例えば次のような観点でフレームワークを使わないことが考えられます。

  • フレームワーク自体の学習コストの削減
  • フレームワークの制約や依存の回避

一方でフレームワークを使うメリットは次のようなことが考えられます。

  • トータルで早く開発できる
  • 便利な機能が利用できる
  • コードの揺れが抑えられる
  • バグが少なくなる

また、Struts のような少し前のフレームワークは Java EE (J2EE) の開発複雑性を隠蔽して開発生産性を上げることを目的にしていました。
一方で、最近のフレームワークではクラウドやコンテナの利用を想定した機能提供がされています。そのため、従来のように開発生産性をあげるという目的ももちろんなのですが、いわゆるクラウドネイティブなアプリケーションの開発を支援する目的もあります。そのような最近のフレームワークで注目されているのは次のようなものになります。

Sprint Boot / Quarkus / Micronaut
  • Spring Boot
  • Quarkus
  • Micronaut

他にも Ktor や Helidon といったフレームワークもあり、クラウドネイティブアプリケーションを作るためのフレームワークは注目を集めています。

このように、いろいろなフレームワークがあるとどれを使おうか、どれを学ぼうかと悩んでしまう方もきっといますよね。
このようなフレームワークの中で “GCP” と銘打っているものがあります。Spring Cloud GCP です。

今回はこの Spring Cloud GCP をご紹介していきます。

Spring Cloud GCP

では、Spring Cloud GCP がどのような機能を提供しているかを説明する前に、どのような考え方のフレームワークなのかを簡単に説明しますね。

クラウドネイティブアプリケーションの話をする時によく登場する Twelve-Factor App という考え方を聞いたことがありますか?
クラウドらしいアプリケーションの開発指針が 12 種類提言されているものになります。必ずしも全ての指針に従う必要はないですが、考え方として参考になるので、一度は目を通しておくとよいと思います。

この Twelve-Factor App の中に “バックエンドサービス” という指針があります。文字通り、データベースやメッセージブローカーなどの外部サービスをネットワーク越しにバックエンドにあるリソースとしてアタッチして使用するという考え方です。

Google Cloud が提供するサービスに置き換えてみると下図になりますね。
データベースに Cloud SQL、メッセージブローカーに Cloud Pub/Sub、ストレージに Cloud Storage になります。このようなサービスをアプリケーションからシンプルに接続して利用するための機能を提供します。

このように Google Cloud 上のサービスをバックエンドリソースとして選択出来るようになった後は、Spring の各種機能を使って実装を行います。
例えば、Cloud SQL を使用する場合は Spring Data を使って データベース処理を実装するといった開発になります。

Spring の世界と Google Cloud の世界を結びつけるような位置付けに Spring Cloud GCP があると考えると分かりやすいですね。

Spring Cloud GCP でサポートする Google Cloud サービス

Google Cloud 提供のサービスと Spring の橋渡しするとなると、どういったサービスが対応しているかが気になりますよね。
Spring Cloud GCP で利用できるサービスは次のようなものになります。

Spring Cloud GCP
  • Cloud SQL (MySQL)
  • Cloud SQL (PostgreSQL)
  • Cloud Spanner
  • Cloud Datastore
  • Cloud Firestore
  • Cloud Memorystore
  • BigQuery
  • Cloud Pub/Sub
  • Cloud Storage
  • Cloud Identiy-Aware Proxy
  • Secret Manager
  • Cloud Logging
  • Cloud Trace
  • Cloud Monitoring
  • Cloud Vision

データベースやメッセージング、監視機能に AI/ML というように様々な機能に対応していることが分かりますよね。これらの機能を使えば多くのアプリケーション開発を効率的にすすめていくことができるのではないかなと思います。

Spring Cloud GCP の使い方

それでは、ここからは Spring Cloud GCP をどのように使うかを見ていこうと思います。
基本的にはいずれの機能の場合も Google Cloud のサービスの API を Dependency Injection (DI) して使用するので、Spring を使った開発をした経験がある方は学習コストをかけることなく使えるようになります。

Google Cloud API の 依存性注入

DI により各種サービスの機能を使うわけなのですが、それらのサービスに対する依存関係にもバージョンがあります。そのためバージョンを指定して依存するライブラリを取得しないといけません。そこでビルドツール (Maven Gradle) を使って自動取得します。

Spring Cloud GCP の各プロジェクトの依存ライブラリは Bill of Materials (BOM) で管理されています。そのためビルドツールの Dependency Management のセクションで BOM を指定しておけば、各プロジェクトのライブラリに対しては個別にバージョン指定をしなくてよくなります。

  • Maven の場合
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2.3.7.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
  • Gradle の場合
dependencyManagement {
imports {
mavenBom "org.springframework.cloud:spring-cloud-dependencies:Hoxton.SR9"
}
}

このように BOM を設定したら、使いたい機能の依存関係を追加していきます。例えば Cloud SQL (MySQL) の場合は次のように追加します。

  • Maven の場合
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gcp-starter-sql-mysql</artifactId>
</dependency>
  • Gradle の場合
dependencies {
implementation("org.springframework.cloud:spring-cloud-gcp-starter-sql-mysql")
}

このように、その他の Spring の依存関係と同じように Spring Cloud GCP でも Starter Dependency を提供しています。この 該当するサービス用の Starter Dependency を追加することで必要になる個々のライブラリが追加されます。
各サービスの Starter Dependency は次のようになります。

  • Cloud SQL (MySQL) : spring-cloud-gcp-starter-sql-mysql
  • Cloud SQL (PostgreSQL) : spring-cloud-gcp-starter-sql-postgresql
  • Cloud Spanner : spring-cloud-gcp-starter-data-spanner
  • Cloud Datastore : spring-cloud-gcp-starter-data-datastore
  • Cloud Firestore : spring-cloud-gcp-starter-data-firestore
  • BigQuery : spring-cloud-gcp-starter-bigquery
  • Cloud Pub/Sub : spring-cloud-gcp-starter-pubsub
  • Cloud Storage : spring-cloud-gcp-starter-storage
  • Cloud Identiy-Aware Proxy : spring-cloud-gcp-starter-security-iap
  • Secret Manager : spring-cloud-gcp-starter-secretmanager
  • Cloud Logging : spring-cloud-gcp-starter-logging
  • Cloud Trace : spring-cloud-gcp-starter-trace
  • Cloud Monitoring : spring-cloud-gcp-starter-metrics
  • Cloud Vision : spring-cloud-gcp-starter-vision

Spring Cloud GCP for Cloud SQL (MySQL) を使ったアプリケーション

このように、Google Cloud 提供サービスに対応した Starter Dependency がある事が分かったと思います。これを追加するだけで、各サービスに関連したライブラリが取得できるのは便利ですね。
それではアプリケーションを作る上で1番必要になるであろうデータベースとの接続を想定したアプリケーションを作っていきたいと思います。

Spring Cloud GCP for Cloud SQL (MySQL)

Cloud SQL の MySQL インスタンスとの接続や操作を行うためには、先ほど例として記述していた spring-cloud-gcp-starter-sql-mysql の依存関係を追加しておきます。これを追加しておくと、Spring Boot のアプリケーションうごプロパティで次の設定項目が使えるようになります。

  • spring.cloud.gcp.sql.enabled
    Cloud SQL の Auto Configuration の 有効/無効切り替え (標準:有効)
  • spring.cloud.gcp.sql.instance-connection-name
    Cloud SQL インスタンスへの接続名
  • spring.cloud.gcp.sql.database-name
    データベース名
  • spring.cloud.gcp.sql.credentials.location
    OAuth2 認証用のクレデンシャルの JSON ファイル配置場所
  • spring.cloud.gcp.sql.credentials.encoded-key
    Base 64 エンコードしたクレデンシャルの JSON ファイルの内容

spring.cloud.gcp.sql.instance-connection-name は Cloud Console で Cloud SQL のところからも確認できますし、CLI で gcloud sql instances describe my-mysql を実行すると connectionName で表示されますね。
また、クレデンシャルは location か encoded-key のどちらかの設定のみでよいです。GKE を使ってアプリケーションを動かす場合は、Secret に入れておいて参照するのでもよいですね。

このように Spring Cloud GCP for Cloud SQL を使って設定をしておくと、内部で Cloud SQL 用の JDBC Provider が構成されてデータソースが自動で設定されます。

Spring によるデータベース操作

Spring Cloud GCP for Cloud SQL で Google Cloud 上に作成されている Cloud SQL インスタンスへの接続の準備ができました。
ここから、Spring を使ってデータベースの操作を行っていきます。

ところで、“Java を使ってデータベース接続” というと何を思い浮かべますか?多くの方は最初に JDBC (Java Database Connectivity) というキーワードを思い浮かべたりするかもしれないですね。

Connection を取得して、PreparedStatement に SQL を設定し実行して、ResultSet で next() を使ってカーソル移動させながら行ごとにデータを取得するボイラープレートコードを逐次書いていく JDBC 処理ですよね。

try (Connection con = dataSource.getConnection();
PreparedStatement ps = con.prepareStatement(SQL_STATEMENT);
ResultSet rs = ps.executeQuery()) {
int id = rs.getInt("id");
String name = rs.getString("name");
:
}

形式化しているとは言え記述量も多く、毎回同じようなコードを書くので結構面倒ですよね。
Spring では、この JDBC をラップして使用する機能を提供しています。

タイプとしては大きく 3 種類です。

  • JDBC ラッパー
    JDBC の使い勝手はそのままに ボイラープレートコードを削減できるように各種 JDBC API をラップ
    [Spring JDBC]
  • SQL マッパー
    SQL 文とエンティティクラスを Mapper クラスが 1:1 にマップ
    [MyBatis-Spring]
  • OR マッパー
    エンティティクラスとデーターベースモデルをマップ
    [Spring Data JPA]

それぞれのタイプをサポートする Spring プロジェクトも合わせて記載していますが、正確には Mybatis-Spring は Spring のプロジェクトではなく、OSS の SQL マッピングツールとして開発されている MyBatis の Spring 拡張プロジェクトです。
ところで、いくつかの機能タイプがありますが、いずれの場合もメリット・デメリットがあります。また使用者の好みやチーム開発での利用規定などもあると思います。そのため、それぞれの機能と上手に付き合ってもらうのが良いと思います。ちなみに個人的な私の好みは後ほど紹介しますね。

Spring JDBC による データベース操作

それではまず、JDBC ラッパーとして提供されている Spring JDBC を使ってデータベース操作を行っていきます。
Spring JDBC の特長は、Connection や PreparedStatement を開発者が自身で生成する必要がない (隠蔽化する) ので、SQL の作成とSQL 実行結果の扱いに注力することができます。

Spring JDBC は、Spring Core で提供されている データベースアクセス機能です。使用するためには依存関係を追加しますが、Spring Cloud GCP for Cloud SQL は Spring JDBC をベースにしているため追加は不要なのですが、明示的には次の Starter Dependency を追加します。

  • Maven の場合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
  • Gradle の場合
dependencies {
implementation("org.springframework.boot:spring-boot-starter-jdbc")
}

Spring JDBC でデータベース操作する場合のポイントは JdbcTemplate です。Autowired によって DI された JdbcTemplate インスタンスに SQL 文を設定するだけでデータベース操作ができるようになります。

public class DemoJdbcRepository {DemoJdbcRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate
}
public List<Book> findAll() {
List<Book> books = jdbcTemplate.queryForList("SELECT * FROM book")
public void addBook(Book book) {
jdbcTemplate.update("INSERT INTO book(isbn, name) values(? , ?)", book.getIsbn(), book.getName());
}
}

JdbcTemplate に対して、query() / queryForList() メソッドで参照系リクエスト、update() メソッドで 更新系リクエストを発行できます。
JDBC API を直接さわって操作するよりも随分とシンプルに操作ができますよね。

Spring Data JPA によるデータ操作

次に OR マッパーとして提供している Spring Data JPA を使ってデータベースを操作します。
Spring Data JPA は、Java EE の標準仕様としても定義されているデータアクセス仕様の JPA に基づいて提供される機能です。Java オブジェクトと データベースモデルを関連付けてデータ処理を行う方式になります。SQL は OR マッパーが Java オブジェクトとデータベースモデルの関係を解釈して自動発行します。
この Spring Data JPA の特長は文字通り JPA の仕様に準じているので、JPA の開発経験がある方はそのままの経験値を活かすことができます。

Spring Data JPA を仕様する場合は、次の Starter Dependency を追加します。

  • Maven の場合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
  • Gradle の場合
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}

Spring Data JPA でデータベース操作する場合のポイントは、データベースモデルと関連づける Java オブジェクトになる Entity クラスを設けることと、操作するための Repository インターフェースを設けることです。

  • Entity クラスの例

Entity クラスはデータモデルを意識したクラス設計になります。Table レイアウトとかをイメージすると分かりやすいですよね。この Entity クラスには業務ロジックは含めずデータの入れ物としてのクラスになります。そのため、getter / setter が乱立してしまい、冗長な記述になることが多々あります。
これを回避するために Lombok を使う方もいらっしゃいますよね。Lombok で getter/setter の記述が省略できます。しかし、IDE の設定が必要だったり、IDE の違いで警告が出たりなどという本質ではない問題が発生したりするので、私は Lombok は使わないです。
ちなみに、Java 14 からこういったデータ専用のクラスを定義できる Record クラスが追加されました。Lombok がカバーしていた機能を Java ネイティブで提供しているような機能です。Java もさらに便利になってきています。

@Entity
public class Book {
@Id
@GeneratedValue(strategy=GenerationType.AUTO)
private Long isbn;
private String title;
private String publisher;
public Book(String title, String publisher) {
this.title = title;
this.publisher = publisher;
}
public Long getId() {
return isbn;
}
public String getName() {
return title;
}
public String getPublisher() {
return publisher;
}
}
public String getLastName() {
return lastName;
}
}

次に Repository インターフェースを定義します。Repository インターフェースとは、Enity クラスを使ってデータベースと会話するためのインターフェースです。次のいずれかのインターフェースを継承することで、Out of the box で JPA の操作ができるようになります。

  • CrudRepository
  • PagingAndSortingRepository
  • JpaRepository

それぞれの違いですが、Out of the box で提供される機能が異なっています。
CrudRepository は、典型的な CRUD 機能を提供しています。findAll() や save()、delete() といったような機能です。
PagingAndSortingRepository は、ページサイズや現在のページ番号、またソート機能です。
JpaRepository は、CRUD 機能にページングとソート機能を提供します。つまり、CrudRepository + PagingAndSortingRepository = JpaRepository という感じです。

public interface DemoJpaRepository extends CrudRepository<Book, Long>

この Repository インターフェースを用いて処理を行う場合、DI された Repository インターフェースを使って定義済みメソッドを呼び出します。

@RestController
class DemoJpaController {
public DemoJpaCotroller(DemoJpaRepository demoJpaRepository) {
this.repository = demoJpaRepository;
}
@GetMapping("/")
public List<Book> findBooks() {
return repository.findAll();
}
:
}

定義済みメソッドだけでなく、カスタムメソッドも Repository インターフェースに定義しておけば使用できます。また、SQL のようにクエリーを直接発行したい場合は、JPA の仕様でも提供している JPQL が使用できます。
Spring Data JPA を使うと、かなりシンプルにデータベース操作ができるように見えますね。

データベース操作のもう1つの選択 — Spring Data JDBC

Spring Data JPA の OR マッピングで SQL も発行せずデータベース操作がシンプルにできるって便利だね、と思われた方もいるかもしれません。シンプルなのは間違いないのですが、JPA 仕様の本質を分かってからでないと、安易に利用するのは避けた方がよい、というのが私からのコメントです。
今回は、JPA の仕様自体は説明しませんが、Entity の読み込みに遅延ロードやキャッシュを使って、SQL 発行とは異なって一気に全てのデータを読み込むといった動きでない、処理が可能です。また、データモデルを保持している Entity インスタンスの生成や破棄といったライフサイクルも意識しておく必要があります。それに、“インピーダンス・ミスマッチ”と言われる、Java オブジェクトとデータモデルの構成の違いを意識した設計が必要になったりもします。JPA を使うのは、こういった考慮事項を把握した上でないとオススメはあまりしていません。

さて、私の好みを後でお伝えすると言ってましたが、次のように操作しています。

  • 基本は JdbcTemplate を使って SQL 発行
  • JPA のような CRUD 処理を OR マッピングで行う場合は、遅延ロードなどは行わない

Spring JDBC と Spring Data JPA をうまい具合に組み合わせたような使い方なのですが、これを機能として提供している Spring の機能があります。それが、Spring Data JDBC です。

Spring Data JDBC では、他にも次のような特長があります。

  • Entity インスタンスのライフサイクル管理不要
  • JPQL ではなく、 SQL フォーマットで記述可能

この Spring Data JDBC を使うための Starter Dependency は次のようになります。

  • Maven の場合
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
  • Gradle の場合
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-jdbc")
}

コードの記述の仕方は、大体 Spring Data JPA のような形式になります。Entity クラスを用意して、Repository インターフェースで操作の定義を行う、といった形です。

Spring Data JPA のときにも アノテーションをつけていたりしましたが、Sprind Data JDBC の場合は、ID を識別するための ID アノテーションが必要になるので忘れずに付けておいて下さい。

SQL を直接発行する場合は、Query アノテーションを使用して SQL を定義します。

public interface DemoJdbcRepository extends CrudRepository<Book, Long> {
@Query("SELECT * FROM book WHERE name = :isbn")
List<Book> getBooksByIsbn(Long isbn);
}

JdbcTemplate でSQL を使いつつ、OR マッピングも使いつつデータベース処理をするにはちょうどよい機能かなと思い、私は Spring Data JDBC を使うことが多いです。

Spring Cloud GCP のまとめ

Spring Cloud GCP は、最初の方にもお伝えしたように、Spring のエコシステムと Google Cloud を繋ぐアダプターのような役割を担ってくれています。
Spring Cloud GCP for Cloud SQL なら Spring Data、
Spring Cloud GCP for Cloud Pub/Sub なら Spring Integration のようにSpring の各種機能を使った Google Cloud のサービス利用をシンプルにしてくれます。

クリーンアーキテクチャのダイアグラムになぞらえると上図のような感じになるかなと思います。大事なのはロジックで、それを使うための環境要件を Google Cloud が与えてくれていて、利用するための仕組みをSpring Cloud GCP、そして Spring が提供してくれている、そんな感じですね。
そのため、もちろんオンプレミスで動かしたい場合も、アダプタ部分の Spring Cloud GCP を差し替えて、オンプレミス環境用に移し替えることも簡単に考えられますね。もちろん、オンプレミスから Google Cloud への移行も出来そうって思えますよね。

今回は、Spring Cloud GCP for Cloud SQL を中心に Spring のエコシステムと Google Cloud を結びつけるお話をしました。しかし、まだまだそれ以外の機能もたくさんあるので、これからも Spring Cloud GCP を使ったアプリケーション開発の話を発信していきたいなと思いますので、ご注目ください。

最後に、Spring Cloud GCP を使って Cloud SQL に接続するサンプルコードのリポジトリを紹介します。ただし、私のお気に入りの組み合わせで書いているのであまり参考にならないかもしれない、という言い訳をしておきます。
まず、言語が Java ではなく、Kotlin を使っています。そのため、ビルドツールの Gradle の build.gradle も Groovy でなく Kotlin で書かれています。コマンドラインも bash や zsh ではなく fish 前提で書いています。参考にならない参考コードということで見てもらえればと思います。
https://github.com/shinyay/spring-cloud-gcp-cloud-sql-gs

明日のアドベントカレンダー

明日 (22日) は、Shingo さんによる 「GCPでマイクロサービス Sagaパターン編」です。クラウドでアプリケーションを作っているとトランザクション処理をどのように処理したらよいかな、と考える方も多くいらっしゃると思います。そんな方たちに役立つヒントを説明してくれます。

お楽しみに!

--

--

Yanagihara Shinya
google-cloud-jp

Developer Advocate at VMware (ex Google and Pivotal). All views and opinions are my own. 🐦 : @yanashin18618