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)
フォルダ構成
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