蔵書管理アプリケーションの概要

今回は、これまでのおさらいも兼ねて、Spring BootとAzure Cosmos DBを使ってもう少し実践的なWebアプリの作成にチャレンジしてみよう。想定しているWebアプリは、持っている書籍の情報を管理する蔵書管理アプリケーションで、全体像は下図のようなものになる。

  • 蔵書管理アプリケーションの概要

    蔵書管理アプリケーションの概要

これまでの例と同様に、データベースとしてCosmos DBを利用し、データの取得や格納をするバックエンドの部分をSpring Bootで作成した上でAzure App Serviceにデプロイする。そしてフロントエンドのユーザインタフェースは、HTMLおよびJavaScriptフレームワークのVue.jsを利用して作ってみる。AzureではAzureストレージに静的なWebサイトをホスティングする機能が用意されているため、フロントエンドの部分はこれを利用して公開してみる。

バックエンドのSpring Bootアプリの作成

まずは、Spring Bootアプリの部分を作っていこう。今回作ったアプリの全ソースコードは、こちらのGitHubリポジトリで公開しているので参考にしていただきたい。このサンプルは、Spring Initializrで雛形を作ったあとで、それをmacOSのIntelliJ IDEAで編集して作成している。JDKはローカルにZulu OpenJDK 11をインストールした。

基本的な内容は第4回および第5回で説明したものと同様なので、ここでは主要なポイントのみ解説する。

雛形の作成とCosmos DBの設定

最初に、Spring Initializrで雛形を作る。設定例は下図のようになる。以前Spring Initializrを紹介したときからリニューアルされてインタフェースが新しくなっているが、基本的な設定項目は変わらない。

  • Spring Initializrの設定例

    Spring Initializrの設定例

以前の例と異なるのは、DependenciesにSpring Securityを含めている点である。詳しくは後述するが、今回はフロントエンドとバックエンドで異なるサービスを使ってデプロイするため、相互アクセスのためのセキュリティ上の制限を考慮する必要がある。Spring Securityはその設定のために必要となる。

Cosmos DBを使用するための設定は、第4回の内容を参照いただきたい。Cosmos DBアカウントは前回と同じもの(本稿の例ではmynavidb)が利用できるだろう。pom.xmlの設定を追加するほか、src/main/resources/application.propertiesに、Cosmos DBのアクセス情報を記載するのを忘れないよにしよう。対象のデータベース名は「Booklist」とした。

application.properties
azure.cosmosdb.uri=[URI]
azure.cosmosdb.key=[プライマリ キー]
azure.cosmosdb.database=Booklist

エンティティの定義

データ格納のためのエンティティは「Book」として、次のように6つのフィールドを用意した。

jp/mynavi/azurejava/booklist/model/Book.java
package jp.mynavi.azurejava.booklist.model;

import com.fasterxml.jackson.annotation.JsonFormat;
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;

import java.time.LocalDate;

@Document(collection = "Book")
public class Book {
    @Id
    private String id;      // ID
    private String title;
    @PartitionKey
    private String category;    // パーティションキー
    private String author;
    private String publisher;
    @JsonFormat(pattern="yyyy-MM-dd")
    private LocalDate publicationDate;

