In this tutorial, we will introduce the Data Transfer Object pattern (DTOs) in order to increase the efficiency and security of transferring data between layers of our REST API.
A DTO (Data Transfer Object) is a simple object used to transfer data between different layers or parts of a software application. It is an object created by attributes from different entities or by fewer attributes than the actual model.
Usually, when working with models stored in databases, we have additional fields which we generate when we are creating a model.
For instance, we can leave the responsibility of generating the ID to the storage layer and avoid passing it when creating a new instance of an entity. Additionally, we might have other attributes, such as createDate or lastUpdateDate, to store information about the entity.
DTOs allow you to control exactly which fields of your domain models are exposed to the API consumers. This minimizes the risk of unintentionally exposing sensitive information and they decouple the internal representation of your business entities from the external API contract.
In this tutorial we will see how to integrate the DTO pattern in our REST API application created with Spring Framework.
You can use Spring Initializr to quickly generate a Spring Boot project with Maven or Gradle. We can use the following configurations:
You will receive a ready-to-use project. All the configurations are already written in the build.gradle file and the start ExplainjavaRestApiApplication class is already created.
The goal of this tutorial is to create a REST API application to serve the resource “Lemonade” to clients. In order to do that, we will create a multi-layer application with an in-memory repository for storage and annotations based dependency injection:
public class Lemonade {
private Long id;
private String name;
private String description;
public Lemonade() {
}
public Lemonade(String name, String description) {
this.name = name;
this.description = description;
}
// getters and setters
}
@Repository
public class LemonadeRepository {
private final Map storage = new HashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
public Lemonade save(Lemonade lemonade) {
if (lemonade.getId() == null) {
lemonade.setId(idGenerator.getAndIncrement());
}
storage.put(lemonade.getId(), lemonade);
return lemonade;
}
public Lemonade update(Long id, Lemonade updatedLemonade) {
if (storage.containsKey(id)) {
updatedLemonade.setId(id);
storage.put(id, updatedLemonade);
return updatedLemonade;
}
return null;
}
public Optional findById(Long id) {
return Optional.ofNullable(storage.get(id));
}
public List findAll() {
return new ArrayList<>(storage.values());
}
public boolean deleteById(Long id) {
return storage.remove(id) != null;
}
}
@Service
public class LemonadeService {
@Autowired
private LemonadeRepository lemonadeRepository;
public Lemonade createLemonade(Lemonade lemonade) {
return lemonadeRepository.save(lemonade);
}
public List getAllLemonades() {
return lemonadeRepository.findAll();
}
}
If we take a look closer, when we are saving an entity, we generate the ID:
@Repository
public class LemonadeRepository {
private final Map storage = new HashMap<>();
private final AtomicLong idGenerator = new AtomicLong(1);
public Lemonade save(Lemonade lemonade) {
if (lemonade.getId() == null) {
lemonade.setId(idGenerator.getAndIncrement());
}
storage.put(lemonade.getId(), lemonade);
return lemonade;
}
// the rest of the CRUD operations
}
That means, when we are creating an entity, we don’t have to specify the ID. We need to pass only the name and the description of the Lemonade. We can create a new class for that (you can check our tutorial on using Records or the Lombok Project to reduce the boilerplate code for these types of classes).
public class LemonadeDTO {
private String name;
private String description;
public LemonadeDTO() {
}
public LemonadeDTO(String name, String description) {
this.name = name;
this.description = description;
}
// getters and setters
}
Additionally, we need to add a specialized class to convert the DTO from model and the model from DTO. We can create a package called “converter” to add the DTO and the converter class.
public class LemonadeConverter {
public static Lemonade convertFromDTO(LemonadeDTO lemonadeDTO){
return new Lemonade(lemonadeDTO.getName(), lemonadeDTO.getDescription());
}
public static LemonadeDTO convertToDTO(Lemonade lemonade){
return new LemonadeDTO(lemonade.getName(), lemonade.getDescription());
}
}
Finally, we can add the Controller class, and we can use the newly created classes.
First, we add the LemonadeDTO as expected body for the POST operation, then, we convert the responses to DTO classes to decouple the internal representation of our entities from the external API.
@RestController
@RequestMapping("/api/lemonades")
public class LemonadeController {
@Autowired
private LemonadeService lemonadeService;
@PostMapping
public LemonadeDTO createLemonade(@RequestBody LemonadeDTO lemonadeDTO) {
Lemonade lemonade = lemonadeService.createLemonade(LemonadeConverter.convertFromDTO(lemonadeDTO));
return LemonadeConverter.convertToDTO(lemonade);
}
@GetMapping
public List getAllLemonades() {
return lemonadeService.getAllLemonades().stream().map(LemonadeConverter::convertToDTO).collect(Collectors.toList());
}
}
Now, when we create a test using Postman, we don’t need to pass the ID of the entity and we let the storage layer handle it.
In this tutorial, we discussed about integrating DTO pattern in a basic REST API created with Spring Framework.
Learn advanced concepts, work on real-world projects, and fast-track your journey to becoming a proficient Java developer. Start now and unlock your full potential in the world of Java programming!
Start now and unlock your full potential in the world of Java programming!
The place where you can start your Java journey.
© All Rights Reserved.