JavaRush /Blog Java /Random-ES /API REST y otra tarea de prueba.
Денис
Nivel 37
Киев

API REST y otra tarea de prueba.

Publicado en el grupo Random-ES
Parte I: Comienzo ¿Por dónde empezar? Por extraño que parezca, pero según las especificaciones técnicas. Es extremadamente importante asegurarse de que después de leer los TOR enviados comprenda completamente lo que está escrito en ellos y lo que espera el cliente. En primer lugar, esto es importante para una mayor implementación y, en segundo lugar, si no implementa lo que se espera de usted, no será beneficioso para usted. Para evitar desperdiciar aire, esbocemos una especificación técnica sencilla. Entonces, quiero un servicio al que pueda enviar datos, que se almacenarán en el servicio y me los devolverán a voluntad. También necesito poder actualizar y eliminar estos datos si es necesario . Un par de frases no parece una cosa clara, ¿verdad? ¿Cómo quiero enviar datos allí? ¿Qué tecnologías utilizar? ¿En qué formato estarán estos datos? Tampoco hay ejemplos de datos entrantes y salientes. Conclusión: las especificaciones técnicas ya son malas . Intentemos reformularlo: necesitamos un servicio que pueda procesar solicitudes HTTP y trabajar con datos transferidos. Esta será la base de datos de registros de personal. Tendremos empleados, están divididos por departamentos y especialidades, los empleados pueden tener tareas asignadas. Nuestra tarea es automatizar el proceso de contabilidad de empleados contratados, despedidos y transferidos, así como el proceso de asignación y cancelación de tareas utilizando la API REST. Como Fase 1, actualmente estamos trabajando solo con empleados. El servicio debe tener varios endpoints para trabajar con él: - POST /empleado - Solicitud POST, que debe aceptar un objeto JSON con datos sobre el empleado. Este objeto debe guardarse en la base de datos; si dicho objeto ya existe en la base de datos, la información de los campos debe actualizarse con nuevos datos. - GET /empleado - Solicitud GET que devuelve la lista completa de empleados guardados en la base de datos - ELIMINAR - ELIMINAR /empleado para eliminar un empleado específico Modelo de datos de empleado:

{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [ 
  	//List of tasks, not needed for Phase 1
  ]
}
Parte II: Herramientas para el trabajo Entonces, el alcance del trabajo está más o menos claro, pero ¿cómo lo vamos a hacer? Obviamente, estas tareas en la prueba se asignan con un par de objetivos de la aplicación: ver cómo codifica, obligarlo a usar Spring y trabajar un poco con la base de datos. Bueno, hagamos esto. Necesitamos un proyecto SpringBoot con soporte API REST y una base de datos. En el sitio web https://start.spring.io/ podrás encontrar todo lo que necesitas. API REST u otra tarea de prueba.  - 1 Puede seleccionar el sistema de compilación, el idioma, la versión de SpringBoot, establecer la configuración de los artefactos, la versión de Java y las dependencias. Al hacer clic en el botón Agregar dependencias, aparecerá un menú de características con una barra de búsqueda. Los primeros candidatos para las palabras descanso y datos son Spring Web y Spring Data; los agregaremos. Lombok es una biblioteca conveniente que le permite usar anotaciones para deshacerse de kilómetros de código con métodos getter y setter. Al hacer clic en el botón Generar recibiremos un archivo con el proyecto que ya podremos descomprimir y abrir en nuestro IDE favorito. De forma predeterminada, recibiremos un proyecto vacío, con un archivo de configuración para el sistema de compilación (en mi caso será gradle, pero con Maven no hay una diferencia fundamental, y un archivo de inicio de primavera). Las personas atentas podrían prestar atención a dos cosas API REST u otra tarea de prueba.  - 2 . . Primero, tengo dos archivos de configuración application.properties y application.yml. De forma predeterminada, obtendrá exactamente propiedades: un archivo vacío en el que puede almacenar la configuración, pero a mí el formato yml me parece un poco más legible, ahora mostraré una comparación: a pesar de que la imagen API REST u otra tarea de prueba.  - 3 de la izquierda parece más compacta , es fácil ver una gran cantidad de duplicaciones en la ruta de propiedades. La imagen de la derecha es un archivo yml normal con una estructura de árbol que es bastante fácil de leer. Usaré este archivo más adelante en el proyecto. La segunda cosa que las personas atentas podrían notar es que mi proyecto ya tiene varios paquetes. Todavía no existe un código sensato, pero vale la pena revisarlo. ¿Cómo se redacta una solicitud? Al tener una tarea específica, debemos descomponerla: dividirla en pequeñas subtareas y comenzar a implementarlas secuencialmente. ¿Qué se requiere de nosotros? Necesitamos proporcionar una API que el cliente pueda usar; el contenido del paquete del controlador será responsable de esta parte de la funcionalidad. La segunda parte de la aplicación es la base de datos: el paquete de persistencia. En él almacenaremos cosas como Entidades de Base de Datos (Entidades), así como Repositorios, interfaces de resorte especiales que le permiten interactuar con la base de datos. El paquete de servicios contendrá clases de servicios. Hablaremos sobre qué es el servicio tipo Spring a continuación. Y por último, pero no menos importante, el paquete de utilidades. Allí se almacenarán clases utilitarias con todo tipo de métodos auxiliares, por ejemplo, clases para trabajar con fecha y hora, o clases para trabajar con cadenas, y quién sabe qué más. Comencemos a implementar la primera parte de la funcionalidad. Parte III: Controlador

