What are DTOs and why use them?
Recently I started my Java Spring boot journey and on this journey I want to write out articles sharing the experiences and knowledge I have received. This is the first article among them! Please let me know any suggestions or recommendations you have!
What is it?
Data Transfer Objects (DTOs) are basically pieces of data you share between the client and API, or between the service layer and repository layer.
What pieces of information are shared?
It depends on what we are willing to share between the two layers. E.g. to get a user from the database, obviously we are not willing to share the password or any other sensitive information like the date of birth, residential information etc. So we need to create a User DTO that includes everything except these sensitive information. Now this is only one side of the story, DTOs used from the backend to the frontend. How about from the frontend to the backend?
Why use DTOs while transferring from frontend to the backend?
We don’t want users, not even admins to manually change or create some pieces of information like the createdAt, updatedAt. These are time sensitive information which should never be mishandled or handled manually.
// For the sake of simplicity and understanding,
// I removed all the annotations, but usually we include
// the nullable=false annotation to avoid null entries in the required field
public class Order {
// orderType, payment method, tax and other amounts related fields
//not shown for simplicity
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime confirmedAt;
private LocalDateTime preparingAt;
private LocalDateTime readyAt;
private LocalDateTime servedAt;
private LocalDateTime paidAt;
private LocalDateTime cancelledAt;
@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
}Let’s say I have this Order entity in my backend system and I have sensitive pieces of data- servedAt, createdAt, updatedAt, that only can be generated by the system (@PrePersist and @PreUpdate ensures the automation before inserting into the database) and not allowed handling manually to anyone. So I create an OrderDTO class which solely contains the information I want my users to take input from. I will not let my admin or cashier to input these fields.
public class OrderCustomerDTO {
private String orderId;
private String paymentMethod;
private OrderType orderType;
private Integer tableNo;
private BigDecimal subAmount;
private BigDecimal taxAmount;
private BigDecimal totalAmount;
private BigDecimal discountAmount;
private BigDecimal serviceChargeAmount;
private List<OrderItemDTO> items;
private UserWhoOrderedDTO orderedBy;
}In the best case, I can only send these fields as DTOs for only getting them, not setting them. Yes, using DTOs I can let users know which type of data they should be able to solely get as well as solely set! I will only show them or get from them customized parts of my entity, via post and get http requests catering to different methods for showing and receiving!
@GetMapping("/{orderId}/user-receipt")
public ResponseEntity<OrderCustomerDTO> getCustomerReceipt(@PathVariable UUID orderId){
Order order= orderService.getOrder(orderId);
return ResponseEntity.ok(orderMapper.toCustomerOrderDTO(order));
}And this is how I do so, I get my order from the database and in the Order controller class, I simply use a get method, getCustomerReceipt that allows the Customer to get the pieces of data that I want to show the customer i.e. convert the database order object into OrderCustomerDTO object and then show them!
What are the benefits that I get besides hiding my sensitive information?
- Entity fields Deprecation
public class Order {
// All previous fields as they are
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
//removed confirmedAt, preparingAt etc fields
private LocalDateTime paidAt;
private LocalDateTime cancelledAt;
@PrePersist
protected void onCreate() {
this.createdAt = LocalDateTime.now();
this.updatedAt = LocalDateTime.now();
}
@PreUpdate
protected void onUpdate() {
this.updatedAt = LocalDateTime.now();
}
}Let’s say we only have all the necessary fields for today. In future if our repository has to change and include any extra sensitive information which we may not want the user to see or input, we do not have to change the frontend fields! We can keep our DTO as it is! Or say we want to remove the time sensitive unnecessary information e.g. preparingAt, confirmedAt may be deprecated and the cashier might not be so interested in this field anymore, we simply remove the field from the entity (but not database yet since old orders might still require it).
Get Ishmaam’s stories in your inbox
Join Medium for free to get updates from this writer.
We do not yet need to handle these changes in the DTO since old orders still have them but in the frontend, the API object we receive might not always have them! To prevent the code breaking, we simply ask if those deprecated fields have null values or not!
//INITIAL DTO
public class OrderCashierDTO {
// orderType, payment method, tax and other amounts related fields
//not shown for simplicity
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime confirmedAt;
private LocalDateTime preparingAt;
private LocalDateTime readyAt;
private LocalDateTime servedAt;
private LocalDateTime paidAt;
private LocalDateTime cancelledAt;
}
//NEW DTO, Frontend just receives the same object
//just without these fields
public class OrderCashierDTO {
// orderType, payment method, tax and other amounts related fields
//not shown for simplicity
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
//removed confirmedAt, preparingAt etc fields
private LocalDateTime paidAt;
private LocalDateTime cancelledAt;
}In the frontend:
const CashierOrderData= await axios.get('api/orders/{orderId}/cashier-details');
const createdAt= CashierOrderData.createdAt;
const paidAt= CashierOrderData.paidAt;
//But no more confirmedAt, preparingAt
const confirmedAt= CashierOrderData.confirmedAt ?? null
//Simply said, if confirmedAt still exists (which it does for old users use it,
//otherwise use null!2. Different views for different types of users
Now here’s comes the cherry on top, let’s say Cashier and Customer want to see different views of the same order. How? Customer does not need to care about what time it was served, what time it was prepared, how long it took, what and how many ingredients were used! Whereas, it is indeed the cashier’s headache! Even though it is the same order, we don’t want both users to see the same view! Isn’t it beautiful that we can customize and create CustomerOrderDTO just to filter out the time and ingredients information whereas CashierOrderDTO just to fetch most of the data, if not all, needed about the order?
public class OrderCustomerDTO {
private String orderId;
private String paymentMethod;
private OrderType orderType;
private Integer tableNo;
private BigDecimal subAmount;
private BigDecimal taxAmount;
private BigDecimal totalAmount;
private BigDecimal discountAmount;
private BigDecimal serviceChargeAmount;
private List<OrderItemDTO> items;
private UserWhoOrderedDTO orderedBy;
}
public class OrderCashierDTO {
private String orderId;
private String paymentMethod;
private OrderType orderType;
private Integer tableNo;
private BigDecimal subAmount;
private BigDecimal taxAmount;
private BigDecimal totalAmount;
private BigDecimal discountAmount;
private BigDecimal serviceChargeAmount;
private List<OrderItemDTO> items;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
private LocalDateTime confirmedAt;
private LocalDateTime preparingAt;
private LocalDateTime readyAt;
private LocalDateTime servedAt;
private LocalDateTime paidAt;
private LocalDateTime cancelledAt;
}3. Less number of remote procedure calls
A great perk of this feature is, let’s say our Users and their contact were separated into two classes from the beginning, but suddenly the need for calling Users and their contacts together became higher! This method needs to separately call both methods getUser() and getContact(). These RPCs are increasing costs to our database. How about creating a separate DTO where fields of these two classes are merged together so that whenever I need them together, I can simply call the method getUserContact() to get the users and contact as single DTO, UserContactDTO?
public class UserContactDTO(){
private String username;
private String email;
private String contactNo;
private String residence;
}We will get to know about these in details when I talk about Mapper Struct in the next article!
Conclusion
That’s what all I learned about DTOs so far. In my next article I am willing to share about the Mapper Struct that we use to map the DTOs to the entities and from entities to DTOs in the backend. Till then, wish me luck! See you in the next article. Thank you for reading so far 🙏









