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

  1. Go to start.spring.io
  2. Select the following dependencies
     - Spring Reactive Web
     - Spring Reactive Data MongoDB
     - Embedded MongoDB Database
     - Lombok
    
  3. 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 :) .