Creating a reactive RESTful web service using Spring WebFlux, Spring Data and MongoDB
This article is taken from Getting started with Spring Framework, 4th Edition book. The source code for this article can be found in ch19-reactor3-webservice project (http://bit.ly/2zTuD0Y). To run the project, deploy ch19-reactor3-webservice project on Tomcat 9 and execute the ReactiveWebClient's main method (located in src/test/java folder).
To create a reactive RESTful web service you need to ensure that
each layer (data access, service and web) of the web service are reactive in nature.
Developing
the data access layer using Spring Data
As reactive database driver is available for MongoDB, you can use Spring
Data (Kay release) to reactively interact with MongoDB database. The following
listing shows the BankAccountReactorRepository (a Spring Data repository) that
defines methods that return reactive types (defined by Reactor):
public
interface BankAccountReactorRepository extends
ReactiveMongoRepository<BankAccountDetails, String>,
BankAccountReactorRepositoryCustom {
Mono<Long> countByBalance(int
balance);
Flux<BankAccountDetails>
findByBalance(int balance);
.....
}
NOTE – Instead of returning reactive types
(Flux and Mono) from the repository methods, you can return reactive types
defined by RxJava 2.
Configure Spring Data MongoDB
import org.springframework.data.mongodb.ReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.core.ReactiveMongoTemplate;
import org.springframework.data.mongodb.core.SimpleReactiveMongoDatabaseFactory;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;
.....
@Configuration
@EnableReactiveMongoRepositories(basePackages = "sample.spring.chapter19.bankapp.repository")
public class DatabaseConfig {
@Bean
public MongoClient mongoClient() throws UnknownHostException {
return MongoClients.create("mongodb://localhost");
}
public ReactiveMongoDatabaseFactory mongoDbFactory() .. {
return new SimpleReactiveMongoDatabaseFactory(mongoClient(),
"test");
}
@Bean
public ReactiveMongoTemplate reactiveMongoTemplate() .. {
return new ReactiveMongoTemplate(mongoDbFactory());
}
}
@EnableReactiveMongoRepositories
annotation enables use of reactive MongoDB repositories. The basePackages attribute
specifies the packages to scan for reactive MongoDB repositories.
@Bean-annotated mongoDbFactory method
creates and returns an instance of SimpleReactiveMongoDatabaseFactory. SimpleReactiveMongoDatabaseFactory’s
constructor accepts an instance of MongoClient and the name of the
database (which is test
in our case).
@Bean-annotated
reactiveMongoTemplate
method configures an instance of Spring Data MongoDB’s ReactiveMongoTemplate that is
used by repositories for performing reactive operations on MongoDB.
Developing
the service layer
As we don’t want the methods in the service layer to block, the
service methods return reactive types. The following listing shows
BankAccountService interface that define service methods:
public
interface BankAccountService {
Mono<BankAccountDetails> saveBankAccount(BankAccountDetails
bankAccountDetails);
Mono<Void> addFixedDeposit(String
bankAccountId, int amount);
.....
}
The
following listing shows the BankAccountServiceImpl class that implements
BankAccountService interface:
@Service
public
class BankAccountServiceImpl implements BankAccountService {
@Autowired
private BankAccountReactorRepository
bankAccountRepository;
.....
@Override
public Mono<Long> countByBalance(int
balance) {
return
bankAccountRepository.countByBalance(balance);
}
@Override
public Flux<BankAccountDetails>
findByBalance(int balance) {
return
bankAccountRepository.findByBalance(balance);
}
.....
}
The
countByBalance and findByBalance methods invoke the corresponding methods
defined in the BankAccountReactorRepository.
Developing the web layer using Spring
WebFlux
Spring
WebFlux module (introduced in Spring 5) supports developing reactive web applications
and RESTful web services. As in the case of Spring Web MVC, you can use @Controller, @GetMapping, and so on,
annotations to write reactive web controllers.
The
following listing shows the BankAccountController class (a reactive web
controller) of that calls BankAccountService’s methods:
import
reactor.core.publisher.Flux;
import
reactor.core.publisher.Mono;
.....
@RestController
@RequestMapping("/bankaccount")
public
class BankAccountController {
@Autowired
private BankAccountService
bankAccountService;
.....
@GetMapping("/countByBalance/{balance}")
public Mono<Long>
countByBalance(@PathVariable("balance") int balance) {
return
bankAccountService.countByBalance(balance);
}
@GetMapping("/findByBalance/{balance}")
public Flux<BankAccountDetails>
findByBalance(@PathVariable("balance") int balance) {
return
bankAccountService.findByBalance(balance);
}
.....
}
Configure Spring WebFlux
The
following listing shows the WebConfig class that configures WebFlux:
import
org.springframework.web.reactive.config.EnableWebFlux;
.....
@EnableWebFlux
@Configuration
@ComponentScan(basePackages
= "sample.spring.chapter19.bankapp.controller")
public
class WebConfig { }
In
the above listing, @EnableWebFlux annotation configures WebFlux for the
project. @ComponentScan specifies the packages that contain the classes
specific to the web layer. As controllers are defined in the
sample.spring.chapter19.bankapp.controller package, it is specified as the
value of basePackages attribute of @ComponentScan annotation.
Configuring the ServletContext
You
can programmatically configure the ServletContext of a WebFlux-based web
application (or RESTful web service) by using Spring’s
AbstractAnnotationConfigDispatcherHandlerInitializer class, as shown in the
following listing:
import
.....web.reactive.support.AbstractAnnotationConfigDispatcherHandlerInitializer;
.....
public
class BankAppInitializer extends
AbstractAnnotationConfigDispatcherHandlerInitializer {
@Override
protected Class<?>[]
getConfigClasses() {
return new Class[] { WebConfig.class,
DatabaseConfig.class,
BankAccountServiceImpl.class };
}
}
getConfigClasses
method returns @Configuration (or @Component) classes that we want to register
with the application context. WebConfig.class registers beans in the web layer
and DatabaseConfig.class registers beans in the data access layer.
Testing the reactive RESTful web service
Spring’s WebClient class (unlike RestTemplate) allows you to reactively interact with a reactive RESTful web service. The following listing shows the ReactiveWebClient class that accesses methods defined by BankAccountController:
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
.....
public class ReactiveWebClient {
private static Logger logger =
LogManager.getLogger(ReactiveWebClient.class);
private static WebClient webClient =
WebClient.create("http://localhost:8080/
ch19-reactor3-webservice/bankaccount");
public static void main(String args[]) throws InterruptedException {
// --find BankAccountDetails entities with balance 1000
webClient.get().uri("/findByBalance/{balance}",
1000).accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToFlux(BankAccountDetails.class)
.subscribe(account -> logger.info("account with balance 1000 -> " + account.getAccountId()));
}
}
WebClient’s create method creates an instance of WebClient with base URL, host and port information. As ch19-reactor3-webservice is deployed locally on port 8080 and the BankAccountController is mapped to /bankaccount request path, the following URL is passed to the create method: http://localhost:8080/ch19-reactor3-webservice/bankaccount.
The retrieve method sends the HTTP request and retrieves the response body.
The bodyToFlux
method extracts the response body to a Flux. As
BankAccountController’s findByBalance
method returns Flux<BankAccountDetails>
type, bodyToFlux(BankAccountDetails.class)
method is called to convert response body to Flux<BankAccountDetails>.
Comments
Post a Comment