○Azure Cosmos DB Spring Boot Starterとは

前回はCosmos DBアカウントをセットアップして、そこにデータベース「mydb」を作成した。そして、Spring Bootアプリに対して次の2つの設定を行った。

application.properties にデータベースの接続情報を記述

プラグイン「Azure Cosmos DB Spring Boot Starter」を利用するための設定の追加

SpringアプリケーションからSQL APIを用いてCosmos DBにアクセスするためのライブラリとしては、「Spring Data for Azure Cosmos DB」がある。これはSpringアプリのための標準的なデータアクセス・フレームワークである「Spring Data」のAPIを用いて、Cosmos DBのデータベースを利用することができるライブラリである。Azure Cosmos DB Spring Boot Starterは、このSpring Data for Azure Cosmos DBを簡易的に使えるようにしてくれるプラグインだ。

○Azure Cosmos DBアカウントに関する補足

さて、ここでAzure Cosmos DBアカウントについて1つ補足しておかなければならない。前回は、最小構成のコンテナであればAzureサプスクリプションの12カ月間の無料サービスの範囲内で利用できることを紹介した。

それに加え、記事公開後に「Azure Cosmos DB Free Tier」というサービスが利用できるようになった。これは、各サブスクリプションに対して1つ設定できるオプションで、有効にすると毎月最初の400RU/s + 5GBストレージのデータベースが期間の制限なく無料で利用できるようになるというもの。スループットを全コンテナで共有するタイプのデータベースであれば、最大25個までコンテナを作成できる。

Cosmos DBアカウントの作成画面に「Apply Free Tier Discount」の設定項目が追加されているので、ここで[Apply]を選択しておけば有効にできる。詳細はMicrosoftによるこちらのエントリを参照していただきたい。

Azure Cosmos DB Free Tierが利用可能に

○エンティティの定義

それでは、Javaのプログラムを作っていこう。前回も使用したSpring Bootアプリをもとに、データベースへアクセスするコードを追加していく。なお、今回作成したサンプルプログラムの全ソースコードは こちらのGitHubリポジトリ で公開している。

まずは、データベースに保存するオブジェクトのエンティティを定義する。次のクラスは、社員の名前と役職を保持する Employee というエンティティを定義した例になる。ここでは省略しているが、各フィールド値のSetter/GetterやtoString()メソッドも必要となる。

Employee.java

package jp.mynavi.azurejava.sample;

import com.microsoft.azure.spring.data.cosmosdb.core.mapping.Document;

import com.microsoft.azure.spring.data.cosmosdb.core.mapping.PartitionKey;

import org.springframework.data.annotation.Id;

@Document(collection = "Employee")

public class Employee {

@Id

private String id; // ID

private String firstName;

private String lastName;

@PartitionKey

private String position; // パーティションキー

/* コンストラクタ */

public Employee(String id, String firstName, String lastName, String position) {

this.id = id;

this.firstName = firstName;

this.lastName = lastName;

this.position = position;

}

/* Setter, Getter, toString は省略 */

}

エンティティの定義は @Document アノテーションを使って行うことができる。引数collectionには対象となるCosmos DBのコンテナ名を指定する。また、フィールド値のうち、Cosmos DBのIDとして使用するものに対しては @Id アノテーションを、パーティションキーとして使用するものについては @PartitionKey アノテーションを指定する。

○リポジトリの作成

続いて、データベースとのインタフェースとなるリポジトリを作成する。Spring Dataのリポジトリは、データベースに対するCRUD操作(生成/読み取り/更新/削除)を簡易化してくれるインタフェースである。Spring Data for Azure Cosmos DBにはSpring Dataに用意されている Repositoryインタフェースを継承した ReactiveCosmosRepositoryインタフェースが提供されている。したがって、次の例のようにこのインタフェースを継承することで、独自のリポジトリを定義することができる。なお、リポジトリのインタフェースに対しては、@Repository アノテーションを指定する。

