2015-05-07 3 views
6

Ich habe viele Tutorials gelesen und viele Codebeispiele bezüglich der Implementierung des Repository Musters gesehen. In fast allen Fällen werden Ausnahmen, die sich aus dem Versuch ergeben, die Datenbank zu schlagen, wenn die Datenbank nicht verfügbar ist, nicht behoben. Dies erscheint seltsam, wenn man bedenkt, dass dies ein sehr realistisches Szenario ist, wenn sich die Datenbank irgendwo in einem Netzwerk befindet.Warum behandeln Beispiele des Repository-Musters niemals Datenbankverbindungsausnahmen?

Was ist die beste Vorgehensweise für die Behandlung dieser Ausnahmen?

  • Wrap jede dieser hundert Anrufe in einem try/catch, wo jeder könnte die gleichen n catch-Blöcke haben? Das ist eine Menge Duplikation, chaotisch, fehleranfällig usw.

  • Lassen Sie die Ausnahmen bis zur App-Ebene aufblitzen und fangen Sie sie als unbehandelte Ausnahmen? Dies ist sinnvoll, wenn die Ausnahme für den Benutzeroberflächenthread ausgelöst wird. Andernfalls führt die Behandlung einer nicht behandelten AppDomain-Ausnahme dazu, dass die App heruntergefahren wird.

  • Verwenden Sie ein Framework wie die Exception der Enterprise Library Application Block?

+1

Viele Fehler können nicht auf Repository-Ebene wiederholt werden. Zum Beispiel müssen Deadlocks, Zeitüberschreitungen, Netzwerkfehler auf der Transaktionsebene wiederholt werden, die mehrere Repo-Operationen umfassen kann. – usr

Antwort

3

Ich denke ehrlich, dass es nicht wegen der anhaltenden (und emotionalen) Debatte darüber, was mit Ausnahmen zu tun ist, angesprochen wird. Es gibt ein ständiges Hin und Her darüber, ob Ausnahmen lokal behandelt werden sollen (wo es eine größere Chance gibt, sie zu verstehen und etwas Intelligentes zu tun, wie Wiederholen) oder auf der UI-Ebene gehandhabt wird (wo 99,9% der Ausnahmen auftauchen).

Persönlich finde ich es am elegantesten, den try/catch in der Repository-Ebene zu tun, um die datenbankspezifischen Ausnahmen zu erfassen und eine neue Ausnahme zu erzeugen. Das gibt mir einen Platz, um Wiederholungslogik zu setzen. Ich kann dann auch entscheiden, ob diese DAOException eine eingecheckte oder eine Laufzeitausnahme ist.

Dies ermöglicht der Benutzeroberfläche, mit einer bekannten Ausnahme umzugehen, und es hilft mir, meine höheren Ebenen von anbieterspezifischen Fehlern zu isolieren. Wenn ich beispielsweise meinen Datenspeicher in eine No-SQL-Datenbank wie Mongo oder Cassandra migrieren würde, könnte ich immer noch dieselben Ausnahmen auslösen und ihre semantische Bedeutung beibehalten, ohne den gesamten aufrufenden Code zu ändern.

+0

Also hätte jede Repository-Methode fast die gleiche Try/Catch-Struktur? In meiner Anwendung (Entitätsframework), richtig oder falsch, habe ich jedes Mal, wenn ich auf die Datenbank zugreife, einen separaten "catch" für "System.Data.Entity.Core.EntityCommandExecutionException" und "System.Data.Entity.Core.EntityException". Scheint so, als ob es eine Menge Updates geben würde, wenn ich einen 3. Ausnahmetyp finden würde ... – BCA

+0

Ja, aber der Punkt ist, dass Sie nur dann damit umgehen müssen, wenn Sie die Datenschicht ändern. Und Sie müssten es * nur * in der Datenschicht ändern. Im Gegensatz dazu, dass es sich auf allen Ebenen ausbreiten und verarbeiten muss. Behalten Sie die Datenbank-Verantwortlichkeiten in der DAO-Schicht. –

+0

Aber was ist mit verschiedenen UIs, die den Fehler auf verschiedene Arten behandeln (z. B. eine WPF-App, die ein Nachrichtenfeld öffnet, aber eine Konsolen-App, die auf die Konsole schreibt, und so weiter)? – BCA

0

Erstens, weil SRP. Eine Klasse behandelt nur eine einzige Verantwortung. Zumindest in einer Methode ist es.

Zweitens hängt es davon ab, was Sie mit dem Fehler zu tun haben. Werden Sie nur die Fehlermeldung Benutzer anzeigen? Behandeln Sie es auf Anwendungsebene seit Daten-Ebene und Business-Ebene nicht wissen, welche UI gibt es.

Wenn Sie eine Logik haben, zum Beispiel: wenn die Datenbank nicht zugegriffen werden kann, den Offline-Cache verwenden, damit umgehen Decorator pattern oder ähnliches, ex mit:

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

Warum wir es so handhaben müssen? Auch hier, weil SRP.