Developpez.com - Rubrique Java

Le Club des Développeurs et IT Pro

Développement full stack d'une application web avec Angular 7 et Spring Boot 2,

Un tutoriel de Georges Kemayo

Le 2019-11-01 00:43:28, par Georges KEMAYO, Membre chevronné
Bonjour,

L'article a pour but de présenter la conception, l'architecture et le développement full stack d'une application web en s'appuyant sur les technologies Java, Spring Boot et Angular. Plusieurs concepts sont abordés et expliqués dans l'article par la mise en oeuvre d'un exemple concret d'une application.

https://gkemayo.developpez.com/tutor...spring-boot-2/

Qu'en pensez-vous ?

Retrouvez les meilleurs cours et tutoriels pour apprendre le développement Web avec Spring Boot
  Discussion forum
33 commentaires
  • Georges KEMAYO
    Membre chevronné
    Envoyé par marc.collin
    bravo pour le tutoriel

    en couplant tes entités, tu brises un peu le concept de package by feature architecture
    il aurait été possible de passer par des id, sinon de copier les données et de passer par un événement pour les mettres à jour, suppression...
    Bonjour Marc.collin,

    Tu as parfaitement raison, la contrainte du package by feature vient vraiment des "ressources" qui sont partagées entre domaines. J'étais bien conscient que je n'ai pas fait un aboutissement de cette architecture dans l'article. Le but est dans un premier temps de sensibiliser le lecteur sur cette architecture qui apporte son lot de concepts, d'avantages et d'inconvénients. Si le lecteur s'y intéresse, il ira chercher plus d'infos.

    Les solutions que l'on peut proposer pour adresser ce problème sont multiples, chacune possédant ces avantages et inconvénients. Pour la solution que tu proposes de copier les données et de d'utiliser l'event sourcing pour les mettre à jour, c'est bien, mais ça passe par une duplication de données et la mise en place de l'event sourcing pour mettre à jour chaque domaine. C'est complexe et impossible d'expliquer tout cela dans un seul et même article .

    Je peux même aller plus loin en disant que l'on peut même mettre en place le pattern Capture Data Change couplé à Kafka qui se chargera de mettre à jour la/les tables de chaque domaine. Là encore c'est très complexe et trop de travail rien que pour respecter le principe du package by feature.

    Il vaut mieux s'y investir sur tout ce que nous venons de citer lorsqu'on est vraiment en contexte Microservice ou une vraie architecture domaine driven.

    D'autres son de cloche, disent de sortir exceptionnellement les "ressources" partagées (en veillant à ce que cela ne devienne pas un fourre tout) dans un package "common" afin que les différents domaines se les partagent. Ce n'est pas moins intelligible, après tout c'est une organisation dans un seul et unique projet.

    Cordialement,
    Georges
  • jeffray03
    Membre chevronné
    Salut,
    tu effectues sans doute une Insertion avec la meme clé primaire dans la table Categorie. As tu declaré cette clé Auto_increment ?

    Eric
  • parchemal
    Membre averti
    Bonjour et Bravo pour ce tutoriel,

    Voici quelques remarques:
    • L'architecture que vous présentez me semble bonne, mais adaptée pour des applications ayant très peu de classes ou de fonctionnalités , qu'en dites-vous ?
    • Par contre faites attention à vos contrôleurs. Car il y a du code métier dans vos contrôleurs puisque vous n'exploitez pas bien les réponses HttpStatus et ResponseEntity


    Voici un exemple de votre contrôleur avec du traitement métier

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
        @PostMapping("/addCustomer")
        public ResponseEntity<CustomerDTO> createNewCustomer(@RequestBody CustomerDTO customerDTORequest) {
            //, UriComponentsBuilder uriComponentBuilder
            Customer existingCustomer = customerService.findCustomerByEmail(customerDTORequest.getEmail());//pas besoin
            if (existingCustomer != null) { //pas besoin
                return new ResponseEntity<CustomerDTO>(HttpStatus.CONFLICT); // pas besoin, ce traitement doit être fait par le service qui gère la sauvegarde, il faut renvoyer directement l'exception depuis le service
            }
            Customer customerRequest = mapCustomerDTOToCustomer(customerDTORequest);
            customerRequest.setCreationDate(LocalDate.now());
            Customer customerResponse = customerService.saveCustomer(customerRequest);
            if (customerResponse != null) {//idem, pas besoin
                CustomerDTO customerDTO = mapCustomerToCustomerDTO(customerResponse);
                return new ResponseEntity<CustomerDTO>(customerDTO, HttpStatus.CREATED);
            }
            return new ResponseEntity<CustomerDTO>(HttpStatus.NOT_MODIFIED);
        }
    Voici un exemple de solution pour le service que je propose (Classe de gestion des exceptions à créer CustomerResourceException):

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public Customer saveOrUpdateCustomer(Customer customer) throws CustomerResourceException{
    	try{
    		return customerRepository.save(customer);
    	} catch(DataIntegrityViolationException ex){
    		logger.error("customer non existant", ex);
    		throw new CustomerResourceException("DuplicateValueError", "Un customer existe déjà avec le compte : "+customer.getLogin(), HttpStatus.CONFLICT);
    	} catch (CustomerResourceException e) {
    		logger.error("Utilisateur non existant", e);
    		throw new CustomerResourceException("CustomerNotFound", "Aucun utilisateur avec l'identifiant: "+customer.getId(), HttpStatus.NOT_FOUND);
    	} catch(Exception ex){
    		logger.error("Erreur technique de création ou de mise à jour de l'utilisateur", ex);
    		throw new CustomerResourceException("SaveOrUpdateUserError", "Erreur technique de création ou de mise à jour du customer: "+customer.getLogin(), HttpStatus.INTERNAL_SERVER_ERROR);
    	}
    }
    Et voici ce que devient le contrôleur:

    Code :
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
        @PostMapping("/addCustomer")
        public ResponseEntity<CustomerDTO> createNewCustomer(@RequestBody CustomerDTO customerDTORequest) {
            //, UriComponentsBuilder uriComponentBuilder
           try{
                Customer customerRequest = mapCustomerDTOToCustomer(customerDTORequest);
                customerRequest.setCreationDate(LocalDate.now());
                Customer customerResponse = customerService.saveCustomer(customerRequest);
                CustomerDTO customerDTO = mapCustomerToCustomerDTO(customerResponse);
                return new ResponseEntity<CustomerDTO>(customerDTO, HttpStatus.CREATED);
           } catch (CustomerResourceException ex) {
             //Traitez vos erreurs ici par un log si nécessaire
            return new ResponseEntity<CustomerDTO>(ex.getHttpStatus()); //Ici tu gères à la fois plusieurs types d'erreurs comme HttpStatus.NOT_MODIFIED, HttpStatus.CONFLICT etc , au lieu de forcer la réponse à HttpStatus.NOT_MODIFIED uniquement
           }  catch (Exception  ex) {
             //Traiter les erreurs techniques ici par un log
           return new ResponseEntity<CustomerDTO>(HttpStatus.INTERNAL_SERVER_ERROR); // un cas typique d'erreur ici serait une erreur liée au mapping, ou autres erreurs non gérées par le service
           }
            
        }
    Voici un exemple du constructeur de ton Exception

    Code :
    1
    2
    3
    4
    5
        public CustomerResourceException(String errorCode, String message, HttpStatus httpStatus) {
            super(message);
            this.errorCode = errorCode;
            this.httpStatus = httpStatus;
        }
    Note: Penser aux getter/setter dans l'exception

    Au final, pas besoin d'une requête supplémentaire customerService.findCustomerByEmail(xxx) dans le createNewCustomer car, en cas conflit, le service sait le code d'erreur à retourner

    Bon courage !!!!
  • marc.collin
    Membre émérite
    bravo pour le tutoriel

    en couplant tes entités, tu brises un peu le concept de package by feature architecture
    il aurait été possible de passer par des id, sinon de copier les données et de passer par un événèment pour les mettres à jour, suppression...
  • momjunior
    Membre actif
    Bonjour

    Très bon tutoriel, cependant je rencontre quelques problèmes lors de l'exécution du code source téléchargeable à la fin de l'article.

    Après avoir installé les dépendances avec npm install, j'exécute ng serve --open. Et là je reçois le message d'erreur suivant:

    Proxy config file D:\workspace-spring_tool_suite\library-ui-master\src\proxy.conf.json does not exist.
    Error: Proxy config file D:\workspace-spring_tool_suite\library-ui-master\src\proxy.conf.json does not exist.
    Je ne sais pas si c'est une erreur de votre part, mais il a fallu que je change le chemin menant vers proxy.conf.json dans le fichier angular.json pour régler le problème:
    Code :
    1
    2
    "proxyConfig": "proxy.conf.json"
    au lieu de:
    Code :
    1
    2
    "proxyConfig": "src/proxy.conf.json"
    Ensuite lorsque j'accède à la page book-page, je reçois le message d'erreur suivant:

    Code :
    An error occurs when retreiving categories data
    Ci-dessous une capture d'écran:



    Voici l'erreur au niveau du log:

    Error occurred while trying to proxy request /library/rest/category/api/allCategories from localhost:4200 to http://localhost:8082 (ECONNREFUSED) (https
    ://nodejs.org/api/errors.html#errors_common_system_errors)
    Merci
  • momjunior
    Membre actif
    finalement ça marche.

    Je lançais le projet via l'option Spring Boot App, mais j'ai par la suite opté de le lancer via le server tomcat installé, tout en changer le port 8080 en 8082, et ça a marché.
  • DarkChyper
    Candidat au Club
    Bonjour,

    Le tuto est vraiment bien fait.

    J'ai juste un petit soucis avec la base de données H2. J'ai fait toute la partie back et j'ai voulu tester.
    Au premier démarrage de tomcat, pas de soucis.
    J'ai fais des modifications et depuis impossible de relancer l'application :

    Code :
    1
    2
    3
    Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Violation d'index unique ou clé primaire: "PUBLIC.PRIMARY_KEY_3 ON PUBLIC.CATEGORY(CODE) VALUES 1"
    Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_3 ON PUBLIC.CATEGORY(CODE) VALUES 1"; SQL statement:
    insert into category values ('INF', 'Informatique') [23505-200]
    Je n'arrive pas à drop les données de la table category :/ j'ai essayé d'ajouté la commande
    Code :
    truncate table category;
    dans le fichier sql chargé au début mais cela me retourne une exception ..

    Edit : j'ai augmenté le cache de la log et je vois qu'en fait le soucis provient d'un drop qui ne se fait pas sur mes tables à cause de FK qui ne sont pas supprimées...
    Code :
    1
    2
    Impossible de supprimer "BOOK" car "FK88C0YDLO57PCGP137TNTRGQX1" dépend de lui
    Cannot drop "BOOK" because "FK88C0YDLO57PCGP137TNTRGQX1" depends on it; SQL statement: drop table book if exists [90107-200]
  • Georges KEMAYO
    Membre chevronné
    Envoyé par momjunior
    finalement ça marche.

    Je lançais le projet via l'option Spring Boot App, mais j'ai par la suite opté de le lancer via le server tomcat installé, tout en changer le port 8080 en 8082, et ça a marché.
  • Georges KEMAYO
    Membre chevronné
    Envoyé par DarkChyper
    Bonjour,

    Le tuto est vraiment bien fait.

    J'ai juste un petit soucis avec la base de données H2. J'ai fait toute la partie back et j'ai voulu tester.
    Au premier démarrage de tomcat, pas de soucis.
    J'ai fais des modifications et depuis impossible de relancer l'application :

    Code :
    1
    2
    3
    Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Violation d'index unique ou clé primaire: "PUBLIC.PRIMARY_KEY_3 ON PUBLIC.CATEGORY(CODE) VALUES 1"
    Unique index or primary key violation: "PUBLIC.PRIMARY_KEY_3 ON PUBLIC.CATEGORY(CODE) VALUES 1"; SQL statement:
    insert into category values ('INF', 'Informatique') [23505-200]
    Je n'arrive pas à drop les données de la table category :/ j'ai essayé d'ajouté la commande
    Code :
    truncate table category;
    dans le fichier sql chargé au début mais cela me retourne une exception ..

    Edit : j'ai augmenté le cache de la log et je vois qu'en fait le soucis provient d'un drop qui ne se fait pas sur mes tables à cause de FK qui ne sont pas supprimées...
    Code :
    1
    2
    Impossible de supprimer "BOOK" car "FK88C0YDLO57PCGP137TNTRGQX1" dépend de lui
    Cannot drop "BOOK" because "FK88C0YDLO57PCGP137TNTRGQX1" depends on it; SQL statement: drop table book if exists [90107-200]

    Salut DarkChyper,

    Normalement cela ne devrait pas poser de problème si tu arrêtais toute l'application via ton Tomcat en faisant un shutdown en invite de commande dans le dossier bin.
    Puis en redémarrant avec start. Puisque l'appli utilise une base embarquée, tout devrait se réinitialiser au redemarrage.

    Assures toi juste qu'aucun processus lié ne tourne au préalable sur ta machine.

    Cordialement,
    Georges
  • thierryler
    Rédacteur
    Bravo et merci pour cet excellent article