JavaRush /Blog Java /Random-FR /API REST et une autre tâche de test.
Денис
Niveau 37
Киев

API REST et une autre tâche de test.

Publié dans le groupe Random-FR
Partie I : Début Par où commencer ? Curieusement, mais d'après les spécifications techniques. Il est extrêmement important de s'assurer qu'après avoir lu les termes de référence soumis, vous comprenez parfaitement ce qui y est écrit et ce que le client attend. Premièrement, cela est important pour la poursuite de la mise en œuvre, et deuxièmement, si vous ne mettez pas en œuvre ce que l’on attend de vous, cela ne sera pas à votre avantage. Pour éviter de gaspiller de l’air, esquissons une spécification technique simple. Donc, je veux un service auquel je peux envoyer des données, elles seront stockées sur le service et me seront restituées à volonté. Je dois également pouvoir mettre à jour et supprimer ces données si nécessaire . Quelques phrases ne semblent pas claires, n'est-ce pas ? Comment puis-je y envoyer des données ? Quelles technologies utiliser ? Dans quel format seront ces données ? Il n'y a pas non plus d'exemples de données entrantes et sortantes. Conclusion - les spécifications techniques sont déjà mauvaises . Essayons de reformuler : nous avons besoin d'un service capable de traiter les requêtes HTTP et de travailler avec les données transférées. Ce sera la base de données des dossiers du personnel. Nous aurons des employés, ils sont répartis par départements et spécialités, les employés pourront se voir attribuer des tâches. Notre tâche est d'automatiser le processus de comptabilisation des employés embauchés, licenciés et transférés, ainsi que le processus d'attribution et d'annulation de tâches à l'aide de l'API REST. En phase 1, nous travaillons actuellement uniquement avec des employés. Le service doit avoir plusieurs points de terminaison pour fonctionner avec lui : - POST /employé - Requête POST, qui doit accepter un objet JSON avec des données sur l'employé. Cet objet doit être enregistré dans la base de données ; si un tel objet existe déjà dans la base de données, les informations dans les champs doivent être mises à jour avec de nouvelles données. - GET /employee - Requête GET qui renvoie la liste complète des employés enregistrés dans la base de données - DELETE - DELETE /employee pour supprimer un employé spécifique Modèle de données des employés :
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  	//List of tasks, not needed for Phase 1
  ]
}
Partie II : Outils pour le travail Ainsi, la portée du travail est plus ou moins claire, mais comment allons-nous le faire ? Évidemment, ces tâches dans le test sont données avec quelques objectifs d'application, pour voir comment vous codez, pour vous forcer à utiliser Spring et à travailler un peu avec la base de données. Eh bien, faisons ça. Nous avons besoin d'un projet SpringBoot avec le support de l'API REST et d'une base de données. Sur le site https://start.spring.io/, vous trouverez tout ce dont vous avez besoin. API REST ou une autre tâche de test.  - 1 Vous pouvez sélectionner le système de build, la langue, la version SpringBoot, définir les paramètres d'artefact, la version Java et les dépendances. Cliquer sur le bouton Ajouter des dépendances fera apparaître un menu caractéristique avec une barre de recherche. Les premiers candidats pour les mots rest et data sont Spring Web et Spring Data - nous les ajouterons. Lombok est une bibliothèque pratique qui vous permet d'utiliser des annotations pour vous débarrasser de kilomètres de code avec les méthodes getter et setter. En cliquant sur le bouton Générer, nous recevrons une archive avec le projet qui peut déjà être décompressée et ouverte dans notre IDE préféré. Par défaut, nous recevrons un projet vide, avec un fichier de configuration pour le système de build (dans mon cas, ce sera gradle, mais avec Maven, il n'y a pas de différence fondamentale, et un fichier de démarrage printanier). Les personnes attentives pourraient prêter attention à deux API REST ou une autre tâche de test.  - 2 choses . Tout d’abord, j’ai deux fichiers de paramètres application.properties et application.yml. Par défaut, vous obtiendrez exactement les propriétés - un fichier vide dans lequel vous pouvez stocker les paramètres, mais pour moi le format yml semble un peu plus lisible, je vais maintenant montrer une comparaison : malgré le fait que l' API REST ou une autre tâche de test.  - 3 image de gauche semble plus compacte , il est facile de constater une grande quantité de duplication dans le chemin des propriétés. L'image de droite est un fichier yml standard avec une structure arborescente assez facile à lire. J'utiliserai ce fichier plus tard dans le projet. La deuxième chose que les gens attentifs pourraient remarquer est que mon projet comporte déjà plusieurs packages. Il n’y a pas encore de code sensé, mais cela vaut la peine de les parcourir. Comment est rédigée une candidature ? Ayant une tâche spécifique, nous devons la décomposer - la diviser en petites sous-tâches et commencer leur mise en œuvre cohérente. Qu’est-ce qu’on attend de nous ? Nous devons fournir une API que le client peut utiliser ; le contenu du package du contrôleur sera responsable de cette partie de la fonctionnalité. La deuxième partie de l'application est la base de données – le package de persistance. Nous y stockerons des éléments tels que des entités de base de données (Entités) ainsi que des référentiels - des interfaces Spring spéciales qui vous permettent d'interagir avec la base de données. Le package de services contiendra des classes de services. Nous parlerons ci-dessous de ce qu'est le service de type Spring. Et enfin, le package utils. Des classes utilitaires avec toutes sortes de méthodes auxiliaires y seront stockées, par exemple des classes pour travailler avec la date et l'heure, ou des classes pour travailler avec des chaînes, et qui sait quoi d'autre. Commençons par implémenter la première partie de la fonctionnalité. Partie III : Contrôleur
@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());
    }
}
Maintenant, notre classe EmployeeController ressemble à ceci. Il y a un certain nombre de choses importantes auxquelles il convient de prêter attention ici. 1. Annotations au-dessus de la classe, le premier @RestController indique à notre application que cette classe sera un point final. 2. @RequestMapping, bien que non obligatoire, est une annotation utile : elle vous permet de définir un chemin spécifique pour le point de terminaison. Ceux. pour y parvenir, vous devrez envoyer des requêtes non pas à localhost:port/employee, mais dans ce cas à localhost:8086/api/v1/employee En fait, d'où viennent ces api/v1 et employe ? Depuis notre application.yml Si vous regardez bien, vous y trouverez les lignes suivantes :
application:
  endpoint:
    root: api/v1
    employee: employee
    task: task
