DEV Community

Ez Pz Developement
Ez Pz Developement

Posted on • Originally published at ezpzdev.Medium on

Data Transfer Objects (DTOs): A Comprehensive Guide

Thumbnail image for the post Data Transfer Objects (DTOs): A Comprehensive Guide

Introduction

Imagine 🤔✨ having a complex object or entity with countless properties and fields. But wait! In certain situations, not all of that data needs to be transferred or processed.

Welcome ✨, brave adventurer, to the realm of Data Transfer Objects (DTOs) 📦! These magical constructs allow you to choose precisely the data you seek from the vast depths of complexity.

Alright, let’s transition back to our world! So, what exactly are DTOs?

For instance, let’s consider an e-commerce application. When displaying a list of products on a webpage, you may only need to transmit basic information like the product name, price, and image URL. In this scenario, instead of transferring the entire product object with all its associated data (e.g., reviews, descriptions, status), you can create a ProductDTO that includes only the essential fields for displaying the product list efficiently.

Purpose of Data Transfer Objects (DTOs) in backend development

DTOs are used to encapsulate and transfer data between different layers of an application :

  • For example, when data needs to be sent from the client (such as a web browser) to the server, or vice versa, DTOs provide a structured way to package and transfer that data. The client can create a DTO, populate it with the necessary data, and send it to the server. The server, in turn, can receive the DTO, extract the relevant information, and process it accordingly.
  • By using DTOs, developers can define and control the data that is sent and received.
  • Minimizing unnecessary data transfer (reducing overhead, minimize bandwidth usage, reduce latency which lead to improvment in performance).
  • Reducing the risk of exposing sensitive information.
  • Enable the decoupling of the API contract from the internal representations, allowing for flexibility and evolution of the backend system without impacting external consumers.

Decoding DTOs: Unveiling Their Definition and Distinctions from Domain Entities and Database Models

An image showing Decoding DTOs Unveiling Their Definition and Distinctions from Domain Entities and Database Models

Let’s break down the table:

  1. Purpose : DTOs serve the purpose of facilitating efficient data transfer and communication between different components or layers of an application. Data models represent the structure and relationships of the underlying database. Domain entities capture the business logic and behavior of the application.
  2. Data Subset : DTOs contain a subset of relevant data needed for specific use cases or communication scenarios. Data models encompass the complete set of data stored in the database. Domain entities also hold the complete set of data related to the business logic.
  3. Abstraction : DTOs provide a layer of abstraction, separating the internal data representations from external interactions. Data models and domain entities do not necessarily involve abstraction as they directly represent the underlying data or business logic.
  4. Immutability : DTOs can be designed as immutable objects, ensuring that the transferred data remains consistent and unmodifiable. Data models and domain entities are typically mutable and subject to modifications.
  5. Transformation : DTOs involve data transformation or conversion to adapt the data from one format to another, facilitating interoperability between different components or systems. Data models and domain entities do not inherently involve transformation as they represent the original data structure and business logic.

The Creation of DTOs

The process of creating DTO classes or structures in a backend application involves the following steps:

  1. Identify the Data Subset : Determine the specific subset of data that needs to be transferred or communicated between different parts of the application.
  2. Design the DTO Class/Structure : Create a DTO class or structure that represents the identified subset of data. The class/structure should contain properties that mirror the fields in the subset, with appropriate data types.
  3. Map Data to DTO : Implement a mapping mechanism to populate the DTO object with data from the source. This could involve manually assigning values from the source object or using mapping libraries/tools to automate the process.
  4. Use DTO for Data Transfer : Pass the DTO object between different components or layers of the application, transferring the necessary data in a standardized format.

Here is an example of code showing the utilization of DTOs

class ProductDTO {
  constructor(id, name, price, description) {
    this.id = id;
    this.name = name;
    this.price = price;
    this.description = description;
  }

  getFormattedPrice() {
    return `$${this.price.toFixed(2)}`;
  }

  static fromProductEntity(productEntity) {
    return new ProductDTO(
      productEntity.id,
      productEntity.name,
      productEntity.price,
      productEntity.description );
  }
}

// Usage
const productEntity = {
  id: 1,
  name: 'iPhone ',
  price: 999,
  description: 'The latest iPhone model.',

};

const product = ProductDTO.fromProductEntity(productEntity);
console.log(product.getFormattedPrice());
Enter fullscreen mode Exit fullscreen mode

In this example, the ProductDTO class represents a product with properties for id , name , price , and description. It has a constructor that takes these properties as arguments and assigns them to the corresponding class properties.

The getFormattedPrice() method is defined within the class. It returns the formatted price of the product by using the toFixed() method to round the price property to 2 decimal places and prepending it with the dollar sign.

The fromProductEntity() method is a static method that takes a productEntity object as an argument. It creates a new instance of the ProductDTO class and initializes its properties with the corresponding properties from the productEntity object.

In the usage example, a productEntity object is created with sample values for id , name , price , and description. The fromProductEntity() method is then called to create a new ProductDTO instance based on the productEntity. Finally, the getFormattedPrice() method is called on the product object to retrieve the formatted price and log it to the console.

