Reactive Endpoints using Project Reactor
In this article we will see how to create reactive endpoints in Spring Boot by performing simple CRUD operations with the reactive MongoDB.
Create a Spring Boot
- Go to start.spring.io
- Select the following dependencies
- Spring Reactive Web - Spring Reactive Data MongoDB - Embedded MongoDB Database - Lombok
- Generate the project and open it any IDE (like IntelliJ, Eclipse, VSCode, etc.)
Create the packages
To keep our code and classes organized, create the following packages
- controller
- entity
- dto
- repository
- service
- utils
Add the customer entity.
This is the entity that will be mapped and stored in database. Add the lombok annotations for Getters and Setters, Constructors, and Document to specify that this class will be mapped to the document in the MongoDB database.
entity/Customer.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "customers")
public class Customer
{
@Id
private String customerId;
private String customerName;
private String customerCountry;
}
Create Customer DTO
Create a data transfer object class for the Customer Entity.
dto/CustomerDto.java
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CustomerDto
{
private String customerId;
private String customerName;
private String customerCountry;
}
Create a utility class that will contain the methods to copy a Customer class object to CustomerDto class object and vice versa.
utils/AppUtils.java
import com.umang345.reactiveprogrammingmongocrud.dto.CustomerDto;
import com.umang345.reactiveprogrammingmongocrud.entity.Customer;
import org.springframework.beans.BeanUtils;
public class AppUtils
{
public static CustomerDto entityToDto(Customer customer)
{
CustomerDto customerDto = new CustomerDto();
BeanUtils.copyProperties(customer,customerDto);
return customerDto;
}
public static Customer dtoToEntity(CustomerDto customerDto)
{
Customer customer = new Customer();
BeanUtils.copyProperties(customerDto,customer);
return customer;
}
}
Create a Customer DAO layer
Create an interface extending ReactiveMongoRepository
repository/CustomerRepository.java
import com.umang345.reactiveprogrammingmongocrud.entity.Customer;
import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface CustomerRepository extends ReactiveMongoRepository<Customer, String> {
}
Create the Service Layer
Create a Customer Service class that will contain methods that will interact with the DAO layer. Add the customer repository as dependency in the service class.
service/CustomerService.java
@Service
public class CustomerService
{
.
.
@Autowired
private CustomerRepository customerRepository;
.
.
}
Add a method to get all customers from database.
service/CustomerService.java
@Service
public class CustomerService
{
.
.
public Flux<CustomerDto> getCustomers()
{
return customerRepository.findAll()
.map(customer -> AppUtils.entityToDto(customer));
}
.
.
}
Add a method to get a customer by customer Id from database.
service/CustomerService.java
@Service
public class CustomerService
{
.
.
public Mono<CustomerDto> getCustomer(String customerId)
{
return customerRepository.findById(customerId)
.map(customer -> AppUtils.entityToDto(customer));
}
.
.
}
Write a method to save the customer in database.
service/CustomerService.java
@Service
public class CustomerService
{
.
.
public Mono<CustomerDto> saveCustomer(Mono<CustomerDto> customerDtoMono)
{
return customerDtoMono.map(customerDto -> AppUtils.dtoToEntity(customerDto))
.flatMap(customer -> customerRepository.insert(customer))
.map(customer -> AppUtils.entityToDto(customer));
}
.
.
}
Write a method to update the customer in database
service/CustomerService.java
@Service
public class CustomerService
{
.
.
public Mono<CustomerDto> updateCustomer(Mono<CustomerDto> customerDtoMono, String customerId)
{
return customerRepository.findById(customerId)
.flatMap(c -> customerDtoMono.map(customerDto -> AppUtils.dtoToEntity(customerDto)))
.doOnNext(customer -> customer.setCustomerId(customerId))
.flatMap(customer -> customerRepository.save(customer))
.map(customer -> AppUtils.entityToDto(customer));
}
.
.
}
Write a method to delete a customer from database by customer id.
service/CustomerService.java
@Service
public class CustomerService
{
.
.
public Mono<Void> deleteCustomer(String customerId)
{
return customerRepository.deleteById(customerId);
}
.
.
}
Finally, the code for Customer Service class will look like this.
service/CustomerService.java
import com.umang345.reactiveprogrammingmongocrud.dto.CustomerDto;
import com.umang345.reactiveprogrammingmongocrud.repository.CustomerRepository;
import com.umang345.reactiveprogrammingmongocrud.utils.AppUtils;
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 CustomerService
{
@Autowired
private CustomerRepository customerRepository;
public Flux<CustomerDto> getCustomers()
{
return customerRepository.findAll()
.map(customer -> AppUtils.entityToDto(customer));
}
public Mono<CustomerDto> getCustomer(String customerId)
{
return customerRepository.findById(customerId)
.map(customer -> AppUtils.entityToDto(customer));
}
public Mono<CustomerDto> saveCustomer(Mono<CustomerDto> customerDtoMono)
{
return customerDtoMono.map(customerDto -> AppUtils.dtoToEntity(customerDto))
.flatMap(customer -> customerRepository.insert(customer))
.map(customer -> AppUtils.entityToDto(customer));
}
public Mono<CustomerDto> updateCustomer(Mono<CustomerDto> customerDtoMono, String customerId)
{
return customerRepository.findById(customerId)
.flatMap(c -> customerDtoMono.map(customerDto -> AppUtils.dtoToEntity(customerDto)))
.doOnNext(customer -> customer.setCustomerId(customerId))
.flatMap(customer -> customerRepository.save(customer))
.map(customer -> AppUtils.entityToDto(customer));
}
public Mono<Void> deleteCustomer(String customerId)
{
return customerRepository.deleteById(customerId);
}
}
Create a Controller
Create a customer controller class to add the endpoints that will communicate with the service layer. Add the Customer Service layer as a dependency in the controller class.
controller/CustomerController.java
@RestController
@RequestMapping("/customers")
public class CustomerController
{
.
.
@Autowired
private CustomerService customerService;
.
.
}
Add an endpoint to get all the customers from the database.
controller/CustomerController.java
@RestController
@RequestMapping("/customers")
public class CustomerController
{
.
.
@GetMapping
public Flux<CustomerDto> getCustomers()
{
return customerService.getCustomers();
}
.
.
}
Add an endpoint to get a customer by customer Id from the database.
controller/CustomerController.java
@RestController
@RequestMapping("/customers")
public class CustomerController
{
.
.
@GetMapping("/{id}")
public Mono<CustomerDto> getCustomer(@PathVariable String customerId)
{
return customerService.getCustomer(customerId);
}
.
.
}
Add an endpoint to add a customer in the database.
controller/CustomerController.java
@RestController
@RequestMapping("/customers")
public class CustomerController
{
.
.
@PostMapping
public Mono<CustomerDto> saveCustomer(@RequestBody Mono<CustomerDto> customerDtoMono)
{
return customerService.saveCustomer(customerDtoMono);
}
.
.
}
Add an endpoint to update a customer in the database.
controller/CustomerController.java
@RestController
@RequestMapping("/customers")
public class CustomerController
{
.
.
@PutMapping("/update/{id}")
public Mono<CustomerDto> updateCustomer(@RequestBody Mono<CustomerDto> customerDtoMono, @PathVariable String customerId)
{
return customerService.updateCustomer(customerDtoMono,customerId);
}
.
.
}
Add an endpoint to delete a customer in the database.
controller/CustomerController.java
@RestController
@RequestMapping("/customers")
public class CustomerController
{
.
.
@DeleteMapping("/delete/{id}")
public Mono<Void> deleteCustomer(@PathVariable String customerId)
{
return customerService.deleteCustomer(customerId);
}
.
.
}
Finally, the controller class will look like this.
controller/CustomerController.java
import com.umang345.reactiveprogrammingmongocrud.dto.CustomerDto;
import com.umang345.reactiveprogrammingmongocrud.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@RestController
@RequestMapping("/customers")
public class CustomerController
{
@Autowired
private CustomerService customerService;
@GetMapping
public Flux<CustomerDto> getCustomers()
{
return customerService.getCustomers();
}
@GetMapping("/{id}")
public Mono<CustomerDto> getCustomer(@PathVariable String customerId)
{
return customerService.getCustomer(customerId);
}
@PostMapping
public Mono<CustomerDto> saveCustomer(@RequestBody Mono<CustomerDto> customerDtoMono)
{
return customerService.saveCustomer(customerDtoMono);
}
@PutMapping("/update/{id}")
public Mono<CustomerDto> updateCustomer(@RequestBody Mono<CustomerDto> customerDtoMono, @PathVariable String customerId)
{
return customerService.updateCustomer(customerDtoMono,customerId);
}
@DeleteMapping("/delete/{id}")
public Mono<Void> deleteCustomer(@PathVariable String customerId)
{
return customerService.deleteCustomer(customerId);
}
}
Create the application.yml file
Add the mongoDB configs and port properties in the application.yml file
application.yml
spring:
data:
mongodb:
database: CustomersDB
host: localhost
port: 27017
server:
port: 9090
Now you can run your mongoDB instance either locally or using docker-compose and call and test these endpoints using any API platform like Postman.
I hope you found the article useful.
Lets connect :
Happy Coding :) .