Code First

Una vez validado que contamos con el entorno de trabajo adecuado, y que la aplicación arranca correctamente, pasaremos a crear nuestra primera versión de Petstore Lite usando CodeFirst

An icon of a key

El código de este capítulo se encuentra en la carpeta 02-petstore-codefirst del repositorio de github

Partiendo del proyecto creado anteriormente, y como queremos proporcionar al equipo de FrontEnd una API REST válida cuanto antes, vamos a trabajar lo primero en la capa http definiendo los Controllers y los DTOs de entrada y salida.

An icon indicating this blurb contains a warning

Como el Controller creado automáticamente por Micronaut no nos interesa podemos/debemos eliminarlo.

DTOs

Creamos un DTO en el package dtos para representar el Pet que queremos crear.

 1 package com.incsteps.dtos;
 2 
 3 import io.micronaut.serde.annotation.Serdeable;
 4 import jakarta.validation.constraints.NotNull;
 5 
 6 @Serdeable
 7 public record CreatePetRequest(
 8         @NotNull String name,
 9         int age
10 ) {
11 }

La anotación @Serdeable nos permite serializar y deserializar el DTO utilizando el formato JSON facilitando su uso en las llamadas HTTP.

Así mismo, crearemos un DTO para representar el Pet creado.

 1 package com.incsteps.dtos;
 2 
 3 import io.micronaut.serde.annotation.Serdeable;
 4 
 5 @Serdeable
 6 public record Pet(
 7         Long id,
 8         String name,
 9         int age
10 ) {
11 }

Como podemos ver, el DTO de salida es muy similar al DTO de entrada, incluyendo el ID del Pet creado.

Con estos DTOs ya podemos crear el Controller para crear un nuevo Pet.

 1 package com.incsteps.http;
 2 
 3 import com.incsteps.dtos.CreatePetRequest;
 4 import com.incsteps.dtos.Pet;
 5 // resto de imports eliminados por brevedad
 6 
 7 @Controller("/pets")
 8 @Tag(name = "Pets", description = "Operaciones sobre el catálogo de mascotas")
 9 public class PetstoreController {
10 
11 }

Get Pet by ID

Por ahora vamos a definir solo el interface http para obtener un Pet por su ID pero devolviendo siempre un 404.

 1     @Get("/{id}")
 2     @Produces(MediaType.APPLICATION_JSON)
 3     @Operation(
 4             summary = "Obtener mascota por ID",
 5             description = "Devuelve los detalles de una mascota concreta dada su clave primaria."
 6     )
 7     @ApiResponse(
 8             responseCode = "200",
 9             description = "Mascota encontrada",
10             content = @Content(schema = @Schema(implementation = Pet.class))
11     )
12     @ApiResponse(
13             responseCode = "400",
14             description = "ID suministrado no válido",
15             content = @Content
16     )
17     @ApiResponse(
18             responseCode = "404",
19             description = "Mascota no encontrada",
20             content = @Content
21     )
22     public HttpResponse<Pet> get(
23             @Parameter(description = "Identificador único de la mascota", required = true)
24             @PathVariable("id")
25             @NotNull
26             @Positive Long id
27     ) {
28         return HttpResponse.notFound();
29     }

Post Pet

 1     @Post
 2     @Produces(MediaType.APPLICATION_JSON)
 3     @Operation(
 4             summary = "Registrar una nueva mascota",
 5             description = "Crea una nueva mascota en el catálogo con los datos proporcionados."
 6     )
 7     @ApiResponse(
 8             responseCode = "201",
 9             description = "Mascota creada con éxito",
10             content = @Content(schema = @Schema(implementation = Pet.class))
11     )
12     @ApiResponse(
13             responseCode = "400",
14             description = "Petición no válida (datos faltantes o erróneos)",
15             content = @Content
16     )
17     public HttpResponse<Pet> create(
18             @Parameter(description = "Datos de la mascota a crear", required = true)
19             @Body @NotNull @Valid CreatePetRequest request
20     ) {
21         return HttpResponse.badRequest();
22     }

Al igual que en el caso de obtener un Pet por su ID, por ahora vamos a devolver un bad request.

Delete Pet

De la misma forma que en el caso de obtener un Pet por su ID o crear un nuevo Pet, definiremos un método DELETE para eliminar un Pet.

An icon of a key

Puedes ver el método completo en el archivo PetstoreController.java en la carpeta 02-petstore-codefirst del repositorio de github.

Desplegando

Una vez que tenemos definido los endpoints (sin lógica de negocio implementada) procederíamos a desplegarlo. Por ahora nos bastará con ejecutarlo en local y comprobar que Micronaut genere correctamente el contrato OpenAPI a partir de los DTOs y los endpoints definidos.

$ ./gradlew run

En un navegador abrimos la URL http://localhost:8080/swagger/petstore-0.0.yml y veremos el contrato OpenAPI generado.

 1 openapi: 3.0.1
 2 info:
 3   title: petstore
 4   version: "0.0"
 5 tags:
 6 - name: Pets
 7   description: Operaciones sobre el catálogo de mascotas
 8 paths:
 9   /pets:
10     post:
11       tags:
12       - Pets
13       summary: Registrar una nueva mascota
14       description: Crea una nueva mascota en el catálogo con los datos proporcionados.
15       operationId: create
16       requestBody:
17         content:
18           application/json:
19             schema:
20               $ref: "#/components/schemas/CreatePetRequest"
21  (continua)

Iterando Code First

Como puedes ver, Micronaut ha generado a partir del código un recurso OpenAPI en formato YAML que representa el contrato de la API.

Esto nos permite documentar la API y generar clientes de cliente para consumirla. Es decir, una vez desplegada la aplicación podremos crear un MockServer para simular la API en nuestro entorno de desarrollo mientras desarrollamos la funcionalidad de negocio por separado.

UseCases

En el repositorio de ejemplo encontrarás una de las tantas posibles implementaciones. Unas implementarían arquitectura hexagonal, otras una arquitectura simple Controller-Service-Repository.

Cúal elegir (y cómo testearlas) queda fuera del alcance de este libro. Por no complicar los ejemplos y tener un proyecto completo, he optado por crear unos casos de uso como SingletonS, que puedes encontrar en la carpeta 02-petstore-codefirst/src/main/java/com/incsteps/usecases los cuales usa el Controller.