Building Reactive REST APIs with Spring boot 3.2.0 part II

Timz Owen
6 min readDec 12, 2023

--

Welcome to part II which is a continuation of part I where we did the basic set up to get us started build CRUD APIs. once done part III comes next.

Up next: Building the business Logic.

1.0 Building the Repository Layer:

The repository layer is responsible for providing and handling database transactions & the relations between databases. Java Persistence API (Application Programming Interface) is the main library that facilitates this. It uses the @Repository annotation to allow us to use the inbuilt methods.

First create a package — you can name it student.

Create an Interface inside the package. it extends the Reactive CRUD repository. Without reactive, you only extend JPA Repository. will cover this in a different story.

public interface StudentRepository extends ReactiveCrudRepository<Student, Long> {
}

Student — Represents the model.

Long — is the type of ID referenced by the model i.e. student in the model.

Now, for instances where we would want to improvise methods not provided by the service class, we can write out own methods here. Example… Querying students by FirstName. It can either return a List of students or just one student with that name. For Reactive, we return a Flux instead of a List.

Flux — allows you to work with streams of data efficiently, also non-blocking, and can handles data streaming in in large quantities. Lists won't allow you to use this capability.

Mono — This represents a container type for a single value that may be available immediately or at some point in the future [streaming data which may not be available at that moment]. it supports g asynchronous and non-blocking operations.

    // method to get students by first Name and ignoreCase
Flux<Student> findStudentsByFirstnameIgnoreCase(String firstName);

2.0 Building the Service & Service Implementation Layers

Service layer is the layer that facilitates communication between the controller and the Repository layer. This is the layer that stores all the business logic.

  1. Create a new package. Name — service.
  2. Inside service package crate a java interface. Name — StudentService.

Create methods that will handle:

  1. save new student in database.
  2. updating existing student record
  3. delete student based on given student Id.
  4. Fetch all students in the database.
  5. Delete all records in the database.

Before we create the method, let's build a class — studentImplentation to help with abstraction and prevent users directly interacting with the database. Direct interaction with the database isn't recommended in programming.

The student implementation class ‘implements service class ’ . Implement allows us to access the methods define in the service interface.

Create a package under the service package — Name “impl”.

Inside the Implementation package, create a Java class and name it “StudentServiceImpl”.

Back to: Service class, lets create the methods now.

Let's write this method to perform CRUD tasks. N/B: Mono is used where the expected return value is just one object While Flux is used where the expected return value is a list.

  // methods goes here

// save all students to the database
Flux<Student> saveAllStudents();

// save multiple entries to the database
Mono<Student> saveSingleStudent(Student student);

// Fetch student record from database
Mono<Student> findStudentById(Long id);

// fetch multiple records from database
Flux<Student> findAllStudents();

//delete single student by id referenced
Mono<Student> deleteById(Long Id);

// delete all entries in the database
void deleteAll();

Up next: ServiceImpl

If you are using IntelliJ Idea, there is a shortcut to help you write the implantations of the methods created in the service class. click on the red warning ‘bulb icon’ for suggestions and click implement method.

Find the steps here.

once you done, your generated code looks like this:

   @Override
public Flux<Student> saveAllStudents() {
return null;
}

@Override
public Mono<Student> saveSingleStudent(Student student) {
return null;
}

@Override
public Mono<Student> findStudentById(Long id) {
return null;
}

@Override
public Flux<Student> findAllStudents() {
return null;
}

@Override
public Mono<Student> deleteById(Long Id) {
return null;
}

@Override
public void deleteAll() {

}

Now let's build finish up writing the code to handle the Logic in the service implantation.

Step 1: Annotate the class with @Service and @RequiredArgsConstructor . Then instantiate the Repository interface inside the service impl class to allow us to use the repository CRUD methods. Instead of returning null, we will now be returning the student repository which has inbult methods to perform CRUD Ops.

Saving Methods:

For the save all Method, is takes in a list i.e Flux of student objects. So, let's go back and change this to have arguments of Flux objects in the student service saveAllStudent Method.

Now the final code base in the implantations class for saving data should look like this.

// This method allows you to save a record in the database.
@Override
public Flux<Student> saveAllStudents(Flux<Student> students) {
return studentRepository.saveAll(students);
}

// save single record to the database
@Override
public Mono<Student> saveSingleStudent(Student student) {
return studentRepository.save(student);
}

Fetch Methods:

This Methods allows us to get student records from the database. We can either get a single record or a list of student records. Let’s check the implementations.

// Fetch single student record from database
@Override
public Mono<Student> findStudentById(Long id) {
return studentRepository.findById(id);
}

// Fetch multiple student records from database
@Override
public Flux<Student> findAllStudents() {
return studentRepository.findAll();
}

Update Method:

This is the method that will allow us to update existing details of a student from the database: Back to the Student Service interface, add method to do so…..

If you are using InteliJ, shortcut way to implement the method is to click on the “related problem” usually in red , and it will ask you to implement the method in the service impl interface.

// updating the student record in the database <implementation in impl class>
@Override
public Mono<Student> updateStudent(Long id) {
return null;
}

Now let's build the Logic to update the record in the database. N/B: In the method for updating student, add entity Student as part of the parameter with Long Id. you can reference below also while writing the logic. Also include the Student in the Service Impl update method

// updating the student record in the database
@Override
public Mono<Student> updateStudent(Student student, Long studentId) {
return studentRepository.findById(studentId)
.flatMap(existingStudent -> {
//update student new values here
existingStudent.setFirstname(student.getFirstname());
existingStudent.setLastname(student.getLastname());
existingStudent.setAge(student.getAge());

// now we save the existing student with the new values
return studentRepository.save(existingStudent);
});
}

Delete Methods

This is a pretty straight forward method for deleting records in the database. N/B I updated my delete for specific id method to return a String. Make sure to do so too.

   // First find the record then delete it.
@Override
public String deleteById(Long id) {
studentRepository.deleteById(id);
return "Student with ID: " + id + " deleted";
}

// CAUTION!! This deletes all records in the database
@Override
public void deleteAll() {
studentRepository.deleteAll();
}

Up Next……...

In the final part, we get to test the CRUD RESTful APIs and work on handling exceptions and improve on the delete method. We shall also close by building the Controller Layer which provides the End points to be consumed……part 3.

--

--

Timz Owen

Full stack SRE | DevOps | Automation Engineer. I Love building Tech communities @Africa