This article will focus on implementing a functional model based Spring WebFlux reactive programming example. In functional programming model, functions are used to route and handle requests and contracts are designed for immutability. It is an alternative to the annotation-based programming model. Introductory concepts around reactive programming and Spring WebFlux is explained in Reactive Programming with Spring WebFlux.
LoanInfoService example
Spring Boot dependencies
<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> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.22</version> <scope>provided</scope> </dependency> </dependencies>
Configure application.properties
spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 spring.data.mongodb.database=loans
Define the data object
package com.stackstalk; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.mapping.Document; import lombok.AllArgsConstructor; import lombok.Data; @Data @AllArgsConstructor @Document public class LoanInfo { @Id private String loanId; private String loanType; private Float interestRate; private Integer maxTermInMonths; private Float processingFee; }
Define the repository
ppackage com.stackstalk; import org.springframework.data.mongodb.repository.ReactiveMongoRepository; public interface LoanInfoRepository extends ReactiveMongoRepository<LoanInfo, String> { }
Define the Handler Function
package com.stackstalk; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; @Component public class LoanInfoHandler { @Autowired private LoanInfoRepository loanRepository; public Mono<ServerResponse> addLoanInfo(ServerRequest serverRequest) { return serverRequest.bodyToMono(LoanInfo.class) .flatMap(l -> { return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(loanRepository.save(l), LoanInfo.class); }).log(); } public Mono<ServerResponse> getAllLoans(ServerRequest serverRequest) { return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(loanRepository.findAll(), LoanInfo.class).log(); } public Mono<ServerResponse> updateLoanInfo(ServerRequest serverRequest) { return serverRequest.bodyToMono(LoanInfo.class) .flatMap(l -> { return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(loanRepository.save(l), LoanInfo.class); }).log(); } public Mono<ServerResponse> deleteLoanInfo(ServerRequest serverRequest) { loanRepository.deleteById(serverRequest.pathVariable("loanId")); return ServerResponse.ok().build(Mono.empty()); } }
Define the Router Function
package com.stackstalk; 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 LoanInfoRouter { @Bean public RouterFunction<ServerResponse> routes(LoanInfoHandler handler) { return RouterFunctions.route(RequestPredicates.GET("/loans"), handler::getAllLoans) .andRoute(RequestPredicates.POST("/loans"), handler::addLoanInfo) .andRoute(RequestPredicates.PUT("/loans"), handler::updateLoanInfo) .andRoute(RequestPredicates.DELETE("/loans/{loanId}"), handler::deleteLoanInfo); } }
Define the application
package com.stackstalk; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class LoanInfoServiceApplication { public static void main(String[] args) { SpringApplication.run(LoanInfoServiceApplication.class, args); } }
Putting it all together and testing
Now, let us test the application and examine. On starting the application observe that Netty server is being started on port 8080. Also see the usage of reactive MongoDB repository./\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ [32m :: Spring Boot :: [39m [2m (v2.6.2)[0;39m [2m2022-01-17 18:41:14.149[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ main][0;39m [36mc.stackstalk.LoanInfoServiceApplication [0;39m [2m:[0;39m Starting LoanInfoServiceApplication using Java 16.0.2 on MYHOST-M-ABCD with PID 13172 (/Users/itsme/Documents/workspace-spring-tool-suite-4-4.12.0.RELEASE/BookInfoService/target/classes started by itsme in /Users/itsme/Documents/workspace-spring-tool-suite-4-4.12.0.RELEASE/LoanInfoService) [2m2022-01-17 18:41:14.151[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ main][0;39m [36mc.stackstalk.LoanInfoServiceApplication [0;39m [2m:[0;39m No active profile set, falling back to default profiles: default [2m2022-01-17 18:41:14.565[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Bootstrapping Spring Data Reactive MongoDB repositories in DEFAULT mode. [2m2022-01-17 18:41:14.689[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ main][0;39m [36m.s.d.r.c.RepositoryConfigurationDelegate[0;39m [2m:[0;39m Finished Spring Data repository scanning in 119 ms. Found 1 Reactive MongoDB repository interfaces. [2m2022-01-17 18:41:15.136[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ main][0;39m [36morg.mongodb.driver.cluster [0;39m [2m:[0;39m Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms'} [2m2022-01-17 18:41:15.492[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[localhost:27017][0;39m [36morg.mongodb.driver.connection [0;39m [2m:[0;39m Opened connection [connectionId{localValue:2, serverValue:96}] to localhost:27017 [2m2022-01-17 18:41:15.492[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[localhost:27017][0;39m [36morg.mongodb.driver.connection [0;39m [2m:[0;39m Opened connection [connectionId{localValue:1, serverValue:95}] to localhost:27017 [2m2022-01-17 18:41:15.492[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[localhost:27017][0;39m [36morg.mongodb.driver.cluster [0;39m [2m:[0;39m Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, minWireVersion=0, maxWireVersion=7, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=235134928} [2m2022-01-17 18:41:15.667[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ main][0;39m [36mo.s.b.web.embedded.netty.NettyWebServer [0;39m [2m:[0;39m Netty started on port 8080 [2m2022-01-17 18:41:15.675[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ main][0;39m [36mc.stackstalk.LoanInfoServiceApplication [0;39m [2m:[0;39m Started LoanInfoServiceApplication in 1.848 seconds (JVM running for 2.549)
Add a loan type with POST on /loans
This API is used to add a new loan type.curl --location --request POST 'http://localhost:8080/loans' \ > --header 'Content-Type: application/json' \ > --data-raw '{ > "loanId": "4", > "loanType": "Education Loan", > "interestRate": 5.5, > "maxTermInMonths": 60, > "processingFee": 20 > }'Returns the loan type added.
{"loanId":"4","loanType":"Education Loan","interestRate":5.5,"maxTermInMonths":60,"processingFee":20.0}In the logs observe usage of reactive signals with onSubscribe, request, onNext (once) and onComplete.
[2m2022-01-17 19:09:42.690[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36morg.mongodb.driver.connection [0;39m [2m:[0;39m Opened connection [connectionId{localValue:3, serverValue:108}] to localhost:27017 [2m2022-01-17 19:10:49.384[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.FlatMap.2 [0;39m [2m:[0;39m | onSubscribe([Fuseable] MonoFlatMap.FlatMapMain) [2m2022-01-17 19:10:49.385[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.FlatMap.2 [0;39m [2m:[0;39m | request(unbounded) [2m2022-01-17 19:10:49.424[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.FlatMap.2 [0;39m [2m:[0;39m | onNext(org.springframework.web.reactive.function.server.DefaultEntityResponseBuilder$DefaultEntityResponse@7afee938) [2m2022-01-17 19:10:49.448[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.FlatMap.2 [0;39m [2m:[0;39m | onComplete()
Query all loan types using GET on /loans
This API is used to query all added loan types.curl --location --request GET 'http://localhost:8080/loans'Returns the loan type added.
[{"loanId":"3","loanType":"Education Loan","interestRate":5.6,"maxTermInMonths":60,"processingFee":20.0}, {"loanId":"4","loanType":"Education Loan","interestRate":5.5,"maxTermInMonths":60,"processingFee":20.0}]In the logs observe usage of reactive signals with onSubscribe, request, onNext (multiple) and onComplete.
[2m2022-01-17 19:09:42.561[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.MapFuseable.1 [0;39m [2m:[0;39m | onSubscribe([Fuseable] FluxMapFuseable.MapFuseableSubscriber) [2m2022-01-17 19:09:42.564[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.MapFuseable.1 [0;39m [2m:[0;39m | request(unbounded) [2m2022-01-17 19:09:42.564[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.MapFuseable.1 [0;39m [2m:[0;39m | onNext(org.springframework.web.reactive.function.server.DefaultEntityResponseBuilder$DefaultEntityResponse@8061ac0) [2m2022-01-17 19:09:42.666[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ctor-http-nio-3][0;39m [36mreactor.Mono.MapFuseable.1 [0;39m [2m:[0;39m | onComplete() [2m2022-01-17 19:09:42.690[0;39m [32m INFO[0;39m [35m18563[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36morg.mongodb.driver.connection [0;39m [2m:[0;39m Opened connection [connectionId{localValue:3, serverValue:108}] to localhost:27017Get access to the full source code of this example in GitHub.
0 comments:
Post a Comment