StackStalk
  • Home
  • Java
    • Java Collection
    • Spring Boot Collection
  • Python
    • Python Collection
  • C++
    • C++ Collection
    • Progamming Problems
    • Algorithms
    • Data Structures
    • Design Patterns
  • General
    • Tips and Tricks

Monday, January 17, 2022

Spring WebFlux Example using Annotation based programming

 January 17, 2022     Java, Microservices     No comments   

This article will focus on implementing an annotation based Spring WebFlux reactive programming example. Introductory concepts around reactive programming and Spring WebFlux is explained in Reactive Programming with Spring WebFlux.

LoanInfoService example

In this example, we will implement a simple loan type handling service using Spring WebFlux. We expose a "/loans" endpoint and demonstrate the usage of annotations and reactive types Flux and Mono. The annotations are identical to Spring MVC framework.

Spring Boot dependencies

Create a Spring starter project and include the Spring Reactive Web support (WebFlux), Spring Data MongoDB Reactive, Project Lombak and test modules as dependencies to the pom.xml. For more details on Project Lombak refer to this article "Introduction to Project Lombak".
<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

Update the application.properties to specify the Mongo database. We are using a local Mongo database on port 27017 and the database would be "loans".
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
spring.data.mongodb.database=loans

Define the data object

We start with the LoanInfo POJO. We use the Lombok @Data annotation which generate the getters and setters needed.
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 Controller

In the Spring application we have included APIs to get all loans, add a loan, modify a loan and delete a loan on the endpoint "/loans". Pls note the usage of Flux and Mono the reactive types in the controller.
package com.stackstalk;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
public class LoanInfoController {

	@Autowired
	private LoanInfoService loanInfoService;
	
	@GetMapping("/loans")
	public Flux<LoanInfo> getLoans() {
		return loanInfoService.getAllLoans().log();
	}
	
	@PostMapping("/loans")
	public Mono<LoanInfo> addLoan(@RequestBody LoanInfo loanInfo) {
		return loanInfoService.addLoanInfo(loanInfo).log();
	}
	
	@PutMapping("/loans")
	public Mono<LoanInfo> updateLoan(@RequestBody LoanInfo loanInfo) {
		return loanInfoService.updateLoadInfo(loanInfo).log();
	}
	
	@DeleteMapping("/loans/{loanId}")
	public Mono<Void> deleteLoan(@PathVariable String loanId) {
		return loanInfoService.deleteLoanInfo(loanId).log();
	}
}

Define the repository

Now let us define the repository. Note that here we use the Mongo specific repository with reactive support.
package com.stackstalk;

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

public interface LoanInfoRepository extends ReactiveMongoRepository<LoanInfo, String> {

}

Define the service

Now let us implement the service logic. Mongo specific reactive repository is auto wired to the service. Also note the usage of Flux and Mono reactive types.
package com.stackstalk;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@Service
public class LoanInfoService {

	@Autowired
	private LoanInfoRepository loanRepository;
	
	public Mono<LoanInfo> addLoanInfo(LoanInfo loadInfo) {
		return loanRepository.save(loadInfo);
	}

	public Flux<LoanInfo> getAllLoans() {
		return loanRepository.findAll();
	}

	public Mono<LoanInfo> updateLoadInfo(LoanInfo loanInfo) {
		return loanRepository.save(loanInfo);
	}

	public Mono<Void> deleteLoanInfo(String loanId) {
		return loanRepository.deleteById(loanId);
	}
}

Define the application

Finally let us 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 on Mono with onSubscribe, request, onNext (once) and onComplete.
[2m2022-01-17 18:45:38.648[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ctor-http-nio-2][0;39m [36mreactor.Mono.UsingWhen.1                [0;39m [2m:[0;39m onSubscribe(MonoUsingWhen.MonoUsingWhenSubscriber)
[2m2022-01-17 18:45:38.650[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ctor-http-nio-2][0;39m [36mreactor.Mono.UsingWhen.1                [0;39m [2m:[0;39m request(unbounded)
[2m2022-01-17 18:45:38.728[0;39m [32m INFO[0;39m [35m13172[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:97}] to localhost:27017
[2m2022-01-17 18:45:38.771[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36mreactor.Mono.UsingWhen.1                [0;39m [2m:[0;39m onNext(LoanInfo(loanId=4, loanType=Education Loan, interestRate=5.5, maxTermInMonths=60, processingFee=20.0))
[2m2022-01-17 18:45:38.777[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36mreactor.Mono.UsingWhen.1                [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 on Flux with onSubscribe, request, onNext (multiple) and onComplete.
[2m2022-01-17 18:51:11.830[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ctor-http-nio-5][0;39m [36mreactor.Flux.UsingWhen.3                [0;39m [2m:[0;39m onSubscribe(FluxUsingWhen.UsingWhenSubscriber)
[2m2022-01-17 18:51:11.830[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ctor-http-nio-5][0;39m [36mreactor.Flux.UsingWhen.3                [0;39m [2m:[0;39m request(unbounded)
[2m2022-01-17 18:51:11.832[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36mreactor.Flux.UsingWhen.3                [0;39m [2m:[0;39m onNext(LoanInfo(loanId=2, loanType=Education Loan, interestRate=5.6, maxTermInMonths=60, processingFee=20.0))
[2m2022-01-17 18:51:11.833[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36mreactor.Flux.UsingWhen.3                [0;39m [2m:[0;39m onNext(LoanInfo(loanId=3, loanType=Education Loan, interestRate=5.5, maxTermInMonths=60, processingFee=20.0))
[2m2022-01-17 18:51:11.833[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36mreactor.Flux.UsingWhen.3                [0;39m [2m:[0;39m onNext(LoanInfo(loanId=4, loanType=Education Loan, interestRate=5.5, maxTermInMonths=60, processingFee=20.0))
[2m2022-01-17 18:51:11.833[0;39m [32m INFO[0;39m [35m13172[0;39m [2m---[0;39m [2m[ntLoopGroup-3-3][0;39m [36mreactor.Flux.UsingWhen.3                [0;39m [2m:[0;39m onComplete()
Get access to the full source code of this example in GitHub.
  • Share This:  
Newer Post Older Post Home

0 comments:

Post a Comment

Follow @StackStalk
Get new posts by email:
Powered by follow.it

Popular Posts

  • Avro Producer and Consumer with Python using Confluent Kafka
    In this article, we will understand Avro a popular data serialization format in streaming data applications and develop a simple Avro Produc...
  • Monitor Spring Boot App with Micrometer and Prometheus
    Modern distributed applications typically have multiple microservices working together. Ability to monitor and manage aspects like health, m...
  • Server-Sent Events with Spring WebFlux
    In this article we will review the concepts of server-sent events and work on an example using WebFlux. Before getting into this article it ...
  • Implement caching in a Spring Boot microservice using Redis
    In this article we will explore how to use Redis as a data cache for a Spring Boot microservice using PostgreSQL as the database. Idea is to...
  • Python FastAPI microservice with Okta and OPA
    Authentication (AuthN) and Authorization (AuthZ) is a common challenge when developing microservices. In this article, we will explore how t...
  • Spring Boot with Okta and OPA
    Authentication (AuthN) and Authorization (AuthZ) is a common challenge when developing microservices. In this article, we will explore how t...
  • Getting started with Kafka in Python
    This article will provide an overview of Kafka and how to get started with Kafka in Python with a simple example. What is Kafka? ...
  • Getting started in GraphQL with Spring Boot
    In this article we will explore basic concepts on GraphQL and look at how to develop a microservice in Spring Boot with GraphQL support. ...

Copyright © StackStalk