Reputation: 139
I am developing my first RESTful API from scratch and with Spring Boot.
I have already created the endpoints, models and JPA repositories for "standalone" entities. But now that I started linking them together and after doing some research I got to the conclusion that I may have to create DTOs. I don't think everytime I'm creating a new Order
with a POST request I should make the client send the whole Customer
and Employee
objects inside the request as nested objects of Order
(if I am also wrong in this please let me know). I am thinking about creating a DTO by just replacing the class relations with just IDs.
This is how my entity is currently defined:
@Data
@Entity
@Table(name = "Orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@NotBlank
@NotNull
private String description;
@NotBlank
@NotNull
private Status status;
@NotNull
@ManyToOne
@JoinColumn(foreignKey = @ForeignKey(name = "employee_id_fk"))
private Employee employee;
@NotNull
@ManyToOne
@JoinColumn(foreignKey = @ForeignKey(name = "customer_id_fk"))
private Customer customer;
protected Order() {}
public Order(String description) {
this.description = description;
this.status = Status.IN_PROGRESS;
}
}
And my endpoint (this is what I must change):
@PostMapping("/orders")
ResponseEntity<EntityModel<Order>> createOrder(@Valid @RequestBody Order order) {
order.setStatus(Status.IN_PROGRESS);
Order newOrder = repository.save(order);
return ResponseEntity
.created(linkTo(methodOn(OrderController.class).getOrder(newOrder.getId())).toUri())
.body(assembler.toModel(newOrder));
}
Now, how should I validate the requests with this format?
Previously, as you can see, I would simply use @Valid
and it would automatically get validated when the endpoint is called against the Order
model. However, if I create the DTO, I would have to validate the DTO with the same methodology and duplicate all the annotations from its model (@NotNull
, @NotBlank
, etc.). Maybe I should validate the entity model after mapping it from the DTO but I don't know how straightforward that would be and whether that is a good practice of validating requests. I also can't remove the validations from the entity model because I'm using Hibernate to map them to tables.
Upvotes: 2
Views: 1598
Reputation: 3049
Great questions!
I don't think everytime I'm creating a new Order with a POST request I should make the client send the whole Customer and Employee objects inside the request as nested objects of Order (if I am also wrong in this please let me know).
You're right. It's not because we can save bits and bytes (as it may look like), but because the lesser information you can ask from the client, the better the experience he/she would get (whether it's an external integrator or front-end/back-end application within the same company). Fewer amounts of data to encompass = easier to comprehend and less room for an error. It also makes your API cleaner from the design perspective. Is it possible to process your request without the field? Then it shouldn't be in your API.
Now, how should I validate the requests with this format? Previously, as you can see, I would simply use @Valid and it would automatically get validated when the endpoint is called against the Order model. However, if I create the DTO, I would have to validate the DTO with the same methodology and duplicate all the annotations from its model (@NotNull, @NotBlank, etc.).
You can also use @Valid
to kick in validation for DTO inside the controller within the method mapped to endpoint. But as you mentioned correctly, all validated fields within DTO should be annotated with @NotNull, @NotBlank, etc. As a solution, to the "duplication" problem, you can create a base class and define all validations in there and inherit DTO and Entity from it. But please, don't do that!
Having the same fields and validation rules within DTO and Enity isn't considered duplication since they are separate concepts and each one of serves its specific purpose within its layer (DTO - top tier, Entity - most often lowest, Data tier). There are a lot of examples demonstrating it (e.g. here and here)
Maybe I should validate the entity model after mapping it from the DTO but I don't know how straightforward that would be and whether that is a good practice of validating requests.
It's a best practice to validate the request and a lot of projects are following it. In your example, it's very straightforward (direct mapping from DTO to Entity), but very often you would have a service layer that does some business logic before handing it off to a data layer (even in your example I recommend moving out your code from controller to a service layer). You don't want malformed request pass beyond the controller to handle it later with excessive if statements, null checks (that leads to a defensive code that's hard to follow and it's also error-prone).
Another note: you shouldn't sacrifice client experience and tell them or force yourself to add two more fields because it allows having one Object serving as DTO and Entity and simplifies development.
The last note: To map fields from DTO to Entity you can use one of the object mapper libraries.
Upvotes: 3