2009-10-27 15 views
6

Sto costruendo una libreria CRUD di base, che prevedo di utilizzare in ambienti locali (aggiungi riferimento) e wcf (aggiungi riferimento servizio).Progettazione libreria di classi CRUD, per restituire messaggi utili sull'errore di business logic, non eccezionale

Quali sono i migliori tipi di restituzione per le parti Crea, Uupdate ed Elimina (che hanno regole di business più complesse) di una configurazione CRUD?

Desidero essere in grado di ridurre al minimo avanti e indietro sul filo, ma voglio anche fornire ai miei clienti informazioni significative su quando un'operazione non funziona correttamente, ma è tecnicamente valida (quindi non è un'eccezione).

Prendiamo ad esempio il CRUD per una classe Person, che ha questi campi: FirstName, MiddleName LastName e Date of Brith. Sono richiesti prima, ultimo e DOB, ma il centro non lo è.

Come devo riportare i guasti della logica aziendale al client? OSSIA "Devi specificare un valore per FirstName."

  1. E 'questo in cui dovrei gettando eccezioni? (non mi sento come un caso eccezionale, quindi penso che non lo sia ma potrei sbagliarmi).
  2. Devo usare void e un parametro "out"? Se sì, che tipo dovrebbe essere?
  3. Devo utilizzare un tipo di ritorno dell'oggetto e inserire i dati in quello che succede?
  4. Qualche altra opzione che mi è sfuggita completamente?

risposta

1

1.È questo dove dovrei generare eccezioni? (non mi sembra un caso eccezionale, quindi penso di no ma potrei sbagliarmi).

Personalmente, ritengo che si dovrebbe restituire un oggetto con un risultato così come eventuali errori di convalida, e non un'eccezione per la validazione dei dati, se questo è a causa di informazioni (convalida formato) o la convalida logica di business mancante. Tuttavia, io suggerisco di un'eccezione per gli errori che non sono legati ai dati in sé - vale a dire: se il database non commettere con dati validi, ecc

Il mio pensiero è che mancanza di convalida non è un "avvenimento eccezionale" . Personalmente ritengo che tutto ciò che un utente può rovinare semplicemente inserendo dati sufficienti/corretti/ecc. È qualcosa che non è eccezionale - è una pratica standard, e dovrebbe essere gestita direttamente dall'API.

Le cose non correlate a ciò che l'utente sta facendo (ad es. Problemi di rete, problemi del server, ecc.) Sono eventi eccezionali e garantiscono un'eccezione.

2. Dovrei utilizzare void e un parametro "out"? Se sì, che tipo dovrebbe essere?

3. Dovrei utilizzare un tipo di ritorno dell'oggetto e inserire i dati in quello che succede?

Personalmente preferisco la terza opzione. i parametri "out" non sono molto significativi. Inoltre, desideri restituire più di una singola informazione di stato da questa chiamata: ti consigliamo di restituire informazioni sufficienti per contrassegnare le proprietà appropriate come non valide, oltre a tutte le informazioni sull'intero funzionamento.

Probabilmente richiederà una classe che contenga almeno uno stato di commit (successo/formato non riuscito/logica aziendale non riuscita/ecc.), Un elenco di mappature per proprietà-> errori (es .: IDataErrorInfo informazioni di stile) e potenzialmente un elenco di errori che non sono legati a una proprietà specifica, ma riguardano piuttosto la logica di business dell'operazione nel suo complesso o la combinazione di valori di proprietà suggeriti.

4. Qualche altra opzione che ho perso completamente?

L'altra opzione, che mi piace un po ', è quella di avere la convalida in un assembly separato dal livello di elaborazione aziendale. Ciò consente di riutilizzare la logica di convalida sul lato client.

Il bello di questo è che è possibile semplificare e ridurre drasticamente il traffico di rete. Il cliente può pre-validare le informazioni e inviare solo i dati attraverso il filo se è valido.