EmployeeRepository.java

package jp.mynavi.azurejava.sample;

import com.microsoft.azure.spring.data.cosmosdb.repository.ReactiveCosmosRepository;

import org.springframework.stereotype.Repository;

@Repository

public interface EmployeeRepository extends ReactiveCosmosRepository {

}

Repositoryインタフェースには、エンティティを追加したり検索したりするためのメソッドが定義されている。今回はやっていないが、ここで自前のメソッドを追加することもできる。

○コントローラへのエンドポイントの追加

次に、SampleController.java に対して、データベースにアクセスするエンドポイントを追加しよう。まず、フィールド値として次のようにログ出力のためのLoggerオブジェクトと、データベースアクセスのためのリポジトリを追加する。 @Autowired はSpring BootのDI(Dependency Injection)のためのアノテーションで、これによって EmployeeRepository のインスタンスを自動で注入してくれる。

SampleController.javaのフィールド宣言

private static final Logger logger = LoggerFactory.getLogger(SampleController.class);

@Autowired

private EmployeeRepository repository; // リポジトリ

エンドポイントとしては、新しいデータを追加する「add」、IDを指定してデータを検索する「search」、そして指定したIDのデータを削除する「delete」の3つを用意する。まず、addに対応するメソッドは次のような感じになった。

SampleController.javaのaddEmployee()メソッド

/**

* 新規登録

*/

@GetMapping("/add")

public ResponseEntity addEmployee(@RequestParam("id") String id,

@RequestParam("fname") String firstName,

@RequestParam("lname") String lastName,

@RequestParam("pos") String position) {

try {

final Employee employee = new Employee(id, firstName, lastName, position);

logger.info("ADD - Received an item: " + employee.toString());

final Employee savedEmployee = repository.save(employee).block();

logger.info("ADD - Saved an item: " + savedEmployee.toString());

return new ResponseEntity("Entity created", HttpStatus.OK);

} catch (Exception e) {

return new ResponseEntity("Entity creation failed", HttpStatus.NOT_FOUND);

}

}

@GetMapping は、GETリクエストを受け付けるエンドポイントのためのアノテーションだ。メソッドの引数に対して @RequestParam アノテーションを指定することで、リクエストに渡されたパラメータの値が、引数の値として渡されるようになる。ここでは Employee インスタンスの生成に必要な値を取得している。

Repositoryインタフェースのsave()メソッドは、データソースに対してデータの追加または更新処理を行うためのもの。save()のようなRepositoryのCRUD操作のためのメソッドは、戻り値としてMonoまたはFluxのインスタンスを返す。これらはReactor CoreというJavaでリアクティブ・プログラミングを実現するためのフレームワークによって提供されているもので、Monoは1つのリアクティブ・ストリームを、Fluxは複数のリアクティブ・ストリームを表すクラスになっている。

save()メソッドは、データの保存に成功すると、保存したエンティティを保持したMonoインスタンスを返す。block()メソッドを使えば、対象のエンティティのインスタンスを取得することができる。

searchエンドポイントについては、URLの「search/」に続けてIDを指定することで、そのIDのデータを検索して返すようにした。次の例のように、@GetMappingのパラメータ注で{}で囲った部分が、メソッドの @PathVariable アノテーションを指定した引数と対応づけられる。

SampleController.javaのgetEmployee()メソッド

/**

* 検索

*/

@GetMapping("/search/{id}")

public ResponseEntity getEmployee(@PathVariable("id") String id) {

try {

logger.info("SEARCH - param: " + id);

final Employee findEmployee = repository.findById(id).block();

logger.info("SEARCH - Find an item: " + findEmployee.toString());

return new ResponseEntity(findEmployee, HttpStatus.OK);

} catch (Exception e) {

return new ResponseEntity(id + " not found", HttpStatus.NOT_FOUND);

}

}

