Creating a Reactive RESTful Web Service Using Spring WebFlux, Spring Data, and MongoDB
Learn how to create a reactive RESTful web service step-by-step in this tutorial for Spring WebFlux, Spring Data, and MongoDB.
Join the DZone community and get the full member experience.
Join For FreeThis article is based on the book Getting Started With Spring Framework, 4th Edition. The source code for this article can be found in the ch19-reactor3-webservice
project (http://bit.ly/2zTuD0Y). To run the project, deploy the ch19-reactor3-webservice
project on Tomcat 9 and execute the ReactiveWebClient
's main
method (located in the 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());
}
}
The @EnableReactiveMongoRepositories
annotation enables the use of reactive MongoDB repositories. The basePackages
attribute specifies the packages to scan for reactive MongoDB repositories.
The @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 the test in our case).
The @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 defines service methods:
public interface BankAccountService {
Mono<BankAccountDetails> saveBankAccount(BankAccountDetails bankAccountDetails);
Flux<BankAccountDetails> findByBalance(int balance);
Mono<Void> addFixedDeposit(String bankAccountId, int amount);
.....
}
The following listing shows the BankAccountServiceImpl
class that implements the 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) 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 the /bankaccount
request path, the following URL is passed to the create the 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, the bodyToFlux
(BankAccountDetails.class
) method is called to convert the response body to Flux<BankAccountDetails>
.
Opinions expressed by DZone contributors are their own.
Comments