Il server può ricevere i dati validi e riconvalidarli e non restituire altro che un singolo risultato di commit. Credo che questo dovrebbe avere almeno tre risposte: successo, non riuscita a causa della logica di business o non riuscita a causa della formattazione. Questo dà sicurezza (non devi fidarti del client) e dà al cliente informazioni su ciò che non viene gestito correttamente, ma evita di passare entrambe le informazioni errate da client-> server e le informazioni di convalida da server-> client, quindi può ridurre drasticamente il traffico.

Il livello di convalida può quindi (in modo sicuro) inviare le informazioni al livello CRUD da inviare.

+0

Grazie per aver dedicato del tempo a scrivere una risposta così dettagliata. Qual è, secondo te, l'opzione migliore per la maggior parte dei casi? Sembra che il # 3 sia molto popolare tra le altre risposte, sarebbe d'accordo che sia la migliore soluzione "catch all"? Specail baring o casi limite dispari? – Nate

+1

Se sei preoccupato per il traffico, la mia opzione 4 è una variante su 3 che fornisce un'alternativa "migliore", IMO. Rende anche la convalida sul lato client un gioco da ragazzi. Altrimenti, l'opzione 3 è la mia preferita. –

1

È possibile trovare questo post sul blog di Rob Bagby interessante; descrive come implementare un repository per gestire le operazioni CRUD e, al punto, specificamente come implementare la validazione, restituendo una raccolta di "RuleViolation" al client nel caso ci fosse un problema.

http://www.robbagby.com/silverlight/patterns-based-silverlight-development-part-ii-repository-and-validation/

[modifica] Per me, si tratta di un caso per un'eccezione: se la creazione di un utente richiede un nome e il nome non viene fornito, il chiamante non ha utilizzato gli argomenti propri, e non sta usando il metodo nel modo previsto. InvalidArgumentException sembra adeguato.

+1

Quindi il tuo metodo sarebbe simile a questo? 'CreateUser (String prime, String middle, String Last, DateTime dob)' perché mi trovo più incline a usare 'CreateUser (User user)' è una cattiva idea di design? Nella versione non-WCF posso anche aggiungere sovraccarichi, ma vorrei usare la firma che accetta un oggetto in WCF. – Nate

+0

Quando si generano eccezioni, come si fa a sapere su quale area puntare l'utente verso l'interfaccia utente? Mi piace quando ti dimentichi di inserire un campo e premi invio, l'applicazione non ti dà solo un messaggio, ma focalizza anche il tuo cursore su quel campo. – Nate

+0

Il nome del metodo CreateUser implica che si sta creando un oggetto Utente; se hai già un oggetto User da passare nel metodo, perché dovresti crearne uno? –

1

Si dovrebbe verificare l'implementazione di Validation Rules di Rocky Lhotka nel suo CSLA framework.

NOTA: ho fatto non dire di usare il suo quadro in massa, in quanto ha alcuni problemi di accoppiamento che non si rompono alcuni sforzi SRP nelle ultime tendenze di sviluppo .NET.

Tuttavia, il suo framework utilizza la notifica "automatica" fino al livello dell'interfaccia utente e l'integrazione con i messaggi di errore di convalida con supporto per i controlli Web/Winform.

1

Edit: Devo notare, vorrei normalmente la roba di validazione astratta in un livello aziendale, che gestisse la convalida e chiamasse i metodi CRUD dopo che la convalida aveva avuto successo.

Un modo per procedere sarebbe quello di restituire una classe di "risposta" che contiene tutte le informazioni che un utente della tua biblioteca dovrebbe valutare cosa è successo e cosa fare dopo.Un esempio molto semplice di una classe si potrebbe usare sarebbe qualcosa di simile a questo:

public class Response<T> where T:BusinessObject 
{ 
    public Response(T oldOriginalValue, T newValue) 
    { 
    } 

    /// <summary> 
    /// List of Validation Messages 
    /// </summary> 
    public List<ValidationMessage> ValidationMessages { get; set; } 

    /// <summary> 
    /// Object passed into the CRUD method 
    /// </summary> 
    public T OldValue { get; private set; } 

    /// <summary> 
    /// Object passed back from the CRUD method, with values of identity fields, etc populated 
    /// </summary> 
    public T NewValue { get; private set; } 

