Create REST API in accounts Microservice
Now is the time to build the actual business logic inside our accounts Microservice.
Here, we are going to build a REST API that will support the creation of a new account and new customer details inside our H2 database that we are maintaining.
So in order to create a new REST API service, we need to go to the AccountsController class.
@RequestMapping annotation
In real projects, whenever we are trying to build REST APIs, it is always recommended to maintain a prefix API path which is common for all REST APIs present inside your controller class. Like specifying the base path.
@RestController
@RequestMapping(path = "/api")
public class AccountsController {
}
For the same, we need to mention the annotation which is @RequestMapping. To this annotation, I’m going to pass the path parameter.
For now I’m giving the prefix path as /api, which means any REST API that I’m trying to implement inside this controller class, it is going to have this prefix path.
It is also good practice to mention what is a return type data format from your REST API. So we are going to support the return type data type as JSON. That’s why we need to mention produces.
@RestController
@RequestMapping(path = "/api", produces = {MediaType.APPLICATION_JSON_VALUE})
public class AccountsController {
}
So this conforms to my spring boot framework, the REST API that I’m going to implement inside this controller (or) inside this microservice, they are going to support the response type of type JSON.
Create REST API method
ResponseEntity
I’m going to create a method that supports create operation.
– All the methods that map to HttpRequest method codes should be public.
– They must return ResponseEntity
ResponseEntity is a class in the Spring Framework that represents the entire HTTP response, including the status code, headers, and response body. It’s commonly used in Spring MVC applications to handle and manipulate HTTP responses sent back to clients.
In our case, we are going to return a ResponseDto object.
– the method name is going to be createAccount().
– to this createAccount() , I’m going to accept the request from my client applications. That’s why I need to mention the @RequestBody. With the help of this annotation, whatever my clients are sending inside the RequestBody, the same will be mapped into the POJO class that I’m trying to mention after this RequestBody.
So anyone who wants to create a new account using my accounts Microservice, they need to pass /api/create along with the input data, then only the invocation will work.
— return object from REST API – ResponseEntity
return ResponseEntity
.status(HttpStatus.CREATED)
.body(new ResponseDto(AccountsConstants.STATUS_201,
AccountsConstants.MESSAGE_201));
- using Response entity class, send what is a status. Whenever the creation of an account is successful, I want to mention the HttpStatus.CREATED. This means the given resource is created successfully in the backend server.
- Using the body method, I’m going to send the object of ResponseDto that we have created.
Skeleton Method without business logic
@RestController
@RequestMapping(path = "/api", produces = {MediaType.APPLICATION_JSON_VALUE})
public class AccountsController {
@PostMapping("/create")
public ResponseEntity<ResponseDto> createAccount(
@RequestBody CustomerDto customerDto) {
return ResponseEntity
.status(HttpStatus.CREATED) // this will go in Header
.body(new ResponseDto(AccountsConstants.STATUS_201,
AccountsConstants.MESSAGE_201));
}
}
ResponseEntity is a class present inside the spring framework using which we can send the overall status, body.
NOTE – If you send only ResponseDto as a return object instead of ResponseEntity, whatever you have populated inside the ResponseDto, the same will be sent inside the body of the response, but the client will never receive what is the overall status.
Service Layer
Business Logic for Create operation
Now, we need to write the business logic to create the new account inside our H2 database.
→ So all the business logic we should always write inside the service layer.
The controller layer is only responsible to accept the request and to send the response and to perform any validations.
That’s why let’s try to create a service layer with the help of an interface and its implementation.
Create Service interface
First create a service package and inside this service package, first I’m going to create a new interface and the interface name is be IAccountsService.
Inside this interface, have methods that can be used for business logic.
import com.eazybytes.accounts.dto.CustomerDto;
public interface IAccountsService {
/**
*
* @param customerDto - CustomerDto object
*/
void createAccount(CustomerDto customerDto);
}
Service Implementation classes
I’m going to create a new package with the name impl. So impl means implementation.
Inside this implementation package, I’m going to create a new class with the name AccountServiceImpl.
@Service
public class AccountsServiceImpl implements IAccountsService {
@Override
public void createAccount(CustomerDto customerDto) {
}
}
First, add @Service annotation to indicate to Spring Boot framework that this class is going to act as a service layer, so as for spring framework to create a bean.
And once the bean is created by the Spring Boot framework, I can auto wire this bean to the controller layer.
Autowire Repository classes inside Service class
First let us autowire the repository classes to this Service implementation class.
@Service
@AllArgsConstructor
public class AccountsServiceImpl implements IAccountsService {
private AccountsRepository accountsRepository;
private CustomerRepository customerRepository;
/**
*
* @param customerDto - CustomerDto object
*/
@Override
public void createAccount(CustomerDto customerDto) {
}
}
By adding @AllArgsConstructor, the Lombok library creates a constructor that takes all the parameters in the class.
Whenever there is only a single constructor inside your class that is accepting parameters, you don’t need to manually autowire these repository classes with the help of @Autowire.
The spring framework can automatically do the Autowiring because there is only a single constructor and the single constructor is going to accept all these parameters. That’s how the autowiring will work automatically.
→ To save the data into the database with the help of these repository interfaces, we need to send the object of entity classes [Customer, Accounts] but not DTO class. There should be some mapping logic which will convert the DTO to entity class and entity class to DTO.
That’s where the Mapper comes into picture.
Mapper classes
I’m going to create a new package with the name Mapper. Inside this mapper package, create the AccountsMapper class.
This mapper is going to take care of mapping DTO to entity and entity to DTO.
AccountsMapper
import com.eazybytes.accounts.dto.AccountsDto;
import com.eazybytes.accounts.entities.Accounts;
public class AccountsMapper {
public static AccountsDto mapToAccountsDto(Accounts accounts,
AccountsDto accountsDto)
{
accountsDto.setAccountNumber(accounts.getAccountNumber());
accountsDto.setAccountType(accounts.getAccountType());
accountsDto.setBranchAddress(accounts.getBranchAddress());
return accountsDto;
}
public static Accounts mapToAccounts(AccountsDto accountsDto,
Accounts accounts)
{
accounts.setAccountNumber(accountsDto.getAccountNumber());
accounts.setAccountType(accountsDto.getAccountType());
accounts.setBranchAddress(accountsDto.getBranchAddress());
return accounts;
}
}
CustomerMapper
import com.eazybytes.accounts.dto.CustomerDto;
import com.eazybytes.accounts.entities.Customer;
public class CustomerMapper {
public static CustomerDto mapToCustomerDto(Customer customer,
CustomerDto customerDto)
{
customerDto.setName(customer.getName());
customerDto.setEmail(customer.getEmail());
customerDto.setMobileNumber(customer.getMobileNumber());
return customerDto;
}
public static Customer mapToCustomer(CustomerDto customerDto,
Customer customer)
{
customer.setName(customerDto.getName());
customer.setEmail(customerDto.getEmail());
customer.setMobileNumber(customerDto.getMobileNumber());
return customer;
}
}
So whenever you need some mapper between entity and DTO, you need to write such logic inside your projects.
Is there any automated way of doing this? Of course we have libraries that support these automatic mapping between entity and POJO Class.
2 such libraries are :
- modelmapper and
- mapstruct.
Using these two libraries, you can do the conversion between DTO to entity and entity to DTO by adding a few dependencies and writing a few lines of code.
Implementing the business logic in Service layer – Part I
This is the method we have –
@Service
@AllArgsConstructor
public class AccountsServiceImpl implements IAccountsService {
private AccountsRepository accountsRepository;
private CustomerRepository customerRepository;
/**
*
* @param customerDto - CustomerDto object
*/
@Override
public void createAccount(CustomerDto customerDto) {
}
}
Now to this method, we need to add the business logic.
@Override
public void createAccount(CustomerDto customerDto) {
Customer customer = CustomerMapper.mapToCustomer(customerDto, new Customer());
Customer savedCustomer = customerRepository.save(customer);
}
First, you need to convert the input received customerDto to Customer entity object. Using mapper class, we can convert that.
Then to save the data to the database, we can use the repository class.
You can see there are multiple save(), but since we want to save a single entity, we need to invoke the save() method.
→ The Spring Data JPA framework will take care of taking the input object, preparing an SQL statement, creating a connection with the database, executing the statement, committing the transaction, closing the connection.
So all those boilerplate code which developers use to write now will be taken care by the Spring data JPA.
NOTE – save() method, I have not written inside the CustomerRepository. It is coming from the Spring Data JPA since we extended this JpaRepository class.
So now we successfully save the customer details into the database. When this customer details is saved into the database, the very first time the customerId will be generated automatically by the Spring Data JPA framework.
So in order to know what the customerId is, we need to store the return type of the save() method into another variable called savedCustomer.
→ With the help of these customer details, I want to create a new bank account for this customer. The customer is only going to give what is his name, email and mobileNumber with his customer details. I want to create a new bank account.
For the same, we need to use the AccountsRepository and invoke the save() method. To this method, we need to pass the object of Accounts entity populated with the values like customerId, accountNumber, accountType, and branchAddress?
Private method to help with creating new account from existing customer –
/**
* @param customer - Customer Object
* @return the new account details
*/
private Accounts createNewAccount(Customer customer) {
Accounts newAccount = new Accounts();
newAccount.setCustomerId(customer.getCustomerId());
long randomAccNumber = 1000000000L + new Random().nextInt(900000000);
newAccount.setAccountNumber(randomAccNumber);
newAccount.setAccountType(AccountsConstants.SAVINGS);
newAccount.setBranchAddress(AccountsConstants.ADDRESS);
return newAccount;
}
With this method in place, we can now create an account for a given customer.
Overall the method looks like this :
@Override
public void createAccount(CustomerDto customerDto) {
Customer customer = CustomerMapper.mapToCustomer(customerDto, new Customer());
Customer savedCustomer = customerRepository.save(customer);
accountsRepository.save(createNewAccount(savedCustomer));
}
So behind the scenes, a new account will be created with the customerId that we have passed to this method, and that way we are establishing a link between a customer and the new account that is created.
Handling Exceptions
Think like someone is trying to use the same mobile number again and again. We don’t want that to happen. We want only one customer to use a single mobile number.
Creating custom exception class
Create a new package ‘exception’ and create a new class ‘CustomerAlreadyExistsException’.
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
public class CustomerAlreadyExistsException extends RuntimeException {
public CustomerAlreadyExistsException(String message) {
super(message);
}
}
So any custom exception that you want to create, it should extend the RuntimeException.
Now, on top of this custom exception class, I’m going to mention an annotation which is @ResponseStatus with the value as HttpStatus.BAD_REQUEST.
Whenever I’m trying to throw this exception in the response, my client is going to receive a status saying that 400 which represents bad requests.
Validating the input values
I need to make sure I’m validating if there is any existing customer with a given mobileNumber, which means I need to execute a query on my database by passing a mobileNumber and to identify if there is any existing record.
Can we do that with the help of this CustomerRepository?
What is the problem ?
We declared customerId as a primary key field inside our entity, so this CustomerRepository class will have a find() with the help of Id, which is customerId.
My framework can only provide a find() based upon my primary key values, which is customerId. That’s why you can only invoke findBy() method which will accept the customerId as an input because customerId is a primary key that we have defined.
But are we receiving the customerId inside the request when they’re trying to create the account for the very first time? Of course we won’t receive that.
That’s why we need to write a different method that will help us to query the database based upon a mobileNumber.
Custom Queries
Go to the repository interface – we need to define an abstract method by following some naming convention and the Spring Data JPA framework is going to write the logic of fetching the record based upon the column that we have mentioned.
I’m going to create a method that is going to return Optional of Customer, Option<Customer>, because for a given mobileNumber there can be a customer or they cannot be any customer. That’s why I need to make sure I’m using optional of customer.
So now I need to write a method with the name findByMobileNumber(). To this method I need to pass the input which is the mobileNumber itself.
So whenever I write this method inside my repository interface, my spring data JPA framework will take care of fetching the record based upon a mobileNumber.
So here you may have a question like how your spring data JPA knows that you are trying to query based upon a single column which is mobileNumber.
@Repository
public interface CustomerRepository extends JpaRepository<Customer, Long> {
Optional<Customer> findByMobileNumber(String mobileNumber);
}
If you see the naming convention findBy<columnName>, whenever we are using findBy we’re telling it to the spring data JPA framework, we are trying to run a select query using the column mobileNumber.
So this field name mobileNumber has to match with what you have mentioned inside your POJO entity class.
Implementing the business logic in Service layer – Part II
In this case, we are validating and throwing an exception if the customer already exists with the given mobile number.
@Override
public void createAccount(CustomerDto customerDto) {
Customer customer = CustomerMapper.mapToCustomer(customerDto, new Customer());
Optional<Customer> existingCustomer = customerRepository.findByMobileNumber(customerDto.getMobileNumber());
if(existingCustomer.isPresent()) {
throw new CustomerAlreadyExistsException
("Customer already exists with given mobile number : " + customerDto.getMobileNumber());
}
Customer savedCustomer = customerRepository.save(customer);
accountsRepository.save(createNewAccount(savedCustomer));
}
We need to validate if there is an existing customer available inside the database or not.
If there is a record, then I want to throw a CustomerAlreadyExistingException with a message.
Who will catch the exception? Inside controller method, but that is not recommended.
GlobalExceptionHandling logic
To handle the exceptions in REST APIs, the best standard is, you need to write a GlobalExceptionHandlingLogic.
Whenever you want to write some global exception logic, we need to make sure you are mentioning the annotation @ControllerAdvice.
import org.springframework.web.bind.annotation.ControllerAdvice;
@ControllerAdvice
public class GlobalExceptionHandler {
}
Using this annotation, we are telling to the Spring Boot framework, whenever an exception happens in any of my control, not only AccountsController, in future I may write different controller, in all such controllers, if any exception happens, please invoke a method that I’m going to write inside this class.
We write a new method – handleCustomerAlreadyExistingException()
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(CustomerAlreadyExistsException.class)
public ResponseEntity<ErrorResponseDto> handleCustomerAlreadyExistsException
(CustomerAlreadyExistsException exception,
WebRequest webRequest)
{
ErrorResponseDto errorResponseDTO = new ErrorResponseDto(
webRequest.getDescription(false), // to get API information
HttpStatus.BAD_REQUEST,
exception.getMessage(),
LocalDateTime.now()
);
return new ResponseEntity<>(errorResponseDTO, HttpStatus.BAD_REQUEST);
}
}
This method is going to handle the exception which is CustomerAlreadyExistException.
And we should also pass this WebRequest parameter to this method because inside our ErrorResponseDto we decided to send, what is the path that my client application is trying to invoke?
So you can see using this WebRequest, I’m trying to getDescription. When I pass this false value to this getDescription, I’ll only get the API information.
We send back ResponseEntity object – It accepts the body as a very first parameter and the second parameter is the HttpStatus code.
@ExceptionHandler annotation
How my Spring Boot framework will know that this method has to be invoked whenever there is an exception CustomerAlreadyExistsException is being thrown.
This annotation we need to pass what is the exception name that this method is supposed to handle.
@ExceptionHandler(CustomerAlreadyExistsException.class)
Updating the Controller class
We need to go to the controller class. Here first I need to do the autowiring of my service class to these AccountsController.
This will do the field level auto wiring. To use the recommended approach of constructor autowiring, let’s use @AllArgsConstructor annotation on the class.
With this Lombok annotation, @AllArgsConstructor, AccountsController is going to have a constructor that accepts the IAccountService as the input parameter.
Like I said before, whenever you have only a single constructor then you don’t need to mention @Autowired annotation anywhere, either inside your constructor or inside your class.
Complete Controller class for Create operation
@RestController
@RequestMapping(path = "/api", produces = {MediaType.APPLICATION_JSON_VALUE})
@AllArgsConstructor
public class AccountsController {
// @Autowired
private IAccountsService service;
@PostMapping("/create")
public ResponseEntity<ResponseDto> createAccount(@RequestBody CustomerDto customerDto) {
service.createAccount(customerDto);
ResponseDto responseDto =
new ResponseDto(AccountsConstants.STATUS_201,
AccountsConstants.MESSAGE_201);
return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
// return ResponseEntity
// .status(HttpStatus.CREATED) // this will go in Response HEADER
// .body(new ResponseDto(AccountsConstants.STATUS_201,
AccountsConstants.MESSAGE_201));
}
}
Build & Deploy
Build the application and deploy.
My application started successfully at port 8080.
As a next step, we can go and send a request to my create API and validate if my account is successfully created or not.
For the same, I came to the postman. Postman is a tool using which we can invoke REST APIs easily.
Send the POST request –
Here, the JSON fields should match with the fields you have in your Entity class.
If the fields are matching, then the Spring Boot is going to take care of converting this JSON to the DTO object. How is this going to happen ?
Inside the Spring Boot, there are Jackson libraries, with the help of those Jackson libraries, the conversion from Json to Pojo object. And similarly, when we return the Pojo object in the Response, the same will be converted to Json and sent back to the client.
When you send the request, this is the response.
So let’s try to invoke the operation again with the same mobileNumber. We should get a business exception.
Check the data in our internal H2 database.