2015-05-07 20 views
6

Ho letto un sacco di tutorial e ho visto molti esempi di codice riguardanti le implementazioni del pattern Repository. In quasi tutti i casi, le eccezioni che deriverebbero dal tentativo di colpire il database quando il database non è disponibile non sono indirizzate. Questo sembra strano considerando che questo è uno scenario molto realistico se il DB è da qualche parte su una rete.Perché gli esempi del pattern di repository non hanno mai a che fare con le eccezioni di connessione al database?

Quindi qual è la procedura migliore per gestire queste eccezioni?

  • Avvolgere ognuna di quelle centinaia di chiamate in un try/catch, dove ognuno potrebbe avere gli stessi blocchi n di cattura? Questo è un sacco di doppioni, disordinato, soggetto a errori, ecc

  • Diamo la bolla eccezioni fino al livello applicazione di una loro cattura come eccezioni non gestite? Questo ha senso se l'eccezione è lanciata sul thread dell'interfaccia utente, ma in caso contrario, la gestione di un'eccezione AppDomain non gestita determina la chiusura dell'app.

  • Utilizzare un framework come Exception Gestione blocco applicazioni?

+1

Molti errori non possono essere riprovati a livello di repository. Ad esempio deadlock, timeout, errori di rete devono essere ripetuti a livello di transazione che può estendersi su più operazioni di pronti contro termine. – usr

risposta

3

Onestamente, penso che non sia affrontato a causa del dibattito in corso (ed emotivo) su cosa fare con le eccezioni. Esiste una costante differenza tra le eccezioni che devono essere gestite localmente (dove c'è una maggiore possibilità di capirle e fare qualcosa di intelligente, come riprovare) o gestite al livello dell'interfaccia utente (dove il 99,9% delle eccezioni alla fine diventa bolla).

Personalmente, trovo molto elegante fare il try/catch nel livello del repository, catturare le eccezioni specifiche del database e lanciare una nuova eccezione di mia creazione. Questo mi dà un posto dove mettere la logica dei tentativi. Inoltre, posso decidere se tale DAOException è un'eccezione controllata o di runtime.

Ciò consente all'interfaccia utente di gestire un'eccezione nota e mi aiuta a isolare i livelli di livello superiore da eventuali errori specifici del provider. Ad esempio, se dovessi migrare il mio archivio dati in un database No-SQL come Mongo o Cassandra, potrei comunque lanciare le stesse eccezioni e mantenere il loro significato semantico, senza modificare tutto il codice chiamante.

+0

Quindi ogni metodo di repository avrebbe praticamente la stessa struttura try/catch? Nella mia applicazione (entità framework), giusto o sbagliato, ogni volta che accedo al database ho un 'catch' separato per' System.Data.Entity.Core.EntityCommandExecutionException' e 'System.Data.Entity.Core.EntityException'. Sembra che ci sarebbe un sacco di aggiornamenti da fare se trovassi un terzo tipo di eccezione da gestire ... – BCA

+0

Sì, ma il punto è che dovresti gestirlo solo quando cambi il livello dati. E dovresti * solo * doverlo modificare nel livello dati. Al contrario di lasciarlo propagare e doverlo gestire a tutti i livelli. Conserva le responsabilità del database nel livello DAO. –

+0

Ma per quanto riguarda le diverse interfacce utente che gestiscono l'errore in modi diversi (ad esempio, un'app WPF fa apparire una finestra di messaggio ma un'app console scrive sulla console e così via)? – BCA

0

In primo luogo, perché SRP. Una classe gestisce solo una e una sola responsabilità. Almeno in un metodo lo è.

In secondo luogo, dipende da cosa è necessario fare con l'errore. Vuoi solo mostrare il messaggio di errore all'utente? Gestirlo a livello di applicazione poiché a livello di dati e livello aziendale non si conosce l'interfaccia utente.

Se si dispone di una logica, per esempio: se il database non è possibile accedere, utilizzare la cache non in linea, di gestirlo con Decorator pattern o simili, es:

public class OnlineUserRepository : IUserRepository{ 
    public User Get(){ /* get the user from online source */ } 
} 

public class OfflineUserRepository : IUserRepository{ 
    public User Get(){ /* get the user from offline source */ } 
} 

public class UserRepository : IUserRepository{ 
    public UserRepository(IUserRepository onlineRepo, IUserRepository offlineRepo){ 
     //parameter assignment 
    } 
    IUserRepository onlineRepo; 
    IUserRepository offlineRepo; 
    public User Get(){ 
     try{ 
      onlineRepo.Get(); 
     } 
     catch{ 
      return offlineRepo.Get(); 
     } 
    } 
} 

Perché abbiamo bisogno di gestire la cosa in questo modo? Ancora una volta, perché SRP.