RepositoryのfindById()メソッドにIDを1つ渡すと、指定されたIDを持つエンティティを検索して、結果をMonoインスタンスとして返す。Employeeの例ではIDはString型なので、検索条件もStringとして渡している。

deleteエンドポイントもsearchと同様に、URLの末尾にIDをパラメータとして指定できるようにした。

SampleController.javaのdeleteEmployee()メソッド

/**

* 削除

*/

@GetMapping("/delete/{id}")

public ResponseEntity deleteEmployee(@PathVariable("id") String id) {

try {

logger.info("DELETE - param: " + id);

final Employee findEmployee = repository.findById(id).block();

logger.info("DELETE - Find an item: " + findEmployee.toString());

repository.deleteById(id, new PartitionKey(findEmployee.getPosition())).block();

logger.info("DELETE - Deleted an item: " + findEmployee.toString());

return new ResponseEntity("Entity deleted", HttpStatus.OK);

} catch (Exception e) {

return new ResponseEntity("Entity deletion failed", HttpStatus.NOT_FOUND);

}

}

deleteById()メソッドは、指定されたIDのエンティティを削除するメソッドだが、Cosmos DBの場合、引数としてIDのほかにパーティションキーも指定する必要がある。そこで、ここではまずIDで対象のエンティティを検索し、その結果からパーティションキーを取得した上で、検索条件となるPartisionKeyオブジェクトを作成した。

○アプリケーション・クラス

アプリケーション・クラスは、Spring Initializerで作成した時のものをそのまま使うことができる。

SampleWebAppApplication.java

package jp.mynavi.azurejava.sample;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication

public class SampleWebAppApplication {

public static void main(String[] args) {

SpringApplication.run(SampleWebAppApplication.class, args);

}

}

○ビルドとローカルでの実行

これで一通りのアプリケーションができたので、プロジェクトをビルドしよう。

$ mvn clean package

ビルドできたら、まずはローカルで実行してみよう。

$ mvn spring-boot:run

アプリケーションを起動した状態で、別のターミナルから次のように入力して、エンティティの追加を行ってみてほしい。

$ curl http://localhost:8080/add?id=001\&fname=Taro\&lname=Yamada\&pos=Programmer

Entity created

$ curl http://localhost:8080/add?id=002\&fname=Ichiro\&lname=Suzuki\&pos=Sales

Entity created

追加に成功していれば、AzureポータルのCosmos DBの管理画面で、2つのエンティティが追加されていることが確認できるはずだ。

エンティティが追加されたことが確認できる

続いて、追加したエンティティを検索してみよう。

$ curl http://localhost:8080/search/001

{"firstName":"Taro","lastName":"Yamada","position":"Programmer"}

$ curl http://localhost:8080/search/002

{"firstName":"Ichiro","lastName":"Suzuki","position":"Sales"}

エンティティの削除は次のように行う。

$ curl http://localhost:8080/delete/002

Entity deleted

削除した結果が反映されたかどうかも、Azureポータルで確認できるはずだ。

○Azure App Serviceへのデプロイ

さて、ローカルでの確認ができたら、今度は作成したアプリケーションをAzure App Serviceにデプロイしてみよう。第3回で説明したように、まずAzure CLIのaz loginコマンドを使ってAzureにログインした状態で、次のコマンドを実行すればデプロイできる。

$ mvn azure-webapp:deploy

デプロイに成功したら、サービスのURLを確認し、Webブラウザからアクセスしてみよう。次の図は、IDに001を指定してsearchしてみた様子である。検索した結果が文字列として返ってきていることがわかる。

検索結果が表示されている

追加や削除も、さきほどコマンドで実行した時と同様に、URLでパラメータを渡せば実行できるはずだ。

なお、App ServiceやCosmos DBのリソースは、立ち上げたままにしておくと利用料金がかかってしまうこともあるので、不要になったら必ずAzureポータルで削除しておくことをお勧めする。