diff --git a/010_spring_boot/api_rest/api/pom.xml b/010_spring_boot/api_rest/api/pom.xml index 2240b31..dcad58c 100644 --- a/010_spring_boot/api_rest/api/pom.xml +++ b/010_spring_boot/api_rest/api/pom.xml @@ -38,6 +38,31 @@ spring-boot-starter-test test + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-mysql + + + + com.mysql + mysql-connector-j + runtime + + + + org.springframework.boot + spring-boot-starter-validation + + diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java index 69bb17a..d24e904 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/controller/MedicoController.java @@ -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)); } } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/DatosDireccion.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/DatosDireccion.java index 892015c..0b3a6ea 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/DatosDireccion.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/DatosDireccion.java @@ -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) { } diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/Direccion.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/Direccion.java new file mode 100644 index 0000000..23488c7 --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/direccion/Direccion.java @@ -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(); + } +} diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosRegistroMedico.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosRegistroMedico.java index 0c22109..78ee8e2 100644 --- a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosRegistroMedico.java +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/DatosRegistroMedico.java @@ -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 +) {} diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/Medico.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/Medico.java new file mode 100644 index 0000000..3df02fb --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/Medico.java @@ -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()); + } +} diff --git a/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/MedicoRepository.java b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/MedicoRepository.java new file mode 100644 index 0000000..dfe4532 --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/java/med/voll/api/medico/MedicoRepository.java @@ -0,0 +1,5 @@ +package med.voll.api.medico; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MedicoRepository extends JpaRepository {} diff --git a/010_spring_boot/api_rest/api/src/main/resources/application.properties b/010_spring_boot/api_rest/api/src/main/resources/application.properties index 8b13789..0794eec 100644 --- a/010_spring_boot/api_rest/api/src/main/resources/application.properties +++ b/010_spring_boot/api_rest/api/src/main/resources/application.properties @@ -1 +1,3 @@ - +spring.datasource.url=jdbc:mysql://192.168.0.8/vollmed_api +spring.datasource.username=alura +spring.datasource.password=alura \ No newline at end of file diff --git a/010_spring_boot/api_rest/api/src/main/resources/db/migration/V1__create-table-medicos.sql b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V1__create-table-medicos.sql new file mode 100644 index 0000000..a7e0bb8 --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V1__create-table-medicos.sql @@ -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) + +); \ No newline at end of file diff --git a/010_spring_boot/api_rest/api/src/main/resources/db/migration/V2__alter-table-medicos-add-telefono.sql b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V2__alter-table-medicos-add-telefono.sql new file mode 100644 index 0000000..0da717e --- /dev/null +++ b/010_spring_boot/api_rest/api/src/main/resources/db/migration/V2__alter-table-medicos-add-telefono.sql @@ -0,0 +1 @@ +alter table medicos add telefono varchar(20) not null; \ No newline at end of file diff --git a/010_spring_boot/spring_boot_1.md b/010_spring_boot/spring_boot_1.md index 3c5c9e0..a32dde5 100644 --- a/010_spring_boot/spring_boot_1.md +++ b/010_spring_boot/spring_boot_1.md @@ -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:///vollmed-api +spring.datasource.username= +spring.datasource.password= +``` + +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) +