Optimize your API responses using DTOs

jarjanazy

Abdulcelil Cercenazi

Posted on April 21, 2021

Optimize your API responses using DTOs

What is it? 🧠

DTO is short for Data Transfer Object.

Why do we need it? πŸ€”

It allows us to decouple domain objects from objects we present to the client.
It helps us present customizable views of our domain objects to the client.
It helps us deliver only the necessary data to the client, thus making our responses smaller and easier to deliver.

  • As an example of this use case, there was a page in our front-end application that was taking a long time to load.
  • After investigation, we noticed that the backend was sending a response with a lot of data inside of it, thus making its processing time longer.
  • We then, applied the DTO principle and sent only the needed data thus resulting in 5x faster response time.

How to do it? πŸ› 

  • Get the business object from the persistence layer (database for example)
  • Create a new DTO object using values from the business object
  • Return the DTO to the client.

Ok, let's code a bit βš™οΈβš™οΈ

Let's say we have a domain class called Shipment🚚

import lombok.*;  
import java.util.Date;
@Getter @Setter @Builder @AllArgsConstructor  
public class Shipment {  
    private Integer id;  
    private Date shipmentDate;  
    private String productCode;  
    private String owner;  
    private String serialNumber;  
}
Enter fullscreen mode Exit fullscreen mode

A mock persistence layer πŸ—‚ (database for example) to get Shipment records

public class ShipmentRepository {  
// methods to get Shipment from database 
}
Enter fullscreen mode Exit fullscreen mode

A DTO for admins' πŸ‘·πŸΏβ€β™‚οΈ view of the Shipment

import lombok.*;  
@Getter @Setter @Builder @AllArgsConstructor  
public class Shipment_AdminDTO {  
    private Integer id;  
    private String productCode;  
    private String serialNumber;  
}
Enter fullscreen mode Exit fullscreen mode

Another DTO for users' πŸ‘·πŸΏβ€β™€οΈ view of the Shipment

import lombok.*;  
@Getter @Setter @Builder @AllArgsConstructor  
public class Shipment_UserDTO {  
    private Date shipmentDate;  
    private String productCode;  
    private String owner;  
}
Enter fullscreen mode Exit fullscreen mode

A service to get the Shipment from the repository, convert it to a DTO, and return it to the client

import lombok.AllArgsConstructor;
@AllArgsConstructor
public class ShipmentService {  
    private final ShipmentRepository shipmentRepository;  

    public Shipment_UserDTO getShipment_ForUser(Integer id){  
        return shipmentRepository.  
                getShipmentById(id).  
                map(this::getShipment_UserDTO_FromShipment).  
                orElseThrow(() -> new RuntimeException("Shipment Not Found"));  
    }  

    public Shipment_AdminDTO getShipment_ForAdmin(Integer id){  
        return shipmentRepository.  
                getShipmentById(id).  
                map(this::getShipment_AdminDTO_FromShipment).  
                orElseThrow(() -> new RuntimeException("Shipment Not Found"));  
    }  

    private Shipment_UserDTO getShipment_UserDTO_FromShipment(Shipment s){  
        return new Shipment_UserDTO(s.getShipmentDate(), s.getProductCode(), s.getOwner());  
    }  
    private Shipment_AdminDTO getShipment_AdminDTO_FromShipment(Shipment s){  
        return new Shipment_AdminDTO(s.getId(), s.getProductCode(), s.getOwner());  
    }  
}
Enter fullscreen mode Exit fullscreen mode

An example response for an admin DTO could be:

{
    "id" : "1",
    "productCode" : "VVV",
    "serialNumber" : "3XX"
}
Enter fullscreen mode Exit fullscreen mode

An example response for a user DTO could be:

{
    "shipmentDate" : "04/16/2021",
    "productCode" : "ZZZ",
    "owner" : "Jalil"
}
Enter fullscreen mode Exit fullscreen mode

Important Note πŸ‘‡πŸΌ πŸ‘‡πŸΌ

it's preferable to not return a list Of DTOs as a response.

Why?

  • Let's say in the future we would like to return another field alongside the list of DTOs, like their total count, or some other metadata.

  • In that case, we might break other people's code that is using our API and expecting a list of objects to find an object with inner fields.

  • If it had an object from the beginning, we could add fields to it without breaking code that uses our API.

A code example,

import lombok.*;  
@Getter @Setter @AllArgsConstructor  
public class Shipments_AdminDTO {  
    private final List<Shipment_AdminDTO> shipment_adminDTOs;  
}
Enter fullscreen mode Exit fullscreen mode
public Shipments_AdminDTO getShipments_ForAdmin(){  
    List<Shipment_AdminDTO> shipment_adminDTOS =  
            shipmentRepository.  
            getAllShipments().  
            stream().  
            map(this::getShipment_AdminDTO_FromShipment).  
            collect(Collectors.toList());  

    return new Shipments_AdminDTO(shipment_adminDTOS);  
}
Enter fullscreen mode Exit fullscreen mode

This tutorial utilized Lombok and Java streams, to learn more about them check out my other posts.

Code on πŸ‘¨πŸΎβ€πŸ’» GitHub.

πŸ’– πŸ’ͺ πŸ™… 🚩
jarjanazy
Abdulcelil Cercenazi

Posted on April 21, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related