    /// <summary> 
    /// Was the operation successful 
    /// </summary> 
    public bool Success { get; set; } 
} 

public class ValidationMessage 
{ 
    /// <summary> 
    /// Property causing validation message (i.e. FirstName) 
    /// </summary> 
    string Property { get; set; } 

    /// <summary> 
    /// Validation Message text 
    /// </summary> 
    string Message { get; set; } 
} 
+0

Credo che dovrei menzionare che il mio vero "CRUD" sta usando LinqToSQL ... Sto esponendo la logica del business nella stessa forma di CRUD. È così male? – Nate

+0

Ad esempio, quello che sto chiamando CRUD sopra, farebbe davvero la convalida dell'input e poi chiamerei LinqToSQL per aggiornare il database o fallire, e la parte fail è cosa non so come fare, ma mi piace il tuo idea. – Nate

+0

No, va bene. I tuoi metodi CRUD sono in realtà il tuo livello aziendale e LinqToSql è il tuo livello dati e ciò che chiami i metodi CRUD restituirebbe l'oggetto risposta per indicare il successo o il fallimento, oltre a fornire un elenco di messaggi per descrivere i motivi di un errore. Eventuali errori che provengono dal tuo LinqToSql catturati dal tuo LinqToSql potrebbero anche essere inseriti nella tua raccolta ValidationMessage (che potrebbe essere meglio denominata Messaggi in modo che possa contenere convalide e messaggi di errore da LinqToSql senza confondere nessuno) –

1

Il dibattito tra il ritorno di una struttura risultato con i dettagli del fallimento contro un'eccezione può essere generalizzato a "Posso eseguire con successo l'richiesto azione?" Stai progettando una libreria e il modo in cui la libreria comunica le modalità di errore fa parte di questo.

Se alla libreria viene richiesto di creare un oggetto Utente ma non è possibile perché la convalida del nome utente non è riuscita, è possibile restituire un utente vuoto insieme ai messaggi di errore di convalida e sperare che il codice client verifichi il valore restituito. La mia esperienza (flashback pre-ATL DCOM) con il dover testare i valori di ritorno per i codici di errore è che è facile ottenere compiacenti (o pigri) e saltarli.

Da ciò che hai descritto, l'utilizzo di eccezioni non è fuori questione. Definisci un'eccezione genitore per tutti i tipi di eccezioni che la tua biblioteca può lanciare. In questo modo i client non devono includere un ampio elenco di eccezioni secondarie a meno che non lo desiderino davvero. Un esempio di questo è SqlException.

3

Per motivi di prestazioni, la convalida dell'input iniziale deve essere eseguita nel livello client. (es .: non preoccuparsi di inviare dati errati lungo il filo.) Nel livello client, un errore di validazione dell'input sarebbe, in effetti, un problema molto prevedibile che non dovrebbe generare eccezioni. Tuttavia, se si applica questa convalida "anticipata" sul livello client, un errore di convalida dei dati riscontrato in qualsiasi livello più profondo potrebbe essere ragionevolmente considerato come un problema imprevisto, pertanto l'attivazione di eccezioni per errori di convalida dei dati in tali livelli non sarebbe inappropriata.

+3

Per motivi di sicurezza, la convalida dei dati DEVE essere eseguita al server, dal momento che il client potrebbe essere compromesso. Mi sbaglio nel trattare sempre il cliente come ostile? – Nate

+3

Hai assolutamente ragione nel trattare il cliente come ostile. La convalida lato client non sostituisce la convalida dal lato del servizio, ma lo integra. Tutto quello che sto suggerendo è che se si applica la convalida sia al client che all'interfaccia di servizio, la maggior parte dei dati non validi non dovrebbe mai raggiungere il servizio, quindi sarebbe accettabile per il servizio trattare i problemi di convalida dei dati come eccezionali. –

+0

Sono tornato indietro dopo aver riflettuto, e in qualche modo non posso accettare una risposta, forse perché questa è una vecchia domanda. Sono giunto alla stessa conclusione. – Nate

Problemi correlati