2023-09-16 16:31:56 -03:00
|
|
|
# API Rest Java - Documentar, Probar y Preparar una API para su Implementación
|
|
|
|
|
|
|
|
Continuación de
|
|
|
|
[Buenas Prácticas y Protección de una API Rest](./spring_boot_2.md) donde se
|
|
|
|
vio:
|
|
|
|
|
|
|
|
- Creación de un API Rest
|
|
|
|
- Crud (Create, Read, Update, Delete)
|
|
|
|
- Validaciones
|
|
|
|
- Paginación y orden
|
|
|
|
- Buenas prácticas REST
|
|
|
|
- Tratamiento de errores
|
|
|
|
- Control de acceso con JWT
|
|
|
|
|
|
|
|
### Objetivos
|
|
|
|
|
|
|
|
- Funcionalidad agendar consultas
|
|
|
|
- Documentación de la API
|
|
|
|
- Tests Automatizados
|
|
|
|
- Build del proyecto
|
|
|
|
|
|
|
|
[trello](https://trello.com/b/NiWWC55L/vollmed-api-3ra-parte) -
|
|
|
|
[figma](https://www.figma.com/file/tWpylp7pB4n8rX1AsKTsY2/Voll_med-FRONT-Mobile)
|
|
|
|
|
|
|
|
|
|
|
|
## Nuevas Funcionalidades
|
|
|
|
|
|
|
|
- Controller
|
|
|
|
- DTOs
|
|
|
|
- Entidades JPA
|
|
|
|
- Repository
|
|
|
|
- Migration
|
|
|
|
- Security
|
|
|
|
- Reglas de negocio
|
|
|
|
|
|
|
|
Proyecto [Voll_Med API](./api_rest/api3/)
|
|
|
|
|
|
|
|
Para implementar esta u otras funcionalidades, siempre es necesario crear los
|
|
|
|
siguientes tipos de códigos:
|
|
|
|
|
|
|
|
- **Controller**(s), para mapear la solicitud de la nueva funcionalidad
|
|
|
|
- **DTO**s, que representan los datos que llegan y salen de la API
|
|
|
|
- **Entidad**(es) **JPA**
|
|
|
|
- **Repository**(s), para aislar el acceso a la base de datos
|
|
|
|
- **Migration**, para hacer las alteraciones en la base de datos
|
|
|
|
|
|
|
|
Estos son los cinco tipos de código que **siempre** se desarrollan para una nueva
|
|
|
|
funcionalidad. Esto también se aplica al agendamiento de las consultas,
|
|
|
|
incluyendo un sexto elemento a la lista, ***las reglas de negocio***.
|
|
|
|
En este proyecto, se implementan las reglas de negocio con algoritmos más
|
|
|
|
complejos.
|
|
|
|
|
|
|
|
### Implementando la funcionalidad
|
|
|
|
|
|
|
|
Se desarrolla la funcionalidad por partes. Empezaremos por los primeros cinco
|
|
|
|
elementos de la lista, que son más básicos. Luego, la parte de reglas de negocio.
|
|
|
|
|
|
|
|
Se crea un nuevo `ConsultaController` en el paquete
|
|
|
|
[`src.main.java.med.voll.api.controller`](./api_rest/api2/src/main/java/med/voll/api/controller).
|
|
|
|
|
|
|
|
La idea es tener un **Controller** para recibir esas solicitudes relacionadas con
|
|
|
|
el agendado de consultas.
|
|
|
|
|
|
|
|
Es una clase Controller, con las ***anotaciones*** de Spring `@Controller`,
|
|
|
|
`@ResponseBody`, `@RequestMapping("consultas")` o `@RestController`. Mapea las
|
|
|
|
solicitudes que llegan con la **URI** `/consultas`, que debe llamar a este
|
|
|
|
controller y no a los otros.
|
|
|
|
|
|
|
|
Luego, el método anotado con `@PostMapping`. Entonces, la solicitud para programar
|
|
|
|
una consulta será del tipo **POST**, como en otras funcionalidades.
|
|
|
|
|
|
|
|
[ConsultaController](./api_rest/api3/src/main/java/med/voll/api/controller/ConsultaController.java)
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Controller
|
|
|
|
@ResponseBody
|
|
|
|
@RequestMapping("/consultas")
|
|
|
|
public class ConsultaController {
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
private AgendaDeConsultaService service;
|
|
|
|
|
|
|
|
@PostMapping
|
|
|
|
@Transactional
|
|
|
|
public ResponseEntity agendar(
|
|
|
|
@RequestBody @Valid DatosAgendarConsulta datos) {
|
|
|
|
System.out.println(datos);
|
|
|
|
service.agendar(datos);
|
|
|
|
return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
[DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
|
|
|
|
se trata de un registro similar a los que visto anteriormente. Tiene los campos
|
|
|
|
que provienen de la API (`Long idMedico`, `Long idPaciente` y
|
|
|
|
`LocalDateTime fecha`) y las anotaciones de **BEAN validation** `@NotNull` para el
|
|
|
|
`Id` del paciente y para la `fecha`, además de que la fecha debe ser en el
|
|
|
|
**futuro** (`@Future`), es decir, no se podrá programar una consulta en días
|
|
|
|
pasados.
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Controller
|
|
|
|
@ResponseBody
|
|
|
|
@RequestMapping("/consultas")
|
|
|
|
public class ConsultaController {
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
private AgendaDeConsultaService service;
|
|
|
|
|
|
|
|
@PostMapping
|
|
|
|
@Transactional
|
|
|
|
public ResponseEntity agendar(@RequestBody @Valid DatosAgendarConsulta datos) {
|
|
|
|
System.out.println(datos);
|
|
|
|
service.agendar(datos);
|
|
|
|
return ResponseEntity.ok(new DatosDetalleConsulta(null, null, null, null));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Al volver al Controller, el otro DTO es el de respuesta, llamado
|
|
|
|
`DatosDetalleConsulta`. Devuelve el `ID` de la consulta creada, del **médico**,
|
|
|
|
del **paciente** y la **fecha de la consulta** registrada en el sistema.
|
|
|
|
|
|
|
|
En el paquete `src.main.java.med.voll.api.domain`, se crea el subpaquete
|
|
|
|
`consulta`, que abarca las clases relacionadas con el dominio de consulta.
|
|
|
|
|
|
|
|
Entre ellas, la ***entidad JPA***
|
|
|
|
[Consulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java),
|
|
|
|
que contiene las anotaciones de ***JPA*** y ***Lombok***, así como la
|
|
|
|
información de la consulta: `medico`, `paciente` y `fecha`.
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Table(name = "consultas")
|
|
|
|
@Entity(name = "Consulta")
|
|
|
|
@Getter
|
|
|
|
@NoArgsConstructor
|
|
|
|
@AllArgsConstructor
|
|
|
|
@EqualsAndHashCode(of = "id")
|
|
|
|
public class Consulta {
|
|
|
|
|
|
|
|
@Id
|
|
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
|
|
private Long id;
|
|
|
|
|
|
|
|
@ManyToOne(fetch = FetchType.LAZY)
|
|
|
|
@JoinColumn(name = "medico_id")
|
|
|
|
private Medico medico;
|
|
|
|
|
|
|
|
@ManyToOne(fetch = FetchType.LAZY)
|
|
|
|
@JoinColumn(name = "paciente_id")
|
|
|
|
private Paciente paciente;
|
|
|
|
|
|
|
|
private LocalDateTime fecha;
|
|
|
|
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
En este caso, `medico` y `paciente` son relaciones con las otras entidades
|
|
|
|
`Medico` y `Paciente`.
|
|
|
|
|
|
|
|
También se crea
|
|
|
|
[ConsultaRepository](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java),
|
|
|
|
que está vacío por el momento.
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Repository
|
|
|
|
public interface ConsultaRepository extends JpaRepository<Consulta, Long> {}
|
|
|
|
```
|
|
|
|
|
|
|
|
Por último, la migración número 6 (`V6`) en
|
|
|
|
`src.main.java.med.voll.api.resources.db.migration`, que crea la tabla de
|
|
|
|
consultas.
|
|
|
|
|
|
|
|
```sql
|
|
|
|
CREATE TABLE consultas(
|
|
|
|
|
|
|
|
id BIGINT NOT NULL AUTO_INCREMENT,
|
|
|
|
medico_id BIGINT NOT NULL,
|
|
|
|
paciente_id BIGINT NOT NULL,
|
|
|
|
fecha DATETIME NOT NULL,
|
|
|
|
|
|
|
|
PRIMARY KEY(id),
|
|
|
|
CONSTRAINT fk_consultas_medico_id FOREIGN KEY(medico_id)
|
|
|
|
REFERENCES medicos(id),
|
|
|
|
CONSTRAINT fk_consultas_paciente_id FOREIGN KEY(paciente_id)
|
|
|
|
REFERENCES pacientes(id)
|
|
|
|
|
|
|
|
);
|
|
|
|
```
|
|
|
|
|
|
|
|
Los campos de `Id` de **consulta**, `Id` de **paciente**, `Id` de **médico** y
|
|
|
|
`fecha`, donde `medico_id` y `paciente_id` son ***claves foráneas*** que apuntan
|
|
|
|
a las tablas `medicos` y `pacientes`.
|
|
|
|
|
|
|
|
Estos son los códigos estándar para cualquier funcionalidad, con sus respectivos
|
|
|
|
cambios de acuerdo con el proyecto. Cada uno creará un controlador o una entidad
|
|
|
|
distinta, pero el funcionamiento es el mismo.
|
|
|
|
|
|
|
|
Ahora se puede intentar enviar una solicitud a la dirección `/consultas` y
|
|
|
|
verificar si se redirige al `ConsultaController` y comprobando el `System.out`
|
|
|
|
que muestra los datos que llegaron en el JSON de la solicitud.
|
|
|
|
|
|
|
|
```json
|
|
|
|
{
|
|
|
|
"idPaciente": 1,
|
|
|
|
"idMedico": 1,
|
|
|
|
"fecha": "2023-09-14T10:00"
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Esta es la ipmplementación del esqueleto de la funcionalidad. Ahora se deben
|
|
|
|
implementar las reglas de negocio.
|
|
|
|
|
|
|
|
El trabajo es un poco diferente a lo ya realizado con la validación de campos
|
|
|
|
de formulario vía ***BEAN validation***. Estas validaciones son más complejas.
|
|
|
|
|
|
|
|
***¿Cómo implementarlas?***
|
|
|
|
|
|
|
|
Observando `ConsultaController.java`, se podrían hacer todas las validaciones
|
|
|
|
en el método `agendar()`, antes del retorno. Sin embargo, esa no es una buena
|
|
|
|
práctica.
|
|
|
|
|
|
|
|
La clase controller no debe traer las reglas de negocio de la aplicación.
|
|
|
|
Es solo una clase que controla el flujo de ejecución: cuando llega una solicitud,
|
|
|
|
llama a la clase X, devuelve la respuesta Y. Si la condición es Z, devuelve otra
|
|
|
|
respuesta y así sucesivamente. Es decir, solo controla el flujo de ejecución y,
|
|
|
|
por lo tanto, no debería tener reglas de negocio.
|
|
|
|
|
|
|
|
Así, aislando las reglas de negocio, los algoritmos, los cálculos y las
|
|
|
|
validaciones en otra clase que será llamada por el Controller.
|
|
|
|
|
|
|
|
En el paquete `consulta`. Se creas la clase para contener las reglas de agendado
|
|
|
|
de consultas llamada
|
|
|
|
[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java).
|
|
|
|
|
|
|
|
El nombre es muy autoexplicativo, esta clase contendrá la agenda de consultas.
|
|
|
|
Se pueden tener otras funcionalidades en esta clase, relacionadas con el
|
|
|
|
agendamiento de consultas.
|
|
|
|
|
|
|
|
Esta no es una clase ***Controller*** ni una clase de ***configuraciones***. Esta
|
|
|
|
clase representa un servicio de la aplicación, el de agendado de consultas. Por
|
|
|
|
lo tanto, será una ***clase de servicios*** (**Service**) y llevará la anotación
|
|
|
|
`@Service`. El objetivo de esta anotación es declarar el componente de servicio
|
|
|
|
a Spring Boot.
|
|
|
|
|
|
|
|
Dentro de esta clase, se crear un método `public void agendar()`, que recibe
|
|
|
|
como parámetro el **DTO** `DatosAgendarConsulta`.
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Service
|
|
|
|
public class AgendaDeConsultaService {
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
private ConsultaRepository consultaRepository;
|
|
|
|
@Autowired
|
|
|
|
private MedicoRepository medicoRepository;
|
|
|
|
@Autowired
|
|
|
|
private PacienteRepository pacienteRepository;
|
|
|
|
|
|
|
|
public void agendar(DatosAgendarConsulta datos) {
|
|
|
|
|
|
|
|
if (pacienteRepository.findById(datos.idPaciente()).isPresent()) {
|
|
|
|
throw new ValidacionDeIntegridad("Id de paciente no encontrado");
|
|
|
|
}
|
|
|
|
if (datos.idMedico() != null && medicoRepository.existsById(datos.idMedico())) {
|
|
|
|
throw new ValidacionDeIntegridad("Id de médico no encontrado");
|
|
|
|
}
|
|
|
|
|
|
|
|
var paciente = pacienteRepository.findById(datos.idPaciente()).get();
|
|
|
|
var medico = medicoRepository.findById(datos.idMedico()).get();
|
|
|
|
var consulta = new Consulta(null, medico, paciente, datos.fecha());
|
|
|
|
consultaRepository.save(consulta);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
La clase Service ejecuta las reglas de negocio y las validaciones de la aplicación.
|
|
|
|
|
|
|
|
Esta clase se utliza en `ConsultaController`, con `@Autowired` se comuníca a
|
|
|
|
Spring que instancie este objeto
|
|
|
|
|
|
|
|
Con esto, se inyecta la clase `AgendaDeConsultas` en el Controller. En el método
|
|
|
|
`agendar` del `Controller`, se obtiene el objeto `agenda` y se llama al método
|
|
|
|
`agendar()`, pasando como parámetro los datos que llegan al `Controller`.
|
|
|
|
Todo esto antes del retorno.
|
|
|
|
|
|
|
|
El **Controller** recibe la información, hace solo la validación de
|
|
|
|
***BIN validation*** y llama a la clase **Service** `AgendaDeConsultas`, que
|
|
|
|
ejecutará las reglas de negocio. Esta es la forma correcta de manejar las reglas
|
|
|
|
de negocio.
|
|
|
|
|
|
|
|
En la clase `AgendaDeConsultas` y están todas las validaciones para el método
|
|
|
|
`agendar()`.
|
|
|
|
|
|
|
|
El requerimiento especifica que se debe recibir la solicitud con los datos de
|
|
|
|
agendamiento y se deben guardar en la tabla de consultas.
|
|
|
|
|
|
|
|
Por lo tanto, se necesita acceder a la base de datos y a la tabla de consultas
|
|
|
|
en esta clase. Así que se declara un atributo `ConsultaRepository`, llamándolo
|
|
|
|
`consultaRepository`.
|
|
|
|
|
|
|
|
Se usa la anotación `@Autowired` para que el Spring Boot inyecte este repository
|
|
|
|
en la clase `Service`.
|
|
|
|
|
|
|
|
Al final del método `agendar()`, se inserta `consultaRepository.save()` pasandole
|
|
|
|
un objeto del tipo consulta, la entidad **JPA**. Obviamente, solo se puede llamar
|
|
|
|
a este método si todas las validaciones se han ejecutado de acuerdo con las
|
|
|
|
reglas de negocio.
|
|
|
|
|
|
|
|
La entidad `Consulta` está anotada con `@AllArgsConstructor` de ***Lombok***,
|
|
|
|
que genera un constructor con todos los atributos. Se puede usar este mismo
|
|
|
|
constructor en `AgendamientoDeConsultas`.
|
|
|
|
El primer parámetro es el `Id null`, ya que es la base de datos la que pasará
|
|
|
|
el `Id`. El segundo es `medico`, `paciente` y `fecha`. Esta última viene en el
|
|
|
|
**DTO**, a través del parámetro `datos`.
|
|
|
|
|
|
|
|
Sucede que el médico y el paciente no llegan en la solicitud, sino el `Id` del
|
|
|
|
médico y el `Id` del paciente. Por lo tanto, es necesario establecer el objeto
|
|
|
|
completo en la entidad y no solo el `Id`.
|
|
|
|
|
|
|
|
Por lo tanto, es necesario cargar médico y paciente desde la base de datos.
|
|
|
|
Se necesita inyectar, entonces, dos Repositories más en `Service`:
|
|
|
|
`MedicoRepository` y `PacienteRepository`.
|
|
|
|
|
|
|
|
En el método `agendar()`, se crea un objeto paciente también. Usando
|
|
|
|
`pacienteRepository.findById()` para buscar el objeto por `Id`, que está dentro
|
|
|
|
del DTO datos.
|
|
|
|
|
|
|
|
En la solicitud solo viene el `Id`, pero se necesita cargar el objeto completo.
|
|
|
|
Por lo tanto, se usa el Repository para cargar por el `Id` de la base de datos.
|
|
|
|
El médico seguirá la misma dinámica (
|
|
|
|
[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java)
|
|
|
|
).
|
|
|
|
|
|
|
|
Aparecerá un error de compilación porque el método `findById()` no devuelve la
|
|
|
|
entidad, sino un Optional. Por lo tanto, al final de la línea, antes del punto
|
|
|
|
y coma, es necesario incluir `.get()` junto a `findById()`. Esto hace que tome
|
|
|
|
la entidad cargada.
|
|
|
|
|
|
|
|
El método `agendar()` en la clase `Service` obteniene el `Id`, cargar el paciente
|
|
|
|
y el médico desde la base de datos creando una entidad consulta pasando el médico,
|
|
|
|
el paciente y la fecha que viene en el DTO, y se guarda en la base de datos.
|
|
|
|
|
|
|
|
Pero antes de esto, se necesita escribir el código para realizar todas las
|
|
|
|
validaciones que forman parte de las reglas de negocio.
|
|
|
|
|
|
|
|
A continuación, se aborda cómo realizar las validaciones de la mejor manera
|
|
|
|
posible.
|
|
|
|
|
|
|
|
### Validaciones
|
|
|
|
|
|
|
|
Para verificar el ID del paciente, se usa un `if`. La idea es comprobar si el `Id`
|
|
|
|
del paciente existe. El Repository es el que consulta la base de datos.
|
|
|
|
|
|
|
|
Se puede lanzar una excepción dentro del `if`, que muestre un mensaje indicando
|
|
|
|
el problema. Incluso se puede crear una excepción personalizada para el proyecto
|
|
|
|
llamada `ValidacaoException()`. Con un mensaje como
|
|
|
|
`"El ID del paciente proporcionado no existe"` o similar (
|
|
|
|
[DatosDetalleConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java)
|
|
|
|
).
|
|
|
|
|
|
|
|
En *package* `med.voll.api.infra.errores` se crea la clase
|
|
|
|
[ValidacionDeIntegridad](./api_rest/api3/src/main/java/med/voll/api/infra/errores/ValidacionDeIntegridad.java)
|
|
|
|
|
|
|
|
```java
|
|
|
|
package med.voll.api.infra.errores;
|
|
|
|
|
|
|
|
public class ValidacionDeIntegridad extends RuntimeException {
|
|
|
|
public ValidacionDeIntegridad(String s) {
|
|
|
|
super(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
En la clase
|
|
|
|
[AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java),
|
|
|
|
se realiza la verificación con. Primero, se verifica si existe un paciente con
|
|
|
|
el `Id` que está llegando a la base de datos. Si no existe, se lanzará una
|
|
|
|
excepción con un mensaje de error.
|
|
|
|
|
|
|
|
Se utiliza el método de la ***interfaz Repository*** en Spring Data llamado
|
|
|
|
`existsById`. Realiza una consulta a la base de datos para verificar si existe
|
|
|
|
un registro con un determinado `Id` que devuelve un booleano, `true` si existe,
|
|
|
|
`false` si no.
|
|
|
|
Pasando el parámetro `datos.idMedico()`. Se niega la expresión. Con esto, si no
|
|
|
|
hay un paciente con el `Id` en la base de datos, se debe detener la ejecución
|
|
|
|
del resto del código.
|
|
|
|
|
|
|
|
Recordando la última regla de negocio.
|
|
|
|
|
|
|
|
***La elección del médico es opcional, y en ese caso el sistema debe elegir
|
|
|
|
aleatoriamente algún médico disponible en la fecha/hora indicada.***
|
|
|
|
|
|
|
|
Por lo tanto, es posible que un `Id` de médico no llegue en la solicitud.
|
|
|
|
No se puede llamar a `existsById` si el `Id` del médico es nulo. Esto resultará
|
|
|
|
en un error para **JPA**.
|
|
|
|
|
|
|
|
Solo se puede llamar al `if` si el `Id` no es nulo. Por lo tanto, se agrega una
|
|
|
|
condición al if antes de la condición actual:
|
|
|
|
|
|
|
|
```java
|
|
|
|
if(datos.idMedico()!=null && !medicoRepository.existsById(datos.idMedico())){
|
|
|
|
throw new ValidacionDeIntegridad("Id de medico no encontrado");
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
En el caso del médico, al ser un campo opcional, puede ser que la línea
|
|
|
|
`var medico = medicoRepository.findById(dados.idMedico()).get()` tenga un
|
|
|
|
`idMedico` nulo.
|
|
|
|
|
|
|
|
De acuerdo con la regla de negocio analizada, se necesita escribir un algoritmo
|
|
|
|
que elija aleatoriamente un médico en la base de datos. Por lo tanto, la línea
|
|
|
|
anterior necesita ser reemplazada. Pare ello se llama al método privado
|
|
|
|
`seleccionarMedico(datos)` que recibe un objeto `DatosAgendarConsulta` como
|
|
|
|
parametro y retorna un objeto `Medico`.
|
|
|
|
|
|
|
|
Esto sirve para aislar el algoritmo y evitar que esté suelto dentro del método
|
|
|
|
de programación de citas. En el método `seleccionarMedico()`, se necesita
|
|
|
|
verificar si está llegando el `Id` del médico en la solicitud o no.
|
|
|
|
|
|
|
|
Si lo está, se obtiene el médico correspondiente de la base de datos. Si no es
|
|
|
|
así, se debe elegir aleatoriamente un profesional de la salud, según lo indica
|
|
|
|
la regla de negocio.
|
|
|
|
|
|
|
|
Para implementar el algoritmo que elige al médico de manera aleatoria, se deben
|
|
|
|
cubrir todos los posibles escenarios. La primera comprobación es que si la
|
|
|
|
persona eligió un médico al hacer la solicitud, usando
|
|
|
|
`if (dados.idMedico() != null)`.
|
|
|
|
|
|
|
|
En este caso, se carga la información de la base de datos con
|
|
|
|
`return medicoRepository.getReferenceById(dados.idMedico())`. En lugar de usar
|
|
|
|
`findById()`, se podemos usar `getReferenceById()` y no es necesario llamar al
|
|
|
|
`.get()` usamdo anteriormente.
|
|
|
|
|
|
|
|
También se puedemo cambiar `findById()` por `getReferenceById()` en la variable
|
|
|
|
paciente, ya que no se quiere cargar el objeto para manipularlo, sino, solo para
|
|
|
|
asignarlo a otro objeto.
|
|
|
|
|
|
|
|
En el método `seleccionarMedico()` , lo primero es verificar si se realiza la
|
|
|
|
con un médico específico para su atención. Si es así, simplemente se carga la
|
|
|
|
información del médico que viene de la base de datos.
|
|
|
|
|
|
|
|
[DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
|
|
|
|
|
|
|
|
```java
|
|
|
|
public record DatosAgendarConsulta(
|
|
|
|
@NotNull Long idPaciente,
|
|
|
|
Long idMedico,
|
|
|
|
@NotNull @Future LocalDateTime fecha,
|
|
|
|
Especialidad especialidad) {
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
***¿Cómo elegir un médico aleatorio de la especialidad elegida, disponible en
|
|
|
|
la fecha y hora seleccionadas?***
|
|
|
|
|
|
|
|
Existen varias formas de hacer esto. Se podrían cargar los médicos, filtrarlos por
|
|
|
|
especialidad y comprobar la fecha de la consulta en Java.
|
|
|
|
|
|
|
|
Lo ideal es cargar un profesional aleatorio directamente de la base de datos.
|
|
|
|
Sin embargo, esta consulta es específica para nuestro proyecto, es decir, no está
|
|
|
|
lista en Spring Data JPA.
|
|
|
|
|
|
|
|
Se necesita crear un método para hacer esto:
|
|
|
|
|
|
|
|
```java
|
|
|
|
return medicoRepository.seleccionarMedicoConEspecialidadEnFecha(
|
|
|
|
datos.especialidad(),datos.fecha()
|
|
|
|
);
|
|
|
|
```
|
|
|
|
|
|
|
|
Creación del método `seleccionarMedicoConEspecialidadEnFecha(especialidad, fecha)`
|
|
|
|
en
|
|
|
|
[MedicoRepository](./api_rest/api3/src/main/java/med/voll/api/domain/medico/MedicoRepository.java).
|
|
|
|
|
|
|
|
Como el nombre del método está en español. No se estam siguiendo el estándar
|
|
|
|
de nomenclatura, como el `findAllByAtivoTrue()`. De esta manera, Spring Data no
|
|
|
|
podrá construir el SQL automáticamente. La idea es precisamente esa, para este método,
|
|
|
|
se escribe el comando SQL manualmente.
|
|
|
|
|
|
|
|
Para hacerlo, se usa la anotación `@Query()` justo encima del método. Que viene
|
|
|
|
del paquete `org.springframework.data.jpa`. Y entre paréntesis, se construye la
|
|
|
|
consulta utilizando la sintaxis del ***Java Persistence Query Language (JPQL)***.
|
|
|
|
|
|
|
|
```java
|
|
|
|
@Query("""
|
|
|
|
select m from Medico m
|
|
|
|
where m.activo= true\s
|
|
|
|
and
|
|
|
|
m.especialidad=:especialidad\s
|
|
|
|
and
|
|
|
|
m.id not in( \s
|
|
|
|
select c.medico.id from Consulta c
|
|
|
|
where
|
|
|
|
c.fecha=:fecha
|
|
|
|
)
|
|
|
|
order by rand()
|
|
|
|
limit 1
|
|
|
|
""")
|
|
|
|
Medico seleccionarMedicoConEspecialidadEnFecha(
|
|
|
|
Especialidad especialidad,
|
|
|
|
LocalDateTime fecha
|
|
|
|
);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### En resumen
|
|
|
|
Creación de nuevo *package*
|
|
|
|
[`domain.consulta`](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/)
|
|
|
|
donde se crean entidad **Consulta**, clases `ConsultaRepository`,
|
|
|
|
`DatosAgendarConsulta` y `DatosDetalleConsulta`.
|
|
|
|
|
|
|
|
- [Consulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/Consulta.java)
|
|
|
|
- [ConsultaRepository](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/ConsultaRepository.java)
|
|
|
|
- [DatosAgendarConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosAgendarConsulta.java)
|
|
|
|
- [DatosDetalleConsulta](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/DatosDetalleConsulta.java)
|
|
|
|
- [AgendaDeConsultasService](./api_rest/api3/src/main/java/med/voll/api/domain/consulta/AgendaDeConsultaService.java)
|
|
|
|
- [migración](./api_rest/api3/src/main/resources/db/migration/V6__create-table-consultas.sql)
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Anotación @JsonAlias
|
|
|
|
|
|
|
|
En caso que uno o mas campos enviados en el formato JSON a la API no correspondan
|
|
|
|
con los atributos de las clases DTO, se pueden utilizar alias
|
|
|
|
|
|
|
|
```java
|
|
|
|
public record DatosCompra(
|
|
|
|
@JsonAlias("producto_id") Long idProducto,
|
|
|
|
@JsonAlias("fecha_compra") LocalDate fechaCompra
|
|
|
|
){}
|
|
|
|
```
|
|
|
|
|
|
|
|
La anotación `@JsonAlias` sirve para mapear *alias* alternativos para los campos
|
|
|
|
que se recibirán del JSON, y es posible asignar múltiples alias:
|
|
|
|
|
|
|
|
```java
|
|
|
|
public record DatosCompra(
|
|
|
|
@JsonAlias({"producto_id", "id_producto"}) Long idProducto,
|
|
|
|
@JsonAlias({"fecha_compra", "fecha"}) LocalDate fechaCompra
|
|
|
|
){}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Formato fechas
|
|
|
|
|
|
|
|
Spring usa un formato definido para las fechas `LocalDateTime`, sin embargo, estas
|
|
|
|
se pueden personalizar. Por ejemplo, para aceptar el formato `dd/mm/yyy hh:mm`.
|
|
|
|
Para ello se utiliza la anotación `@JsonFormat`
|
|
|
|
|
|
|
|
```java
|
|
|
|
@NotNull
|
|
|
|
@Future
|
|
|
|
@JsonFormat(pattern = "dd/MM/yyyy HH:mm")
|
|
|
|
LocalDateTime fecha
|
|
|
|
```
|
|
|
|
|
|
|
|
Esta anotación también se puede utilizar en las clases DTO que representan la
|
|
|
|
información que devuelve la API, para que el JSON devuelto se formatee de
|
|
|
|
acuerdo con el patrón configurado.
|
|
|
|
Además, no se limita solo a la clase `LocalDateTime`, sino que también se puede
|
|
|
|
utilizar en atributos de tipo `LocalDate` y `LocalTime`.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Patrón Service
|
|
|
|
|
|
|
|
El ***Service pattern*** es muy utilizado en la programación y su nombre es muy
|
|
|
|
conocido. Pero a pesar de ser un nombre único, **Service** puede ser interpretado
|
|
|
|
de varias maneras:
|
|
|
|
|
|
|
|
- Puede ser un caso de uso, **Application Service**
|
|
|
|
- Un **Domain Service**, que tiene reglas de su dominio
|
|
|
|
- Un **Infrastructure Service**, que utiliza algún paquete externo para realizar
|
|
|
|
tareas
|
|
|
|
- etc
|
|
|
|
|
|
|
|
A pesar de que la interpretación puede ocurrir de varias formas, la idea detrás
|
|
|
|
del patrón es separar las reglas de negocio, las reglas de la aplicación y las
|
|
|
|
reglas de presentación para que puedan ser fácilmente probadas y reutilizadas
|
|
|
|
en otras partes del sistema.
|
|
|
|
|
|
|
|
Existen dos formas más utilizadas para crear **Services**. Puede crear
|
|
|
|
**Services** más genéricos, responsables de todas las asignaciones de un
|
|
|
|
**Controller**. O ser aún más específico, aplicando así la S del ***SOLID***:
|
|
|
|
***Single Responsibility Principle*** (*Principio de Responsabilidad Única*).
|
|
|
|
Este principio nos dice que una clase/función/archivo debe tener sólo una
|
|
|
|
única responsabilidad.
|
|
|
|
|
|
|
|
Piense en un sistema de ventas, en el que probablemente tendríamos algunas
|
|
|
|
funciones como:
|
|
|
|
|
|
|
|
- Registrar usuario
|
|
|
|
- Iniciar sesión
|
|
|
|
- Buscar productos
|
|
|
|
- Buscar producto por nombre
|
|
|
|
- etc
|
|
|
|
|
|
|
|
Entonces, se podrían crear los siguientes **Services**:
|
|
|
|
|
|
|
|
- RegistroDeUsuarioService
|
|
|
|
- IniciarSesionService
|
|
|
|
- BusquedaDeProductosService
|
|
|
|
- etc
|
|
|
|
|
|
|
|
Pero es importante estar atentos, ya que muchas veces no es necesario crear un
|
|
|
|
**Service** y, por lo tanto, agregar otra capa y complejidad innecesarias a
|
|
|
|
una aplicación. Una regla que podemos utilizar es la siguiente: si no hay reglas
|
|
|
|
de negocio, simplemente se puede realizar la comunicación directa entre los
|
|
|
|
controllers y los repositories de la aplicación.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|