Hello, Habr! I present to your attention an amateur translation of the article “Rethinking the Java DTO” by Stephen Waterman , where the author considers an interesting and non-standard approach to using DTOs in Java.
I spent 12 weeks in the Scott Logic graduate training program, working with fellow alumni on an internal project. And there was a moment that stalled me more than others: the structure and style of writing our DTOs. This caused a lot of controversy and discussion throughout the project, but in the end I realized that I like using DTOs.
This approach is not the only correct solution, but it is quite interesting and great for development using modern IDEs. Hope the initial shock wears off and you enjoy it too.
What is DTO (Data Transfer Object)?
Often, in client-server applications, data on the client ( presentation layer ) and on the server ( domain layer ) are structured differently. On the server side, this gives us the opportunity to comfortably store data in the database or optimize the use of data for the sake of performance, while at the same time engage in “user-friendly” display of data on the client, and, for the server side, we need to find a way to translate data from one format to another. Of course, there are other application architectures, but we'll focus on the current one as a simplification. DTO-like objects can be used between any two data presentation layers.
DTO — value-object , , . DTO , (Request) , (Response). , Spring.
, endpoint DTO :
// Getters & Setters, ,
public class CreateProductRequest {
private String name;
private Double price;
}
public class ProductResponse {
private Long id;
private String name;
private Double price;
}
@PostMapping("/products")
public ResponseEntity<ProductResponse> createProduct(
@RequestBody CreateProductRequest request
) { /*...*/ }
DTO?
-, , DTO. .
- , DTO.
- JSON, !
. DTO , , , (decoupling) , .
, DTO . DTO API .
API, . (endpoint) . , . price “ ”, price . API , - , .
DTO . DTO , , API . DTO “ ”, — , .
DTO, , .
!
, . . , .
, -. , . Double, BigDecimal.
public enum ProductDTO {;
private interface Id { @Positive Long getId(); }
private interface Name { @NotBlank String getName(); }
private interface Price { @Positive Double getPrice(); }
private interface Cost { @Positive Double getCost(); }
public enum Request{;
@Value public static class Create implements Name, Price, Cost {
String name;
Double price;
Double cost;
}
}
public enum Response{;
@Value public static class Public implements Id, Name, Price {
Long id;
String name;
Double price;
}
@Value public static class Private implements Id, Name, Price, Cost {
Long id;
String name;
Double price;
Double cost;
}
}
}
, enum , ProductDTO. , DTO , (Request) , (Response). endpoint Request DTO Response DTO . Response DTO, Public
Private
.
. - — , . . , @NotBlank
DTO .
DTO . @Value
Lombok , .
“ !”
. .
enum ! namespace-, .. DTO ProductDTO.Request.Create
. “” , ;
enum. () ! namespace- DTO, IDE . , , new ProductDTO()
new Create()
. , .
— ! . , , .
. , . Lombok . , , DTO . , java . , .
()
DTO. ?
. API , . DTO — IDE . :
@Value public static class PatchPrice implements Id, Price {
String id; // Long;
Double prise; // price
}
PatchPrice is not abstract and does not override abstract method getId() in Id
PatchPrice is not abstract and does not override abstract method getPrice() in Price
, , , endpoint .
DTO . . :
private interface Cost {
/**
* The amount that it costs us to purchase this product
* For the amount we sell a product for, see the {@link Price Price} parameter.
* <b>This data is confidential</b>
*/
@Positive Double getCost();
}
DTO , .
DTO, . , API, , . , , .
&
: . 4 , , DTO . , “” c DTO. , , . , .
, DTO. @Value public static class [name] implements
, . , IDE . ! DTO .
, DTO . . . ctrl + q IntelliJ .
, .. . DTO — , .
, , . , , :
markup = (sale_price - cost_price) / cost_price
java, :
public static <T extends Price & Cost> Double getMarkup(T dto){
return (dto.getPrice() - dto.getCost()) / dto.getCost();
}
T
, . dto
Price
Cost
— , Public
(.. Cost
). , dto (). .
, DTO. :
- API .
- .
- , , !
PS Thank you for reading my first post on Habré to the end. I would be glad to any criticism regarding the translation, because I had to deviate a little from the original due to lack of knowledge and experience.