@RestController
@RequestMapping("${application.endpoint.root}")
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;

    @GetMapping("${application.endpoint.employee}")
    public ResponseEntity<List<Employee>> getEmployees() {
        return ResponseEntity.ok().body(employeeService.getAllEmployees());
    }
}
Ahora nuestra clase EmployeeController se ve así. Hay una serie de cosas importantes a las que vale la pena prestar atención aquí. 1. Anotaciones encima de la clase, el primer @RestController le dice a nuestra aplicación que esta clase será un punto final. 2. @RequestMapping, aunque no es obligatorio, es una anotación útil: le permite establecer una ruta específica para el punto final. Aquellos. para llamarlo, necesitarás enviar solicitudes no a localhost:port/employee, sino en este caso a localhost:8086/api/v1/employee. En realidad, ¿de dónde vinieron estas api/v1 y empleado? Desde nuestro application.yml Si miras de cerca, puedes encontrar las siguientes líneas allí:

application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
Como puede ver, tenemos variables como application.endpoint.root y application.endpoint.employee, estas son exactamente las que escribí en las anotaciones, recomiendo recordar este método; ahorrará mucho tiempo al expandir o reescribir el funcionalidad: siempre es más conveniente tener todo en la configuración y no codificar todo el proyecto. 3. @RequiredArgsConstructor es una anotación de Lombok, una biblioteca conveniente que le permite evitar escribir cosas innecesarias. En este caso, la anotación equivale a que la clase tendrá un constructor público con todos los campos marcados como finales.

public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
Pero ¿por qué deberíamos escribir algo así si una anotación es suficiente? :) Por cierto, felicidades, este campo final tan privado no es más que la famosa Inyección de Dependencia. Sigamos adelante, ¿qué tipo de campo es el servicio de empleado? Este será uno de los servicios de nuestro proyecto que procesará las solicitudes para este punto final. La idea aquí es muy simple. Cada clase debe tener su propia tarea y no debe sobrecargarse con acciones innecesarias. Si se trata de un responsable del tratamiento, que se encargue de recibir solicitudes y enviar respuestas, pero preferimos confiar el procesamiento a un servicio adicional. Lo último que queda en esta clase es el único método que devuelve una lista de todos los empleados de nuestra empresa que utilizan el servicio mencionado anteriormente. La lista en sí está envuelta en una entidad llamada ResponseEntity. Hago esto para que en el futuro, si es necesario, pueda devolver fácilmente el código de respuesta y el mensaje que necesito, que el sistema automatizado pueda entender. Entonces, por ejemplo, ResponseEntity.ok() devolverá el código número 200, que dirá que todo está bien, pero si vuelvo, por ejemplo

return ResponseEntity.badRequest().body(Collections.emptyList());
entonces el cliente recibirá el código 400: solicitud incorrecta y una lista vacía en la respuesta. Normalmente, este código se devuelve si la solicitud es incorrecta. Pero un controlador no será suficiente para iniciar la aplicación. Nuestras dependencias no nos permitirán hacer esto, porque aún debemos tener una base :) Bueno, pasemos a la siguiente parte. Parte IV: persistencia simple Dado que nuestra tarea principal es iniciar la aplicación, por ahora nos limitaremos a un par de apéndices. Ya has visto en la clase Controlador que devolvemos una lista de objetos de tipo Empleado, esta será nuestra entidad para la base de datos. Creémoslo en el paquete demo.persistence.entity . En el futuro, el paquete de entidades se podrá complementar con otras entidades de la base de datos.

@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
Esta es una clase tan simple como una puerta, cuyas anotaciones dicen exactamente lo siguiente: esta es una entidad de base de datos @Entity, esta es una clase con datos @Data - Lombok. El útil Lombok creará para nosotros todos los captadores, definidores y constructores necesarios: relleno completo. Bueno, una pequeña guinda del pastel es @Accessors(chain = true). De hecho, esta es una implementación oculta del patrón Builder. Supongamos que tiene una clase con un montón de campos que desea asignar no a través del constructor, sino mediante métodos. En distinto orden, quizá no todos al mismo tiempo. Nunca se sabe qué tipo de lógica habrá en su aplicación. Esta anotación es la clave para esta tarea. Miremos:

