Dans quelle couche la validation doit-elle être située?

18

Je crée une API Rest à l'aide de Spring Boot et j'utilise Hibernate Validation pour valider les entrées de demande.

Mais j'ai également besoin d'autres types de validation, par exemple lorsque les données de mise à jour doivent être vérifiées, si l'ID de l'entreprise n'existe pas, je veux lever une exception personnalisée.

Cette validation doit-elle se situer au niveau de la couche service ou de la couche contrôleur?

Couche de service:

 public Company update(Company entity) {
    if (entity.getId() == null || repository.findOne(entity.getId()) == null) {
        throw new ResourceNotFoundException("can not update un existence data with id : " 
            + entity.getId());
    }
    return repository.saveAndFlush(entity);
}

Couche de contrôleur:

public HttpEntity<CompanyResource> update(@Valid @RequestBody Company companyRequest) {
    Company company = companyService.getById(companyRequest.getId());
    Precondition.checkDataFound(company, 
        "Can't not find data with id : " + companyRequest.getId());

    // TODO : extract ignore properties to constant

    BeanUtils.copyProperties(companyRequest, company, "createdBy", "createdDate",
            "updatedBy", "updatedDate", "version", "markForDelete");
    Company updatedCompany = companyService.update(company);
    CompanyResource companyResource = companyAssembler.toResource(updatedCompany);
    return new ResponseEntity<CompanyResource>(companyResource, HttpStatus.OK);
}
fdarmanto
la source

Réponses:

8

La couche contrôleur et la couche service exposent certaines interfaces. Les interfaces définissent des contrats sur la façon dont l'interface doit être utilisée. Le contrat signifie généralement quels arguments (et ses types et valeurs) sont attendus, quelles exceptions peuvent être levées, quels effets secondaires sont créés, etc.

Maintenant, votre validation consiste essentiellement à appliquer le contrat de la méthode update () du contrôleur et la méthode update () de la couche de service. Les deux ont un contrat très similaire, il serait donc naturel que la validation (exécution du contrat) soit également courante.

Une façon possible de le faire est de séparer la validation de ce contrat et de le faire appeler dans les deux couches. Ceci est généralement plus clair - chaque classe / méthode applique son propre contrat, mais est souvent peu pratique en raison des performances (accès à la base de données) ou d'autres raisons.

Une autre possibilité consiste à déléguer cette validation à la couche service tout en définissant explicitement le comportement en cas d'échec de la validation dans le contrat de couche service. La couche service retournera généralement une erreur de validation générique (ou une exception de levée) et la couche contrôleur voudra réagir d'une manière spécifique à l'erreur - dans ce cas, nous retournerons 400 Bad request pour signaler que cette demande entrante n'était pas valide.

Dans cette conception, il existe un risque de trop couplage entre la logique métier dans la couche de service (qui devrait être assez générique) et le contrôleur (qui gère la logique d'intégration).

Quoi qu'il en soit, c'est une question assez controversée et 100 personnes répondront avec 100 réponses. Ceci est juste mon point de vue.

qbd
la source
1

L'entrée doit être vérifiée dans la couche de service.

Et "Can't find id" est une condition d'erreur logique. Donc, devrait être jeté de la couche contrôleur.

Cela dépend à nouveau de votre stratification / conception.
Ce qu'une couche service est censée faire et ce qui est attendu de la couche contrôleur.

Sans papier
la source
Une réponse ne devrait pas chercher à obtenir des éclaircissements supplémentaires sur la question. Si la question doit être clarifiée, elle doit être commentée et éventuellement signalée pour clôture si elle n'est pas trop claire. Oui, je me rends compte que vous n'avez la réputation d'aucune de ces actions.
La "vérification des entrées" est ambiguë. Par exemple, je pourrais mettre un attribut obligatoire sur un champ pour indiquer qu'il doit être rempli, mais je pourrais également mettre un attribut personnalisé complexe qui vérifie, par exemple, qu'une valeur de champ est supérieure à une autre. À mon humble avis, la validation de comparaison «sent» beaucoup plus la couche de services aux entreprises que la couche de contrôleur.
JustAMartin
1

Les validations Hibernate sont des vérifications de l' intégrité des données. Afin d'éviter les RuntimeExceptions de bbdd. Ce sont à peu près les mêmes validations que vous devriez contrôler avec des contraintes . Étant donné que seule la couche métier doit alimenter la couche de persistance, vous pouvez (ou non, selon vous) faire confiance à l'exactitude des données provenant de votre couche métier

Je ne mets pas de validations dans les DAO. J'attends des données valides des couches supérieures. En cas d'erreur je délègue au bbdd la responsabilité de connaître son contenu.

Viennent ensuite les validations au niveau de la couche métier. Toutes les validations commerciales se sont concentrées sur le maintien de la cohérence des données, pas sur leur intégrité .

Enfin je fais des validations précédentes sur la couche de contrôle. Ceux liés uniquement à une telle couche.

Vous verrez bientôt quelles validations sont censées être impliquées dans la couche métier. Le plus courant: le contrôle d'identité. Celui-ci peut facilement être implémenté sur les deux couches. Si vous vous attendez à ce que de nombreux contrôleurs ou clients consomment votre couche métier, au lieu de répéter la même validation partout, ce sera un excellent candidat à placer dans la couche métier.

Parfois, les contrôleurs ont leurs propres règles et conditions qui ne seront reproduites sur aucune autre façade. C'est alors un candidat à intégrer dans un tel contrôleur.

Pensez à ce que vous validez et si vous souhaitez l'appliquer à tout le monde, quoi qu'il arrive. Ou s'il s'agit d'une validation contextuelle ("Je valide quelque chose qui ne se produit que sur une façade de contrôle / vue particulière).

Laiv
la source
0

Dans notre boutique Java, nous avons intentionnellement divisé la validation des widgets Web en trois opérations distinctes.

  1. Formatage de base - les nombres doivent être des nombres; les dates doivent être des dates valides, etc. Habituellement, cette validation est gratuite - le cadre Web le fera pour vous lors de la liaison du contenu du widget au modèle.
  2. Validation d'un widget unique - la date doit être antérieure à la date du jour; un entier doit être compris entre 1 et 100; customerId doit exister dans la base de données, etc. Il appartient dans la couche contrôleur dans la plupart des cas, mais peut nécessiter la prise en charge du référentiel de données.
  3. Validation croisée des widgets - la date de départ doit être postérieure à la date d'arrivée; la date de décès ne peut pas être antérieure à la date de naissance, etc. Nous avons également tendance à mettre cela dans la couche contrôleur, mais vous pouvez le déplacer dans un validateur d'entreprise afin qu'il puisse être réutilisé.

Si la couche 1 échoue, nous ne vérifions pas 2 ou 3. De même, si 1 réussit et 2 échoue, nous n'en faisons pas 3. Cela empêche la génération de messages d'erreur parasites.

Vous demandez des valeurs dans un appel REST plutôt que le contenu d'un widget, mais les mêmes principes s'appliquent.

kiwiron
la source
-1

Une approche testée permet d'ombrer une lumière à ce sujet, après tout, il n'y a pas de contrôleur et vous devez choisir une autre option. De toute évidence, les règles de bussines doivent être au même endroit, et c'est une autre contrainte dans votre décision.

Hans Poo
la source