    public Book(String id, String title, String category, String author, String publisher, LocalDate publicationDate) {
        this.id = id;
        this.title = title;
        this.category = category;
        this.author = author;
        this.publisher = publisher;
        this.publicationDate = publicationDate;
    }

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

プライマリキーはid、パーティションキーはカテゴリーを表すcategoryになっている。発行日を表すpublicationDateは、日付形式の値としたいためLocalDateクラスになっており、フォーマットは@JsonFormatアノテーションを利用して定義している。

リポジトリの作成

データベースとのインタフェースとなるリポジトリは「BookRepository」とした。

jp/mynavi/azurejava/booklist/dao/BookRepository.java
package jp.mynavi.azurejava.booklist.dao;

import com.microsoft.azure.spring.data.cosmosdb.repository.ReactiveCosmosRepository;
import jp.mynavi.azurejava.booklist.model.Book;
import reactor.core.publisher.Flux;

public interface BookRepository extends ReactiveCosmosRepository<Book, String> {
    Flux<Book> findByTitleContaining(String title);
}

今回は、書籍のタイトルで検索できるようにするために、findByTitleContaining()というメソッドを宣言してある。Spring Data JPAでは、特定の命名規則にしたがったメソッドをRepositoryインターフェースに宣言することによって、その実装が自動で作成される仕組みになっている。「findByTitleContaining」とした場合、titleフィールドに対するCONTAINS関数を利用したクエリを発行する実装になる。例えば「findByTitleContaining("hoge")」というメソッド呼び出しでは、「SELECT * FROM c WHERE CONTAINS(c.title, 'hoge')」のようなクエリになる。

メソッドの命名規則に関する具体的な仕様は公式ドキュメントにまとめられている。ただし、Cosmos DBはLIKE句をサポートしていないので、Likeを使うメソッドは正しく動作しない。Containingは標準ではLIKE句を使ったクエリになるが、Cosmos DBの場合はCONTAINS関数を使うように実装されている。

コントローラへのエンドポイントの定義

コントローラの定義は、jp/mynavi/azurejava/booklist/controller/BooklistController.javaのようになった。ここでは、下の表に示すようなエンドポイントと対応するメソッドを定義してある。

エンドポイント メソッド名 内容 HTTPメソッド
/api/booklist getBooklist すべての書籍のリストを返す GET
/api/booklist/search/{id} searchById 指定されたidの書籍を検索して詳細情報を返す GET
/api/booklist/titlesearch/{title} searchByTitle 指定された文字列を含むタイトルの書籍を検索して詳細情報を返す GET
/api/booklist/add addNewBook 新規で書籍を追加する POST
/api/booklist/update updateBook 既存の書籍の情報を更新する POST
/api/booklist/delete/{id} deleteBook 指定されたidの書籍を削除する GET
/api/hello hello 接続テスト用。「Hello Spring Boot!」という文字列を返す GET

新規登録用のaddNewBook()メソッドでは、UUID.randomUUID()を利用してidを自動生成するようになっている。また、getBooklist()、searchById()、searchByTitle()の3つのメソッドは検索結果をJSON形式で返す。

タイトルで書籍を検索するsearchByTitle()メソッドについては、前述のfindByTitleContaining()メソッドを呼び出して、指定された文字列を含むタイトルの書籍を探す。

BooklistController.javaのsearchByTitle()メソッド
@GetMapping(value = "/api/booklist/titlesearch/{title}", produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<?> searchByTitle(@PathVariable("title") String title) {
    try {
        logger.info("SEARCH - Seach param: " + title);
        final Flux<Book> findedItems = bookRepository.findByTitleContaining(title);
        logger.info("SEARCH - Find an item: " + findedItems.collectList().block().toString());
        return new ResponseEntity<>(findedItems.collectList().block(), HttpStatus.OK);
    } catch (Exception e) {
        logger.error("SEARCH ERROR: ", e.fillInStackTrace());
        return new ResponseEntity<String>(title + " not found", HttpStatus.NOT_FOUND);
    }
}

検索結果はFlux<Book>オブジェクトとして返されるため、collectList().block()を使ってList<Book>に変換している。

Azure App Serviceへのデプロイ設定

Azure App Serviceへのデプロイについては、IntelliJ IDEAのプラグインを用いて行う場合は第6回、Azure CLIを利用する場合には第3回の解説を参照いただきたい。その際、フロントエンドと連携させることを考慮して、リソースグループとアプリケーション名を固定のものにしておくとよい。

Azure CLIを使う場合、これはpom.xmlに対するazure-webapp-maven-pluginの設定で行うことができる。例えばリソースグループを「cloudnative」、アプリ名を「mynavi-booklist」としたい場合、azure-webapp-maven-pluginの設定例は次のようになる(ただし、自分で試す場合は任意の名前に変更すること)。

pom.xmlのazure-webapp-maven-pluginの設定例
      <plugin> 
        <groupId>com.microsoft.azure</groupId>  
        <artifactId>azure-webapp-maven-plugin</artifactId>  
        <version>1.9.0</version>  
        <configuration>
          <schemaVersion>V2</schemaVersion>
          <resourceGroup>cloudnative</resourceGroup>
          <appName>mynavi-booklist</appName>
          <pricingTier>B1</pricingTier>
          <region>japaneast</region>
          <runtime>
            <os>linux</os>
            <javaVersion>java11</javaVersion>
            <webContainer>java11</webContainer>
          </runtime>
          <appSettings>
            <property>
              <name>JAVA_OPTS</name>
              <value>-Dserver.port=80</value>
            </property>
          </appSettings>
          <deployment>
            <resources>
              <resource>
                <directory>${project.basedir}/target</directory>
                <includes>
                  <include>*.jar</include>
                </includes>
              </resource>
            </resources>
          </deployment>
        </configuration>
      </plugin> 

ここまでの状態でApp Serviceにデプロイしてみよう。公開用URLのhelloエンドポイント(本稿の例では「https://mynavi-booklist.azurewebsites.net/api/hello」)にアクセスして、「Hello Spring Boot!」と返ってくれば成功だ。

次回は、引き続きこの蔵書管理アプリケーションについて、Spring Securityの設定や、フロントエンドのWebサイトを公開するためのAzureストレージの設定について解説したい。