REST Api

description: Deal with REST in Java

lang: FR

TL;DR

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
public class Person {
    private String name;
    private String gender;
    private String address;

    /* Getters / Setters */
}
public record PersonDto(
    String name,
    String gender
) {}
@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

@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 {}
    }
}
@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.

@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 { /* ... */ }

@JsonTypeName("book")
public class Book extends Product { /* ... */ }

Resources & HTTP statuses

Status codes

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é.

public class Controller {
    @ExceptionHandler({ Exception1.class, Exception2.class})
    public void handler() {
        // Handle Here
    }
}
@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);
    }
}

Reminder:

Hateoas

HAL - Hypertext Application Language

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
</dependency>
    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);
    }
    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));
    }

OpenAPI (ex Swagger)

<!-- 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>
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")));
}