Spring Boot 3 - Desarrollo API Rest: Aula 3
- Nuevas dependencias en el proyecto - Asignación de entidad JPA y creación de interfaz de `Repositorio` para esta - Utilización de Flyway para migración - Validaciones con Bean Validation, anotaciones como @NotBlank.
This commit is contained in:
parent
41f062fdd7
commit
fb7ea652e4
@ -38,6 +38,31 @@
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
<artifactId>flyway-mysql</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
@ -1,15 +1,23 @@
|
||||
package med.voll.api.controller;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import med.voll.api.medico.DatosRegistroMedico;
|
||||
import med.voll.api.medico.Medico;
|
||||
import med.voll.api.medico.MedicoRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/medicos")
|
||||
public class MedicoController {
|
||||
|
||||
// No es recomendable usar @Autowired a nivel de declaración pues genera
|
||||
// problemas al realizar pruebas unitarias
|
||||
@Autowired
|
||||
private MedicoRepository medicoRepository;
|
||||
@PostMapping
|
||||
public void registrarMedico(@RequestBody DatosRegistroMedico datosRegistroMedico) {
|
||||
System.out.println(datosRegistroMedico);
|
||||
public void registrarMedico(@RequestBody @Valid DatosRegistroMedico datosRegistroMedico) {
|
||||
medicoRepository.save(new Medico(datosRegistroMedico));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,4 +1,11 @@
|
||||
package med.voll.api.direccion;
|
||||
|
||||
public record DatosDireccion(String calle, String distrito, String cuidad, String numero, String complemento) {
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
public record DatosDireccion(
|
||||
@NotBlank String calle,
|
||||
@NotBlank String distrito,
|
||||
@NotBlank String ciudad,
|
||||
String numero,
|
||||
String complemento) {
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package med.voll.api.direccion;
|
||||
|
||||
import jakarta.persistence.Embeddable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Embeddable
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class Direccion {
|
||||
|
||||
private String calle;
|
||||
private String numero;
|
||||
private String complemento;
|
||||
private String distrito;
|
||||
private String ciudad;
|
||||
|
||||
public Direccion(DatosDireccion direccion) {
|
||||
this.calle = direccion.calle();
|
||||
this.numero = direccion.numero();
|
||||
this.complemento = direccion.complemento();
|
||||
this.distrito = direccion.distrito();
|
||||
this.ciudad = direccion.ciudad();
|
||||
}
|
||||
}
|
@ -1,8 +1,17 @@
|
||||
package med.voll.api.medico;
|
||||
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import med.voll.api.direccion.DatosDireccion;
|
||||
|
||||
public record DatosRegistroMedico(String nombre, String email, String documento,
|
||||
Especialidad especialidad, DatosDireccion direccion) {
|
||||
|
||||
}
|
||||
public record DatosRegistroMedico(
|
||||
@NotBlank String nombre,
|
||||
@NotBlank @Email String email,
|
||||
@NotBlank String telefono,
|
||||
@NotBlank @Pattern(regexp = "\\d{4,6}") String documento,
|
||||
@NotNull Especialidad especialidad,
|
||||
@NotNull @Valid DatosDireccion direccion
|
||||
) {}
|
||||
|
@ -0,0 +1,38 @@
|
||||
package med.voll.api.medico;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import med.voll.api.direccion.Direccion;
|
||||
|
||||
@Table(name="medicos")
|
||||
@Entity(name="Medico")
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@EqualsAndHashCode(of = "id")
|
||||
public class Medico {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
private String nombre;
|
||||
private String email;
|
||||
private String telefono;
|
||||
private String documento;
|
||||
@Enumerated
|
||||
private Especialidad especialidad;
|
||||
@Embedded
|
||||
private Direccion direccion;
|
||||
|
||||
public Medico(DatosRegistroMedico datosRegistroMedico) {
|
||||
this.nombre = datosRegistroMedico.nombre();
|
||||
this.email = datosRegistroMedico.email();
|
||||
this.telefono = datosRegistroMedico.telefono();
|
||||
this.documento = datosRegistroMedico.documento();
|
||||
this.especialidad = datosRegistroMedico.especialidad();
|
||||
this.direccion = new Direccion(datosRegistroMedico.direccion());
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package med.voll.api.medico;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
public interface MedicoRepository extends JpaRepository<Medico, Long> {}
|
@ -1 +1,3 @@
|
||||
|
||||
spring.datasource.url=jdbc:mysql://192.168.0.8/vollmed_api
|
||||
spring.datasource.username=alura
|
||||
spring.datasource.password=alura
|
@ -0,0 +1,16 @@
|
||||
create table medicos(
|
||||
|
||||
id bigint not null auto_increment,
|
||||
nombre varchar(100) not null,
|
||||
email varchar(100) not null unique,
|
||||
documento varchar(6) not null unique,
|
||||
especialidad varchar(100) not null,
|
||||
calle varchar(100) not null,
|
||||
distrito varchar(100) not null,
|
||||
complemento varchar(100),
|
||||
numero varchar(20),
|
||||
ciudad varchar(100) not null,
|
||||
|
||||
primary key(id)
|
||||
|
||||
);
|
@ -0,0 +1 @@
|
||||
alter table medicos add telefono varchar(20) not null;
|
@ -156,9 +156,9 @@ Representación en formato **JSON**
|
||||
|
||||
```json
|
||||
{
|
||||
“nombre” : “Mochila”,
|
||||
“precio” : 89.90,
|
||||
“descripcion” : “Mochila para notebooks de hasta 17 pulgadas”
|
||||
"nombre" : "Mochila",
|
||||
"precio" : 89.90,
|
||||
"descripcion" : "Mochila para notebooks de hasta 17 pulgadas"
|
||||
}
|
||||
```
|
||||
|
||||
@ -315,10 +315,10 @@ public final class Telefono {
|
||||
}
|
||||
```
|
||||
|
||||
Con **Record** todo ese código se puede resumir en una sola línea:
|
||||
Con **Record** el código anterior se resume en una sola línea:
|
||||
|
||||
```java
|
||||
public record Telefono(String ddd, String numero){}
|
||||
public record Telefono(String ddd, String numero) {}
|
||||
```
|
||||
|
||||
Internamente, Java transforma este registro en una clase inmutable, muy similar
|
||||
@ -327,3 +327,192 @@ al código que se muestra arriba.
|
||||
Documentación Java
|
||||
[record](https://docs.oracle.com/en/java/javase/17/language/records.html)
|
||||
|
||||
|
||||
## Agregando dependencias
|
||||
|
||||
Copiar `xml` de Spring [initializr](https://start.spring.io/), y pegar en `pom.xml`
|
||||
|
||||
Modificar `resources/application.properties`
|
||||
|
||||
```ini
|
||||
spring.datasource.url=jdbc:mysql://<URL>/vollmed-api
|
||||
spring.datasource.username=<USER>
|
||||
spring.datasource.password=<PASS>
|
||||
```
|
||||
|
||||
Recomendaciones [12 Factor App](https://12factor.net/es/), que define las 12
|
||||
mejores prácticas para crear aplicaciones modernas, escalables y de mantenimiento
|
||||
sencillo.
|
||||
|
||||
En algunos proyectos Java, dependiendo de la tecnología elegida, es común
|
||||
encontrar clases que siguen el patrón DAO, usado para aislar el acceso a los
|
||||
datos. Sin embargo, en este curso usaremos otro patrón, conocido como
|
||||
***Repositorio***.
|
||||
|
||||
***¿cuál es la diferencia entre los dos enfoques y por qué esta elección?***
|
||||
|
||||
#### Patrón DAO
|
||||
|
||||
El patrón de diseño DAO, también conocido como **Data Access Object**, se
|
||||
utiliza para la persistencia de datos, donde su objetivo principal es separar
|
||||
las reglas de negocio de las reglas de acceso a la base de datos. En las clases
|
||||
que siguen este patrón, aislamos todos los códigos que se ocupan de conexiones,
|
||||
comandos SQL y funciones directas a la base de datos, para que dichos códigos
|
||||
no se esparzan a otras partes de la aplicación, algo que puede dificultar el
|
||||
mantenimiento del código y también el intercambio de tecnologías y del mecanismo
|
||||
de persistencia.
|
||||
|
||||
Implementación
|
||||
Supongamos que tenemos una tabla de productos en nuestra base de datos. La
|
||||
implementación del ***patrón DAO*** sería la sgte:
|
||||
|
||||
Primero, será necesario crear una clase básica de dominio Producto:
|
||||
|
||||
```java
|
||||
public class Producto {
|
||||
private Long id;
|
||||
private String nombre;
|
||||
private BigDecimal precio;
|
||||
private String descripcion;
|
||||
|
||||
// constructores, getters y setters
|
||||
}
|
||||
```
|
||||
|
||||
A continuación, necesitaríamos crear la clase ProductoDao, que proporciona
|
||||
operaciones de persistencia para la clase de dominio Producto:
|
||||
|
||||
```java
|
||||
public class ProductoDao {
|
||||
|
||||
private final EntityManager entityManager;
|
||||
|
||||
public ProductoDao(EntityManager entityManager) {
|
||||
this.entityManager = entityManager;
|
||||
}
|
||||
|
||||
public void create(Producto producto) {
|
||||
entityManager.persist(producto);
|
||||
}
|
||||
|
||||
public Producto read(Long id) {
|
||||
return entityManager.find(Producto.class, id);
|
||||
}
|
||||
|
||||
public void update(Producto producto) {
|
||||
entityManger.merge(producto);
|
||||
}
|
||||
|
||||
public void remove(Producto producto) {
|
||||
entityManger.remove(producto);
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
En el ejemplo anterior, se utilizó JPA como tecnología de persistencia de datos
|
||||
de la aplicación.
|
||||
|
||||
**Patrón Repository**. Según el famoso libro Domain-Driven Design de Eric Evans:
|
||||
|
||||
El repositorio es un mecanismo para encapsular el almacenamiento, recuperación y
|
||||
comportamiento de búsqueda, que emula una colección de objetos.
|
||||
|
||||
En pocas palabras, un repositorio también maneja datos y oculta consultas
|
||||
similares a DAO. Sin embargo, se encuentra en un nivel más alto, más cerca de
|
||||
la lógica de negocio de una aplicación. Un repositorio está vinculado a la
|
||||
regla de negocio de la aplicación y está asociado con el agregado de sus objetos
|
||||
de negocio, devolviéndolos cuando es necesario.
|
||||
|
||||
Pero debemos estar atentos, porque al igual que en el patrón DAO, las reglas de
|
||||
negocio que están involucradas con el procesamiento de información no deben estar
|
||||
presentes en los repositorios. Los repositorios no deben tener la responsabilidad
|
||||
de tomar decisiones, aplicar algoritmos de transformación de datos o brindar
|
||||
servicios directamente a otras capas o módulos de la aplicación. Mapear entidades
|
||||
de dominio y proporcionar funcionalidades de aplicación son responsabilidades muy
|
||||
diferentes.
|
||||
|
||||
Un repositorio se encuentra entre las reglas de negocio y la capa de persistencia:
|
||||
|
||||
1. Proporciona una interfaz para las reglas comerciales donde se accede a los
|
||||
objetos como una colección.
|
||||
2. Utiliza la capa de persistencia para escribir y recuperar datos necesarios
|
||||
para persistir y recuperar objetos de negocio.
|
||||
|
||||
Por lo tanto, incluso es posible utilizar uno o más DAOs en un repositorio.
|
||||
|
||||
***¿Por qué el patrón repositorio en lugar de DAO usando Spring?***
|
||||
|
||||
El patrón de repositorio fomenta un diseño orientado al dominio, lo que
|
||||
proporciona una comprensión más sencilla del dominio y la estructura de datos.
|
||||
Además, al usar el repositorio de Spring, no tenemos que preocuparnos por usar
|
||||
la API de JPA directamente, simplemente creando los métodos, que Spring crea la
|
||||
implementación en tiempo de ejecución, lo que hace que el código sea mucho más
|
||||
simple, pequeño y legible.
|
||||
|
||||
### Validaciones
|
||||
|
||||
Bean Validation se compone de varias ***anotaciones*** que se deben agregar a los
|
||||
atributos en los que queremos realizar las validaciones. Se implementaron algunas
|
||||
de estas anotaciones, como **`@NotBlank`**, que indica que un atributo String no
|
||||
puede ser nulo o vacío. (
|
||||
[DatosRegistroMedico.java](./api_rest/api/src/main/java/med/voll/api/medico/DatosRegistroMedico.java)
|
||||
)
|
||||
|
||||
Sin embargo, existen decenas de otras anotaciones que se pueden utilizar, para
|
||||
los más diversos tipos de atributos.
|
||||
|
||||
Se puede consultar una lista de las principales anotaciones de **Bean Validation**
|
||||
en la
|
||||
[documentación oficial](https://jakarta.ee/specifications/bean-validation/3.0/jakarta-bean-validation-spec-3.0.html#builtinconstraints).
|
||||
|
||||
### Migraciones
|
||||
|
||||
Estas se crean en el directorio
|
||||
[/src/db/migration/](./api_rest/api/src/main/resources/db/migration/). Es
|
||||
importante detener siempre el proyecto al crear los archivos de migración, para
|
||||
evitar que **Flyway** los ejecute antes de tiempo, con el código aún incompleto,
|
||||
lo que podría causar problemas.
|
||||
|
||||
En caso de:
|
||||
|
||||
```sql
|
||||
# Soft
|
||||
DELETE FROM flyway_schema_history WHERE success = 0;
|
||||
|
||||
# Hard
|
||||
DROP TABLE flyway_schema_history;
|
||||
```
|
||||
|
||||
### Lombok
|
||||
|
||||
Es una biblioteca de Java especialmente enfocada en la reducción de código y
|
||||
en la productividad en el desarrollo de proyectos.
|
||||
|
||||
Utiliza la idea de ***anotaciones*** para generar códigos en el tiempo de
|
||||
compilación. Pero el código generado no es visible, y tampoco es posible cambiar
|
||||
lo que se ha generado.
|
||||
|
||||
Puede ser una buena herramienta aliada a la hora de escribir clases complejas,
|
||||
siempre que el desarrollador tenga conocimiento sobre ella. Para más información
|
||||
ver la documentación de [Lombok](https://projectlombok.org/)
|
||||
|
||||
### Anotacion Autowired
|
||||
|
||||
En el contexto del framework Spring, que utiliza como una de sus bases el patrón
|
||||
de diseño *"Inyección de Dependencias"*, la idea sirve para definir una
|
||||
inyección automática en un determinado componente del proyecto Spring, ese
|
||||
componente puede ser atributos, métodos e incluso constructores.
|
||||
|
||||
Esta anotación se permite por el uso de la anotación `@SpringBootApplication`,
|
||||
en el archivo de configuración de Spring, disponible cada vez que se crea un
|
||||
proyecto Spring.
|
||||
|
||||
Al marcar un componente con la anotación `@Autowired` le estamos diciendo a
|
||||
Spring que el componente es un punto donde se debe inyectar una dependencia,
|
||||
en otras palabras, el componente se inyecta en la clase que lo posee,
|
||||
estableciendo una colaboración entre componentes.
|
||||
|
||||
Para más información sobre la anotación, ver la
|
||||
[documentación oficial](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/annotation/Autowired.html)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user