# Notions brief "Spring" (ATLAS) ## Client-serveur Un client c'est un logiciel qui va communiquer avec un server en envoyant des requêtes dans l'attente d'avoir une réponse. Un serveur rend des services aux client (plusieurs clients) en servant les requêtes et va retourner ce qui est emandé dans des réponses. L'architecture en "trois couches" s'appuie sur une architecture client-serveur. Les trsoi couches sont : - Présentation (interactions avec l'utilisateur) : cliente de la logique métier (serveur) - Logique métier (lien avec la base de données, traitements métiers) : cliente de la couche données (serveur) - Données (stockage et la récupération des données) : seveur ## API Rest API, Application Programming Interface. Rest, REpresentational State Transfer. Principe d'architecture logicel, qui s'appuie un protocole simple (HTTP), sur un format d'échange de données standard (JSON) pour facilité la communication client-serveur dans le contexte du Web. Pour faire une requête à une API Rest : - URL : où se situe le serveur et identifie la resource à consommer (consume) - Verbe (méthode HTTP) : le type d'action sur la resource (GET, POST, PUT, PATCH, DELETE) - Entêtes (headers) : type du contenu au moins (Content-Type = application/json) - Corps (body) : la resource (optionnelle) à envoyer au serveur Le serveur retourne une réponse (HTTP) au client : - Code statut : informe le client du succès ou non des traitements demandés sur la resource côté serveur (400, 404, 405, 200, 201, 500, 401, 403) - Entêtes (headers) : type du contenu retourné (exemple) - Corps (body) : la resource (optionnelle) retournée au client Un des grands intérêts de cette approche c'est de faire abstraction côté client de l'implémentation côté serveur. # Fondamentaux Spring ## Spring vs Spring boot Spring c'est un framework. Offre un cadre de travail, une manière de faire, en suivant des conventions (organisation des classes et autre fichier de configuration, nommage). Un framework s'appuie sur un langage (Java), des bibliothèques (dépendances). Spring boot c'est une boîte à outils. On peut faire une application Spring sans Spring boot. Aide le dévelopeeur à initialiser, configurer un projet. Deux principales caractéristiques : - Auto-configuration: moins de dépendances de déclarées, moins de configuration à faire. Cependant, exclure les dépendances non utiles si c'est le cas, ou désactiver les configurations par défaut inutiles - Serveur Web (HTTP) embarqué (par défaut Tomcat), ce qui évite d'avoir un serveur à part (standalone) pour y déployer son application (API Rest) ## Structure d'un projet Spring boot Arborescence de dossiers et de sous dossiers avec comme dossier racine le projet : - `src/main/java` : classes avec une classe en particulier, la classe application (avec la méthode main) - Classes sont organisées dans des packages - Nom du package racine composé ainsi : `co.simplon` (nom de domaine inversé de l'entreprise) `atlas.api` (nom du projet) - On retrouve la classe application dans le package racine - Dans un contexte Spring boot il faut que toutes les classes de l'application soient localisées dans le package racine ou (et c'est mieux) dans des sous packages spécialisés en fonction des responsabiltiés des classes ## Controller Un controller ça contrôle. Notamment contrôler les données en entrées et "qui peut faire quoi". Il expose des endpoints pour accéder aux services depuis les clients. Une classe annotée `@RestController` indique à Spring au démarrage de l'application, que la classe expose des routes et que par défaut elle traite des données au format JSON dans les requêtes et les réponses. ### Request mappings Un request mapping c'est faire le lien entre l'appel HTTP (verbe + url) et une méthode à exécuter côté serveur. ```java= @RestController @RequestMapping("/persons") // GET http://localhost:8080/persons // "/persons": convention nommage Rest (collection de ressources) public class PersonController { // EntityNameController (convention) @PostMapping @ResponseStatus(HttpStatus.CREATED) // Par défaut = 200 OK public void create(@RequestBody Person person) { // Sauvegarder person dans la base } @GetMapping("/{id}") // GET http://localhost:8080/persons/1 public Person getById(@PathVariable("id") UUID id) { Person person = null; // Récupérer l'entité dans la base return person; } } ``` **Note sur `@RequestBody`** : cette annotation apposée sur le paramètre de la méthode d'une route vient indiquer à Spring que les données pour construire un objet Java correspondant à chaque requête se trouvent dans le corps de la requête. **Note sur `ResponseEntity`** : cette classe est d'une part historique dans Spring, même si elle n'est pas dépréciée il n'est pas souvent utile aujourd'hui de l'utiliser. C'est une classe qui permet de construire des réponses complexes avec des informations en plus de celle par défaut, notamment des entêtes HTTP. La pratique est de retourner directement l'entité ou l'ensemble d'entités sans la ou les envelopper dans un `ResponseEntity`. ### Entité Person Respect des conventions "JavaBeans". Champs `private`, constructeur `public` sans argument, des getters et setters pour les champs (avec les mêmes types). Préfixes `get` ou `set` suivis du nom du champ en camelCase. ```java= public class Person { private UUID id; private String firstname; private String lastname; public Person() { } public String getFirstname() { return firstname; } public void setFirstname(String firstname) { this.firstname = firstname; } // Autres getters et setters } ``` ### La "conversion" du JSON en entité (exemple lors d'un POST) Spring extrait le JSON du corps de la requête HTTP, construit un objet Person puis cherche les correspondances entre le nom des propriétés du JSON et le nom des champs dans la classe de l'entité en s'appuyant sur le respect des conventions "JavaBeans". Autrement formulé, il recherche un setter associé au champ, exemple : - JSON: `{ "firstname": "Toto"}` - Spring : `person.setFirstname("Toto");` Si les conventions de nommage ne sont pas respectées, il n'y a pas d'erreur, Spring va tout simplement ne pas appeler le setter car il ne le trouve pas, exemple : - JSON: `{ "firstName": "Toto"}` - Spring : `person.setFirstName("Toto"); // N'existe pas ! Ignoré par Spring` Si les types ne correspondent pas, Spring va essayer de convertir ce qui peut l'être, s'il n'y arrive, une erreur se produit. **Morale de l'histoire** : respecter les conventions de nommage, être très attentif au nommage, côté client ET côté serveur ! L'opération de conversion d'une chaîne de caractères (du texte) en objet (binaire) s'appelle la désérialisation. ### La "conversion" de l'entité en JSON (exemple lors d'un GET) Spring fait l'opération inverse en s'appuyant toujours sur les conventions "JavaBeans". Il va lire l'entité et écrire un JSON avec les propriétés qui ont un getter, en l'absence de getter la donnée sera absente du JSON dans la réponse. L'opération de conversion d'un objet (binaire) en chaîne de caractères (du texte) s'appelle la sérialisation. ## Service Un service ça rend service, le service métier pour l'utilisateur. C'est dans les classes service qu'est implémentée la logique métier. Motivation : diviser pour mieux régner. ```java= @Service public class PersonService { public void create(Person person) { // Implémenter la logique métier pour créer une personne // Intéragir avec la "Database" } public Person getById(UUID id) { // Implémnter la logique métier pour récupérer une personne // Intéragir avec la "Database" } } ``` Changements dans le controller : ```java= @RestController @RequestMapping("/persons") public class PersonController { // final = constante // une fois que la variable a été affectée, elle ne plsu changer private final PersonService service; // Scanner le projet, trouver le controller et le service // Lire la class PersonController et trouver un constructeur avec argument (de type PersonService) // PersonService service = new PersonService(); // PersonController controller = new PersonController(service); // Injection de dépendance (design pattern) // la classe PersonController a une dépendance vers PersonService // Spring qui injecte l'ojet service dans le controller public PersonController(PersonService service) { this.service = service; } @PostMapping @ResponseStatus(HttpStatus.CREATED) public void create(@RequestBody Person person) { service.create(person); // Déléguer suite des traitements (logique métier) } @GetMapping("/{id}") public Person getById(@PathVariable("id") UUID id) { return service.getById(id); // Déléguer suite des traitements (logique métier) } } ``` ## Data Transfer Objects (DTO) et validation des données en entrée Note sur les entités : - Un objet qui représente les données métiers que les utilisateurs manipulent pour l'activité et permettre de faire des traitements dessus - Ce sont des classes qui seront mappées (mises en correspondance) avec les tables d'une base de données dans le but de sauvegarder (persister) durablement ### DTO Un design pattern qui vient répondre à deux problématiques courantes dans une architecture client-serveur : - Limiter le nombre des appels entre un client et un serveur - Ne communiquer en input ou en output que les données nécessaires pour le service en question Conventions de nommage : - Un nom de classe qui indique ce que représente les données - Un suffixe "in" ou "dto" (pour les outputs "out") ```java= public class CreatePerson { private String firstname; private String lastname; public CreatePerson() { // } // getters + setters } ``` > Eclipse tip for JavaBean classes: penser à demander à votre IDE de générer les getters et les setters, et le ou les constructeurs. ### Validation Spring s'appuie sur un framework historique de validation, l'équipe n'a pas réinventé la roue. Les bibliothèques correspondantes doivent être ajoutées dans les dépendances, elles n'y sont pas par défaut. Dans un projet Spring boot il faut ajouter le starter validation. Sans framework de validation: ```java= @PostMapping public void create(CreatePerson inputs) { if(inputs.getFirstname == null) { // Ajouter/gérer l'erreur } if(inputs.getLastname() == null) { // Ajouter/gérer l'erreur } } ``` Validation avec un framework: ```java= public class CreatePerson { @NotNull private String firstname; @Past @NotNull private LocalDate birthdate; } ``` Changements dans le controller : ```java= @RestController @RequestMapping("/persons") public class PersonController { private final PersonService service; public PersonController(PersonService service) { this.service = service; } @PostMapping @ResponseStatus(HttpStatus.CREATED) public UUID create(@RequestBody @Valid CreatePerson inputs) { // La méthode est exécutée si et seulement si il n'y a aucune violation de contrainte return service.create(inputs); } } ``` Changements dans le service : ```java= @Service public class PersonService { public void create(CreatePerson inputs) { // On a la "garantie" (sauf bug) que les iputs sont valides // On peut en toute sérénité appliquer notre logique métier à partir du DTO (les inputs validées) Person person = new Person(); person.setFirstname(inputs.getFirstname()); person.setLastname(inputs.getLastname()); // ... avec chaque propriétés de l'objet Database.save(person); } } ```