TL;DR
- Les Data Transfert_Object sont utilisés en I/O. Permet de simplifier les échanges.
DTO
C'est un objet qui permet de cacher au niveau de l'API le vrai contenu des entités. Ils sont gérés
par les Controllers. Un DTO vient toujours avec un Mapper. Les record
sont particulièrement adaptés à cela.
Example
- Entity:
@Entity
public class Person {
private String name;
private String gender;
private String address;
/* Getters / Setters */
}
- DTO (souvent un record mais attention à la version de Spring):
public record PersonDto(
String name,
String gender
) {}
- Mapper (soit un composant Spring, soit une classe "static"):
@Component
public class PersonDtoMapper {
public PersonDto fromModel(Person p) {
return new PersonDto(
p.getName(),
p.getGender()
);
}
}
JsonView (peu utilisé)
@JsonView
définit un ensemble de propriétés qui seront exportées en JSON. On peut annoter les
propiétés avec une vue pour qu'elle soit associée à celle-ci. Quand un controller mapping est annoté
avec une @JsonView
, il utilisera les gens qui fonctionnent pour produire la vue associé en JSON.
On peut utiliser l'héritage pour étendre le scoe des vues.
Example
- Entity:
@JsonView(Views.LIGHT.class)
public class Person {
@JsonView(View.ID.class)
private Long id;
private String name;
private String gender;
private String address;
public static class Views {
public interface ID {}
public interface LIGHT extends ID {}
public interface PAGE extends LIGHT {}
public interface FULLL extends PAGE {}
}
}
- Method call:
@GetMapping
@JsonView(Person.Views.LIGHT.class)
public Page<Person> fetchPage() {
return personService.findAll();
}
JsonIdentity
JsonIdentityInfo
Permet de dédupliquer les champs envoyés:
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Person { /* ... */}
JsonTypeInfo
Ajoute un field type pour les cas où il y a du polymorphisme. Cela permet de désérialiser dans le front.
- Par exemple, avec une classe mère:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, defaultImpl = Product.class)
@JsonSubTypes({
@JsonSubTypes.Type(value = Book.class),
@JsonSubTypes.Type(value = VideoGame.class),
@JsonSubTypes.Type(value = Phone.class)
})
public class Product { /* ... */ }
- Dans la classe fille:
@JsonTypeName("book")
public class Book extends Product { /* ... */ }
Resources & HTTP statuses
Status codes
- 2XX
- 200 OK
- 201 CREATED
- 3XX
- 4XX:
- 400: Bad Request
- 401: Unauthorized
- 403: Forbidden
- 404: Not Found
- 406: Not acceptable
- 409: Conflict
- 410: Gone
- 5XX:
- 500: Internal Server Error
In Spring
@ResponseStatus(HttpStatus.status)
<type> <method_name>(){}
Exception Handler
Cf here
Possible de faire une entity pour renvoyer des erreurs. Ça permet de controller la granularité.
- Exception avec l'annotation
@ResponseStatus
sont retournés avec le bon http code. - Exception Handler pour controlleur unique:
public class Controller {
@ExceptionHandler({ Exception1.class, Exception2.class})
public void handler() {
// Handle Here
}
}
DefaultHandlerExceptionResolver
définit par défaut à partir de Spring.3ResponseStatusExcetionREsolver
: permet de redéfinir avec un@ResponseStatus
le code d'erreur des exceptions. Il faut que la classe avec le@ResponseStatus
extendsRuntimeException
.@ControllerAdvice
permet de faire un handler global:
@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler(value
= { IllegalArgumentException.class, IllegalStateException.class })
protected ResponseEntity<Object> handleConflict(
RuntimeException ex, WebRequest request) {
String bodyOfResponse = "This should be application specific";
return handleExceptionInternal(ex, bodyOfResponse,
new HttpHeaders(), HttpStatus.CONFLICT, request);
}
}
- For REST use
@RestControllerAdvice
instead.
Reminder:
-
DO:
- If you need an error code/message, put it in the exception at the time you throw it
- Use
@ControllerAdvice
or@RestControllerAdvice
to handle all general purpose exceptions - Box all exceptions into a single
ApiException
with a uniformed shape
-
DON'T:
- Try-catch business-level exception everywhere in your controllers, to box them into
ApiException
with error message. - Write your own custom exception where
java.lang
orjava.util
has already a standard exception for the same purpose eg: Do not writeNotFoundException
, use java.util.NoSuchElementException. - Let your API output exceptions of various shape
- Try-catch business-level exception everywhere in your controllers, to box them into
Hateoas
HAL - Hypertext Application Language
- Import du package suivant:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
- Pour mettre un lien dans le header location, il faut modifier le code Java avec:
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;
public ResponseEntity<Customer> create(/* Args */) {
Customer c = createCustomer();
URI uri = linkTo(methodOn(CustomerApi.class).getCustomer(customer.getId())).toUri();
return ResponseEntity.created(uri).body(customer);
}
- Pour rajouter les liens dans le corps du Json, il faut utiliser:
public ResponseEntity<Customer> create(/* Args */) {
Customer c = createCustomer();
Link selfLink = linkTo(methodOn(CustomerApi.class).getCustomer(customer.getId())).withSelfRel();
Link cartLink = linkTo(methodOn(CartApi.class).getCart(customer.getId(), customer.getCart().getId())).withRel("cart");
return ResponseEntity.created(selfLink.toUri()).body(EntityModel.of(customer, selfLink, cartLink));
}
- On peut utiliser le
RepresentationModelAssembler
pour créer les liens des entités automatiquement (comme les serializers et les generators).
OpenAPI (ex Swagger)
- To generate the OpenAPI specification, we have to add the following packages:
<!-- Spring OpenAPI doc -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!--support for hateoas -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-hateoas</artifactId>
<version>${springdoc.version}</version>
</dependency>
<!-- support for Pageable-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-data-rest</artifactId>
<version>${springdoc.version}</version>
</dependency>
- The Bean should be added to the configuration:
OpenAPI openApi() {
Contact contact = new Contact();
contact.setName("Étienne Marais");
contact.setEmail("[email protected]");
return new OpenAPI().info(new Info()
.contact(contact)
.title("Takima-store Backend API")
.license(new License().name("ISC")));
}
- The API is available on:
- These annotations can be used to be more user friendly:
@Parameter
:@Schema
:@Content
:@Tag
: Marque une classe comme une ressource swagger@Operation
: Décrit une opération ou une méthode HTTP spécifique pour le chemin en question@ApiResponse
: Décrit une réponse possible à l'opération