Mapping Techniques for DTOs and Domain Entities/Models:

There are many ways /techniques for mapping , lets explore some of them

  • Manual Mapping : Writing custom code to map between DTOs and domain entities/models. Provides control and customization but can be time-consuming and error-prone for complex mappings.
  • Mapping Libraries like AutoMapper : Utilizing libraries like AutoMapper for automated and convention-based mapping. Reduces manual mapping code, improves readability, and simplifies maintenance. Requires initial setup and configuration.
  • Object-Relational Mapping (ORM) Frameworks: Leveraging ORM frameworks such as Entity Framework ( EF ) or *Hibernate … *. These frameworks handle mapping between domain entities and the database. They automate mapping, handle complex scenarios, and provide additional features like lazy loading and transaction management

Use Cases and Benefits (Node js example)

  • User Registration : When a user registers in your Node.js application, you can use a UserDTO (User Data Transfer Object) to encapsulate the user’s registration data, such as username, email, and password. The UserDTO allows you to validate the data, apply any necessary transformations or sanitization, and pass it to the appropriate services for further processing.
// UserDTO.js
class UserDTO {
  constructor(username, email, password) {
    this.username = username;
    this.email = email;
    this.password = password;
  }
}

// UserService.js
class UserService {
  registerUser(userDTO) {
    // Validate and process user registration data
    // Save user to the database
    // Perform any additional operations
  }
}

// Usage in Node.js app
const userDTO = new UserDTO('john', 'john@example.com', 'password123');
const userService = new UserService();
userService.registerUser(userDTO);
Enter fullscreen mode Exit fullscreen mode
  • API Responses: When responding to API requests, you can use DTOs to structure and control the data sent back to the clients. For example, you may have a ProductDTO to represent the data returned when querying a product. The ProductDTO allows you to select specific fields, exclude sensitive information, and ensure consistent data formatting across different API endpoints.
// ProductDTO.js
class ProductDTO {
  constructor(id, name, price) {
    this.id = id;
    this.name = name;
    this.price = price;
  }
}

// API Endpoint
app.get('/products/:id', (req, res) => {
  // Fetch product data from the database
  const product = getProductById(req.params.id);

  // Map product data to ProductDTO
  const productDTO = new ProductDTO(product.id, product.name, product.price);

  // Send the productDTO as the API response
  res.json(productDTO);
});
Enter fullscreen mode Exit fullscreen mode
  • Database Operations: DTOs can be useful when interacting with the database in a Node.js app. For instance, you might use a DatabaseRecordDTO to represent a specific record retrieved from the database, allowing you to manipulate and transform the data before presenting it to the user or passing it to other parts of your application.
// DatabaseRecordDTO.js
class DatabaseRecordDTO {
  constructor(id, data) {
    this.id = id;
    this.data = data;
  }
}

// DatabaseService.js
class DatabaseService {
  fetchRecordById(id) {
    // Fetch record from the database
    const record = getRecordById(id);

    // Map and transform the record data to DatabaseRecordDTO
    const recordDTO = new DatabaseRecordDTO(record.id, record.data);

    // Perform further operations with the recordDTO
    // ...
  }
}
Enter fullscreen mode Exit fullscreen mode

DTO Best Practices

In order to effectively work with DTOs and ensure their optimal usage, it is important to follow a set of best practices. These practices will help you leverage the full potential of DTOs and improve the overall quality of your code.

  • Simplify DTOs: Keep DTOs focused on their purpose and avoid unnecessary complexity. Limit the fields and business logic within DTOs to ensure they serve their primary role of transferring data between components.
  • Use Clear Naming: Choose descriptive names for DTOs and their properties to enhance code readability and understanding.
  • Separate Validation: Handle data validation separately from DTOs using validation libraries or dedicated validation classes
  • Document Purpose: Provide documentation for DTOs, including their purpose, usage, and any validation rules or constraints
  • Framework Independence: Ensure that DTOs remain independent of specific frameworks or libraries for improved reusability

Conclusion

In conclusion, Data Transfer Objects (DTOs) play a crucial role in backend development by facilitating the transfer of data between different layers or components of an application. They allow for efficient and controlled data communication, validation, and transformation. By keeping DTOs focused, using meaningful names, and minimizing data, we can create simple and effective DTOs

Top comments (1)

Collapse
 
adirtrichter profile image
Adir Trichter

In this article, you talked about the different components of the data structures that may be found in a system. I would want to focus on the technical part, would you say that the DTOs may or even should be based on the domain entities when possible? or should we keep them separated all the time?

For example, a domain entity such as a person should include the following fields: fName, lName, birthDate, location, etc... and a simple DTO for getting basic info about a person may only have fName and lName.

So back to the question, would you get the properties of the DTO cut from the Domain Entity or would you re-write them? I know this might sound stupid question but for usages like class-validator from JS, and other tools this can save a lot of time maintaining multiple places that when a type may change or a new validation is being used across the places can matter very much.