public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
Supongamos que tenemos todos estos campos en nuestra clase😄Puedes asignarlos, no puedes asignarlos, puedes mezclarlos en algunos lugares. En el caso de sólo 3 propiedades esto no parece algo destacable. Pero hay clases con una cantidad mucho mayor de propiedades, por ejemplo 50. Y escribe algo como

public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
No parece muy bonito, ¿verdad? También debemos seguir estrictamente el orden de agregar variables de acuerdo con el constructor. Sin embargo, estoy divagando, volvamos al punto. Ahora tenemos un campo (obligatorio): un identificador único. En este caso, se trata de un número de tipo Long, que se genera automáticamente cuando se guarda en la base de datos. En consecuencia, la anotación @Id nos indica claramente que se trata de un identificador único; @GeneratedValue es responsable de su generación única. Vale la pena señalar que @Id se puede agregar a campos que no se generan automáticamente, pero luego la cuestión de la unicidad deberá resolverse manualmente. ¿Qué podría ser un identificador único de empleado? Bueno, por ejemplo, nombre completo + departamento... sin embargo, una persona tiene homónimos completos y existe la posibilidad de que trabajen en el mismo departamento, pequeña, pero la hay, eso significa que la decisión es mala. Sería posible agregar muchos otros campos, como fecha de contratación, ciudad, pero me parece que todo esto complica demasiado la lógica. Quizás se pregunte: ¿cómo es posible que varios campos sean únicos a la vez? Respondo: tal vez. Si tienes curiosidad, puedes buscar en Google cosas como @Embeddable y @Embedded. Bueno, hemos terminado con la esencia. Ahora necesitamos un repositorio simple. Se verá así:

public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
Sí, eso es todo. Solo una interfaz, la llamamos EmployeeRepository, extiende JpaRepository que tiene dos parámetros escritos, el primero es responsable del tipo de datos con el que trabaja, el segundo del tipo de clave. En nuestro caso, estos son Empleado y Largo. Es suficiente por ahora. El toque final antes de lanzar la aplicación será nuestro servicio:

@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
Existe el ya familiar RequiredArgsConstructor y la nueva anotación @Service: esto es lo que normalmente denota la capa de lógica empresarial. Cuando se ejecuta un contexto de primavera, las clases marcadas con esta anotación se crearán como Beans. Cuando en la clase EmployeeController hemos creado la propiedad final EmployeeService y adjuntamos RequiredArgsConstructor (o creamos un constructor a mano) Spring, al inicializar la aplicación, encontrará este lugar y nos deslizará un objeto de clase en esta variable. El valor predeterminado aquí es Singleton, es decir. Habrá un objeto para todos esos enlaces; esto es importante tenerlo en cuenta al diseñar la aplicación. En realidad, eso es todo, se puede iniciar la aplicación. No olvide ingresar las configuraciones necesarias en la configuración. API REST u otra tarea de prueba.  - 4 No describiré cómo instalar una base de datos, crear un usuario y la base de datos en sí, pero solo señalaré que en la URL utilizo dos parámetros adicionales: useUnicore=true y CharacterEncoding=UTF-8. Esto se hizo para que el texto se mostrara más o menos igual en cualquier sistema. Sin embargo, si es demasiado vago para jugar con la base de datos y realmente quiere husmear en el código de trabajo, existe una solución rápida: 1. Agregue la siguiente dependencia a build.gradle:

	implementation 'com.h2database:h2:2.1.214'
2. En application.yml necesitas editar varias propiedades, daré un ejemplo completo de la sección Spring para simplificar:

spring:
  application:
    name: "employee-management-service"
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.H2Dialect
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:file:./mydb
    username: sa
    password:
La base de datos se almacenará en la carpeta del proyecto, en un archivo llamado mydb . Pero recomendaría instalar una base de datos completa 😉 Artículo útil sobre el tema: Spring Boot con base de datos H2 Por si acaso, proporcionaré una versión completa de mi build.gradle para eliminar discrepancias en las dependencias:

plugins {
	id 'org.springframework.boot' version '2.7.2'
	id 'io.spring.dependency-management' version '1.0.12.RELEASE'
	id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'mysql:mysql-connector-java:8.0.30'
	compileOnly 'org.projectlombok:lombok'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
	useJUnitPlatform()
}
El sistema está listo para iniciarse: API REST u otra tarea de prueba.  - 5 puede comprobarlo enviando una solicitud GET desde cualquier programa adecuado a nuestro punto final. En este caso particular, un navegador normal servirá, pero en el futuro necesitaremos Postman. API REST u otra tarea de prueba.  - 6 Sí, de hecho, todavía no hemos implementado ninguno de los requisitos comerciales, pero ya tenemos una aplicación que se inicia y se puede ampliar a la funcionalidad requerida. Continuación: API REST y validación de datos
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION