In quale livello deve essere posizionata la convalida?


18

Sto creando un'API Rest utilizzando Spring Boot e sto utilizzando Hibernate Validation per convalidare gli input della richiesta.

Ma ho anche bisogno di altri tipi di convalida, ad esempio quando i dati di aggiornamento devono essere controllati, se l'id dell'azienda non esiste voglio lanciare un'eccezione personalizzata.

Questa convalida dovrebbe trovarsi al livello di servizio o al livello del controller?

Livello di servizio:

 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);
}

Livello controller:

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);
}

Risposte:


8

Sia il livello controller che il livello servizio espongono determinate interfacce. Le interfacce definiscono i contratti su come utilizzare l'interfaccia. Contratto di solito indica quali argomenti (e relativi tipi e valori) sono attesi, quali eccezioni possono essere lanciate, quali effetti collaterali vengono creati ecc.

Ora, la convalida consiste essenzialmente nell'applicazione del contratto del metodo update update () del controller e del metodo update () del livello di servizio. Entrambi hanno un contratto molto simile, quindi sarebbe naturale se anche la convalida (esecuzione del contratto) fosse comune.

Un modo possibile per farlo è separare la convalida di questo contratto e farlo chiamare in entrambi i livelli. Questo di solito è più chiaro: ogni classe / metodo applica il proprio contratto, ma spesso non è pratico a causa delle prestazioni (accesso al database) o di altri motivi.

Un'altra possibilità è delegare questa convalida al livello di servizio, definendo esplicitamente il comportamento in caso di convalida non riuscita nel contratto del livello di servizio. Il livello di servizio restituisce in genere un errore di convalida generico (o genera un'eccezione) e il livello del controller vorrà reagire in qualche modo specifico all'errore - in questo caso restituiremo 400 Richiesta errata per segnalare che la richiesta in arrivo non è valida.

In questo progetto, esiste il pericolo di un eccessivo accoppiamento tra la logica aziendale nel livello di servizio (che dovrebbe essere piuttosto generico) e il controller (che gestisce la logica di integrazione).

Comunque, questa è una domanda piuttosto controversa e 100 persone risponderanno con 100 risposte. Questa è solo la mia opinione.


1

L'input deve essere verificato nel livello di servizio.

E "Impossibile trovare l'id" è una condizione di errore logico. Quindi dovrebbe essere lanciato dal livello del controller.

Questo dipende ancora dalla tua stratificazione / disegno.
Cosa dovrebbe fare un livello di servizio e cosa ci si aspetta dal livello del controller.


Una risposta non dovrebbe cercare ulteriori chiarimenti dalla domanda. Se la domanda necessita di chiarimenti, deve essere commentata ed eventualmente contrassegnata per la chiusura se non è chiara. Sì, mi rendo conto che non hai la reputazione per nessuna di queste azioni.

"Controllo input" è ambiguo. Ad esempio, potrei inserire un attributo obbligatorio su un campo per indicare che deve essere compilato, ma potrei anche inserire un attributo personalizzato complesso che controlla, ad esempio, che un valore di campo sia maggiore di un altro. IMHO, la convalida di confronto "profuma" molto di più del livello di servizio aziendale rispetto al livello del controller.
JustAMartin,

1

Le convalide dell'ibernazione sono controlli sull'integrità dei dati. Al fine di evitare RuntimeExceptions da bbdd. Sono praticamente le stesse convalide che dovresti controllare con Vincoli . Poiché solo il livello aziendale deve alimentare il livello di persistenza, è possibile (o non dipende da te) fidarsi della correttezza dei dati che provengono dal livello aziendale

Non inserisco convalide nei DAO. Mi aspetto dati validi dai livelli superiori. In caso di errore, delegare a bbdd la responsabilità di essere consapevole del suo contenuto.

Poi arrivano le convalide a livello aziendale. Tutte le validazioni aziendali si sono concentrate sul mantenimento della coerenza dei dati, non della sua integrità .

Infine, eseguo precedenti convalide sul livello di controllo. Quelli legati solo a tale livello.

Presto vedrai quali convalide sono destinate a essere impiantate a livello aziendale. Il più comune: controllo ID. Questo può essere facilmente implementato su entrambi i livelli. Se prevedi di avere molti controller o clienti che consumano il tuo livello aziendale, invece di ripetere la stessa convalida ovunque, sarà un candidato eccellente da mettere a livello aziendale.

A volte i controller hanno le proprie regole e condizioni che non possono essere riprodotte in qualsiasi altra facciata. Quindi è un candidato ad essere inserito in tale controller.

Pensa a ciò per cui stai convalidando e se desideri applicarlo a tutti, non importa quale. O se si tratta di una convalida contestuale ("Sto convalidando qualcosa che accade solo in una particolare facciata di controllo / vista).


0

Nel nostro negozio Java, abbiamo intenzionalmente suddiviso la convalida del widget Web in tre operazioni separate.

  1. Formattazione di base: i numeri devono essere numeri; le date devono essere date valide ecc. Di solito questa convalida viene fornita gratuitamente: il framework Web lo farà per te quando si associano i contenuti del widget al modello.
  2. Convalida singolo widget: la data deve essere nel passato; un numero intero deve essere compreso tra 1 e 100; customerId deve esistere nel database, ecc. Questo appartiene al livello del controller nella maggior parte dei casi, ma potrebbe essere necessario il supporto dal repository di dati.
  3. Convalida cross-widget: la data di checkout deve essere successiva alla data di check-in; la data di morte non può essere antecedente alla data di nascita ecc. Questa è sicuramente la validazione delle regole aziendali. Tendiamo a inserirlo anche nel livello del controller, ma potresti volerlo spostare in un validatore aziendale in modo che possa essere riutilizzato.

Se il layer 1 fallisce, non controlliamo 2 o 3. Allo stesso modo se 1 ha successo e 2 fallisce non lo facciamo 3. Questo interrompe la generazione di messaggi di errore spuri.

Stai chiedendo dei valori in una chiamata REST piuttosto che i contenuti del widget, ma si applicano gli stessi principi.


-1

Un test di avvicinamento guidato da una luce su questo, dopo tutto non c'è controller e devi scegliere un'altra opzione. Ovviamente le regole del business dovrebbero essere in un posto, e questo è un altro vincolo nella tua decisione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.