Comme vous pouvez le voir, nous avons des variables telles que application.endpoint.root et application.endpoint.employee, c'est exactement ce que j'ai écrit dans les annotations, je recommande de rappeler cette méthode - cela permettra d'économiser beaucoup de temps pour développer ou réécrire le fonctionnalité - il est toujours plus pratique d'avoir tout dans la configuration et de ne pas coder en dur l'ensemble du projet. 3. @RequiredArgsConstructor est une annotation Lombok, une bibliothèque pratique qui vous permet d'éviter d'écrire des choses inutiles. Dans ce cas, l'annotation équivaut au fait que la classe aura un constructeur public avec tous les champs marqués comme final
public EmployeeController(EmployeeService employeeService) {
    this.employeeService=employeeService;
}
Mais pourquoi devrions-nous écrire une telle chose si une seule annotation suffit ? :) Au fait, félicitations, ce domaine final le plus privé n'est rien de plus que la fameuse injection de dépendances. Passons à autre chose, en fait, quel genre de domaine est employéService ? Ce sera l'un des services de notre projet qui traitera les demandes pour ce point de terminaison. L'idée ici est très simple. Chaque classe doit avoir sa propre tâche et ne doit pas être surchargée d'actions inutiles. S’il s’agit d’un responsable du traitement, laissez-le se charger de recevoir les requêtes et d’envoyer les réponses, mais nous préférons confier le traitement à un service supplémentaire. La dernière chose qui reste dans cette classe est la seule méthode qui renvoie une liste de tous les employés de notre entreprise utilisant le service mentionné ci-dessus. La liste elle-même est enveloppée dans une entité appelée ResponseEntity. Je fais cela pour qu'à l'avenir, si nécessaire, je puisse facilement renvoyer le code de réponse et le message dont j'ai besoin, que le système automatisé peut comprendre. Ainsi, par exemple, ResponseEntity.ok() renverra le 200ème code, qui dira que tout va bien, mais si je retourne, par exemple
return ResponseEntity.badRequest().body(Collections.emptyList());
alors le client recevra le code 400 - mauvaise demande et une liste vide dans la réponse. Généralement, ce code est renvoyé si la demande est incorrecte. Mais un seul contrôleur ne nous suffira pas pour démarrer l'application. Nos dépendances ne nous permettront pas de faire cela, car encore faut-il avoir une base :) Bon, passons à la partie suivante. Partie IV : persistance simple Puisque notre tâche principale est de lancer l'application, nous nous limiterons pour l'instant à quelques talons. Vous avez déjà vu dans la classe Controller que nous renvoyons une liste d'objets de type Employee, ce sera notre entité pour la base de données. Créons-le dans le package demo.persistence.entity . À l'avenir, le package d'entités pourra être complété par d'autres entités de la base de données.
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
}
Il s'agit d'une classe aussi simple qu'une porte, dont les annotations disent exactement ce qui suit : c'est une entité de base de données @Entity, c'est une classe avec des données @Data - Lombok. L'utile Lombok créera pour nous tous les getters, setters, constructeurs nécessaires - un rembourrage complet. Eh bien, une petite cerise sur le gâteau est @Accessors(chain = true) En fait, il s'agit d'une implémentation cachée du modèle Builder. Supposons que vous ayez une classe avec un ensemble de champs que vous souhaitez attribuer non pas via le constructeur, mais par des méthodes. Dans un ordre différent, peut-être pas tous en même temps. Vous ne savez jamais quel type de logique sera présent dans votre application. Cette annotation est votre clé pour cette tâche. Regardons:
public Employee createEmployee() {
    return new Employee().setName("Peter")
        				.setAge("28")
        				.setDepartment("IT");
}
Supposons que nous ayons tous ces champs dans notre classe😄Vous pouvez les attribuer, vous ne pouvez pas les attribuer, vous pouvez les mélanger par endroits. Dans le cas de seulement 3 propriétés, cela ne semble pas être quelque chose d'exceptionnel. Mais il existe des classes avec un nombre beaucoup plus grand de propriétés, par exemple 50. Et écrivez quelque chose comme
public Employee createEmployee() {
    return new Employee("Peter", "28", "IT", "single", "loyal", List.of(new Task("do Something 1"), new Task ("do Something 2")));
}
Ça n'a pas l'air très joli, n'est-ce pas ? Nous devons également suivre strictement l'ordre d'ajout des variables conformément au constructeur. Cependant, je m'éloigne du sujet, revenons au fait. Nous avons maintenant un champ (obligatoire) - un identifiant unique. Dans ce cas, il s'agit d'un numéro de type Long, qui est généré automatiquement lors de l'enregistrement dans la base de données. En conséquence, l'annotation @Id nous indique clairement qu'il s'agit d'un identifiant unique ; @GeneratedValue est responsable de sa génération unique. Il convient de noter que @Id peut être ajouté à des champs qui ne sont pas générés automatiquement, mais le problème de l'unicité devra alors être traité manuellement. Quel pourrait être un identifiant unique pour un employé ? Eh bien, par exemple, nom complet + département... cependant, une personne a des homonymes complets, et il y a une chance qu'elle travaille dans le même département, petite, mais il y en a - cela signifie que la décision est mauvaise. Il serait possible d'ajouter un tas d'autres champs, comme la date d'embauche, la ville, mais tout cela, me semble-t-il, complique trop la logique. Vous vous demandez peut-être comment est-il possible qu'un ensemble de champs soient uniques à la fois ? Je réponds - peut-être. Si vous êtes curieux, vous pouvez rechercher sur Google des choses telles que @Embeddable et @Embedded. Eh bien, nous en avons terminé avec l'essentiel. Nous avons maintenant besoin d'un référentiel simple. Il ressemblera à ceci:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {

}
Oui c'est tout. Juste une interface, nous l'avons appelée EmployeeRepository, elle étend JpaRepository qui a deux paramètres typés, le premier est responsable du type de données avec lequel il travaille, le second du type de clé. Dans notre cas, il s'agit de Employee et Long. C'est suffisant pour l'instant. La touche finale avant de lancer l'application sera notre service :
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;

    public List<Employee> getAllEmployees() {
        return List.of(new Employee().setId(123L));
    }
}
Il existe le RequiredArgsConstructor déjà familier et la nouvelle annotation @Service - c'est ce qui désigne généralement la couche de logique métier. Lors de l'exécution d'un contexte Spring, les classes marquées de cette annotation seront créées en tant que Beans. Lorsque dans la classe EmployeeController nous avons créé la propriété finale EmployeeService et attaché RequiredArgsConstructor (ou créé un constructeur à la main) Spring, lors de l'initialisation de l'application, elle trouvera cet endroit et nous glissera un objet de classe dans cette variable. La valeur par défaut ici est Singleton - c'est-à-dire il y aura un seul objet pour tous ces liens ; ceci est important à prendre en compte lors de la conception de l'application. En fait, c’est tout, l’application peut être lancée. N'oubliez pas de saisir les paramètres nécessaires dans la configuration. API REST ou une autre tâche de test.  - 4 Je ne décrirai pas comment installer une base de données, créer un utilisateur et la base de données elle-même, mais je noterai simplement que dans l'URL j'utilise deux paramètres supplémentaires - useUnicore=true et CharacterEncoding=UTF-8. Cela a été fait pour que le texte soit affiché plus ou moins également sur n'importe quel système. Cependant, si vous êtes trop paresseux pour bricoler la base de données et que vous voulez vraiment fouiller dans le code de travail, il existe une solution rapide : 1. Ajoutez la dépendance suivante à build.gradle :
implementation 'com.h2database:h2:2.1.214'
2. Dans application.yml, vous devez modifier plusieurs propriétés, je vais donner un exemple complet de la section spring pour plus de simplicité :
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 données sera stockée dans le dossier du projet, dans un fichier appelé mydb . Mais je recommanderais d'installer une base de données à part entière 😉 Article utile sur le sujet : Spring Boot avec la base de données H2 Juste au cas où, je fournirai une version complète de mon build.gradle pour éliminer les divergences dans les dépendances :
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()
}
Le système est prêt à démarrer : API REST ou une autre tâche de test.  - 5 vous pouvez le vérifier en envoyant une requête GET depuis n'importe quel programme approprié à notre point de terminaison. Dans ce cas particulier, un navigateur classique fera l'affaire, mais à l'avenir nous aurons besoin de Postman. API REST ou une autre tâche de test.  - 6 Oui, en fait, nous n'avons encore mis en œuvre aucune des exigences commerciales, mais nous disposons déjà d'une application qui démarre et peut être étendue jusqu'aux fonctionnalités requises. Suite : API REST et validation des données
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION