Day-to-day the memorandum

やったことのメモ書きです。

Spring Webfluxを触ってみた

Spring Webfluxを触ってみました。

はじめに

以下のようにデータを保存したり、リストを取ってきたり、データを削除したりするAPIを作ります。
データベースはMongoDBを使いました。

  • データ登録
$ curl -i -XPOST -H 'Content-Type: application/json' localhost:8080/person -d '{"name": "jiro", "age": 18}'
HTTP/1.1 201 Created
Connection: keep-alive
Location: http://localhost:8080/person
Content-Type: application/json
Content-Length: 56

{"id":"5bb07371f25a3a1844bc6530","name":"jiro","age":18}
  • リスト取得
$ curl -i -XGET localhost:8080/person
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: application/json

[{"id":"5bb048fef25a3a30e8d07338","name":"taro","age":20},{"id":"5bb07371f25a3a1844bc6530","name":"jiro","age":18}]
  • データ削除
$ curl -i -XDELETE localhost:8080/person/5bb07371f25a3a1844bc6530
HTTP/1.1 204 No Content

データベース

今回はDockerでMongoDBを用意しました。

$ docker --version
Docker version 18.06.1-ce, build e68fc7a

$ docker run -d -p 27017:27017 --name dev-mongo mongo:4.1.3

API作成

ここからAPIを作っていきます。

Java version

$ java -version
java version "11" 2018-09-25
Java(TM) SE Runtime Environment 18.9 (build 11+28)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11+28, mixed mode)

フォルダ構成

f:id:ZYPRESSEN:20180930204713p:plain

pomの定義

以下URLを参考にしながら定義していきます。
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#getting-started-maven-installation
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#howto-use-another-web-server
特に理由はないのですが、Undertowを使うようにしてみました。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-reactor-netty</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
    
</project>

apllication.yamlの作成

以下URLを参考にMongoDBのエンドポイントなどを設定していきます。
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-connecting-to-mongodb
DBにユーザを設定してないので、ここにも書きません。

spring:
  data:
    mongodb:
      host: localhost
      port: 27017
      database: sample

Application.javaの作成

https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#using-boot-locating-the-main-class
ドキュメントに書いてある通りで、なにも手を加えてません。

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Collection

MongoDBのCollectionを表現するクラスを作っていきます。
今回はnameとageのデータを持つことにします。
MongoDBのCollectionの指定を省略するとクラス名が小文字になった名前が使われます。
指定したい場合は@Document(collection="people")のように指定します。

package com.example.demo;

import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document
public class Person {
    @Id
    private String id;
    private String name;
    private int age;
    // getter/setter 省略
}

Repositoryクラスの作成

@Repositoryを付けてReactiveMongoRepositoryを継承します。
新たにメソッドは定義しません。

package com.example.demo;

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PersonRepository extends ReactiveMongoRepository<Person, String> {
}

Handlerクラスの作成

https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/web-reactive.html#webflux-fn-handler-functions
以上のURLを参考に、以下の処理とHTTPレスポンスなどを書いていきます。

  • データを全件とってくる
  • データを保存する
  • データを削除する

おまけでHello Worldの文字列を返す処理も書いておきました。

package com.example.demo;

import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;

@Component
public class PersonHandler {
    private final PersonRepository personRepository;

    public PersonHandler(PersonRepository personRepository) {
        this.personRepository = personRepository;
    }

    public Mono<ServerResponse> hello(ServerRequest request) {
        return ServerResponse.ok().body(BodyInserters.fromObject("Hello World"));
    }

    // 全件とってくる
    public Mono<ServerResponse> findAll(ServerRequest request) {
        return ServerResponse  // レスポンス組み立てる
                .ok()  // HTTPステータス
                .contentType(MediaType.APPLICATION_JSON)
                .body(personRepository.findAll(), Person.class);  // bodyに結果をつめる
    }

    // 作成する
    public Mono<ServerResponse> create(ServerRequest request) {
        Mono<Person> personMono = request.bodyToMono(Person.class);  // リクエストからMonoを作る
        return personMono
                .flatMap(personRepository::save)  // データベースに保存
                .flatMap(person ->
                        ServerResponse
                                .created(request.uriBuilder().build())  // HTTPステータス
                                .contentType(MediaType.APPLICATION_JSON)
                                .body(BodyInserters.fromObject(person)));  // 保存したデータをbodyにつめる
    }

    // 削除する
    public Mono<ServerResponse> delete(ServerRequest request) {
        return personRepository
                .findById(request.pathVariable("id"))  // 該当データを取得
                .flatMap(person -> ServerResponse.noContent().build(personRepository.delete(person)))  //  データを削除、削除できれば204を返す
                .switchIfEmpty(ServerResponse.notFound().build());  // データがなければ404を返す
    }
}

Routerクラスの作成

以下のURLのRoutingConfigurationを参考に、どのパスにアクセスしたらどのHandlerのメソッドを呼び出すかを定義していきます。
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-webflux

package com.example.demo;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;

@Configuration
public class Router {
    @Bean
    public RouterFunction<ServerResponse> monoRouterFunction(PersonHandler personHandler) {
        return RouterFunctions
                .route(RequestPredicates.GET("/"), personHandler::hello)
                .andRoute(RequestPredicates.GET("/person"), personHandler::findAll)
                .andRoute(RequestPredicates.POST("/person"), personHandler::create)
                .andRoute(RequestPredicates.DELETE("/person/{id}"), personHandler::delete);
    }
}

curlでリクエストを送ってみる

IDEの起動ボタン、もしくは以下のコマンドで起動します。

$ mvn spring-boot:run

あとはcurlを叩くだけ!

# 登録
$ curl -i -XPOST -H 'Content-Type: application/json' localhost:8080/person -d '{"name": "taro", "age": 20}'
HTTP/1.1 201 Created
Connection: keep-alive
Location: http://localhost:8080/person
Content-Type: application/json
Content-Length: 56

{"id":"5bb048fef25a3a30e8d07338","name":"taro","age":20}

# 全件取得
$ curl -i -XGET localhost:8080/person
HTTP/1.1 200 OK
Connection: keep-alive
Transfer-Encoding: chunked
Content-Type: application/json

[{"id":"5bb048fef25a3a30e8d07338","name":"taro","age":20}]

# 削除
$ curl -i -XDELETE localhost:8080/person/5bb048fef25a3a30e8d07338
HTTP/1.1 204 No Content

さいごに

githubリポジトリです。
今後の記事に、このリポジトリを使いたいのでv1.0というTagを打っておきました。
github.com