2009-02-22 5 views
12

Wir implementieren die Mehrheit unserer Geschäftsregeln in der Datenbank mit gespeicherten Procs.Wie behandelt man DB-Constraint-Verletzungen in der Benutzeroberfläche?

Ich kann nie entscheiden, wie Fehler bei der Dateneinschränkungsverletzung am besten von der Datenbank an die Benutzerschnittstelle übergeben werden. Die Einschränkungen, über die ich spreche, sind mehr an Geschäftsregeln als an Datenintegrität gebunden.

Zum Beispiel ist ein DB-Fehler wie "Kann doppelte Schlüsselzeile nicht einfügen" dasselbe wie die Geschäftsregel "Sie können nicht mehr als ein Foo mit dem gleichen Namen haben". Aber wir haben es an der gebräuchlichsten Stelle implementiert: als eine einzigartige Einschränkung, die eine Ausnahme auslöst, wenn die Regel verletzt wird.

Andere Regeln wie "Sie dürfen nur 100 Foos pro Tag verwenden" verursachen keine Fehler per-say, da sie ordnungsgemäß von benutzerdefiniertem Code wie behandelt werden, den der Anwendungscode sucht und an den zurückgibt UI-Schicht.

Und darin liegt der Haken. Unser ui Code sieht wie folgt aus (dies ist AJAX.NET einen Web Service-Code, aber jeder Ajax-Framework tun wird):

WebService.AddFoo("foo", onComplete, onError); // ajax call to web service 

function onComplete(newFooId) { 
    if(!newFooId) { 
     alert('You reached your max number of Foos for the day') 
     return 
    } 
    // update ui as normal here 
} 

function onError(e) { 
    if(e.get_message().indexOf('duplicate key')) { 
     alert('A Foo with that name already exists'); 
     return; 
    } 
    // REAL error handling code here 
} 

(Als Randbemerkung: Ich stelle fest, das ist, was tut, wenn Sie Stackoverflow Kommentare einreichen zu schnell: der Server generiert eine HTTP 500 Antwort und die ui fängt es ab.)

So sehen Sie, wir behandeln Verstöße gegen die Geschäftsregeln an zwei Stellen hier, von denen einer (dh der einzigartige Constaint-Fehler) als Sonderfall behandelt wird der Code, der echte Fehler behandelt (nicht Verstöße gegen die Geschäftsregeln), da .NET Exceptions bis zum onError() Handler propagiert.

Das fühlt sich falsch an. Meine Optionen Ich denke, sind:

  1. fängt die ‚doppelte Schlüsselverletzung‘ Ausnahme bei der App-Server-Ebene und konvertieren es, was auch immer es die ui erwartet als „Business-Regel verletzt“ Flagge,
  2. preempt der Fehler (sagen wir, mit einem "select name from Foo where name = @Name") und zurück, was auch immer der App-Server erwartet als die "Business Rule verletzt" -Flag,
  3. im selben Stadion wie 2): Hebel die einzigartige Integritätsbedingung in die dB-Schicht integriert und blind insert into Foo, irgendwelche Ausnahmen abfangen und es in alles umwandeln, was die App bedient r erwartet als die Flag „Geschäftsregel verletzt“
  4. blind insert into Foo (wie 3) und lassen Sie die Ausnahme von der ui propagieren, Plus die App-Server erhöht Geschäft Regelverletzungen als echte Exceptions hat (im Gegensatz zu 1 gegenüber). Auf diese Weise werden ALLE Fehler in dem onError() (oder ähnlichem) Code der ui-Schicht behandelt.

Was Ich mag über 2) und 3) ist, dass die Geschäftsregelverletzungen „geworfen“ werden wo sie umgesetzt werden: in der gespeicherten proc. Was ich an 1) und 3) nicht mag, ist denke sie beinhalten dumme Schecks wie "if error.IndexOf('duplicate key')", genau wie in der UI-Schicht derzeit.

bearbeiten: Ich mag 4), aber die meisten Leute sagen, Exception s nur in außergewöhnlichen Umständen zu verwenden.

Also, wie gehen Sie Menschen mit der Verbreitung von Verstößen gegen die Geschäftsregeln bis zur UI elegant?

Antwort

1

Eine gespeicherte Prozedur kann die RAISERROR-Anweisung verwenden, um Fehlerinformationen an den Aufrufer zurückzugeben. Dies kann in einer Weise verwendet werden, die es der Benutzerschnittstelle ermöglicht, zu entscheiden, wie der Fehler angezeigt wird, während es der gespeicherten Prozedur erlaubt wird, die Details des Fehlers bereitzustellen.

RAISERROR kann mit einem msg_id, Schweregrad und Status und mit einer Reihe von Fehlerargumenten aufgerufen werden. Bei Verwendung dieser Methode muss eine Nachricht mit der angegebenen Nummer msg_id mit der gespeicherten Prozedur sp_addmessage in die Datenbank eingegeben worden sein. Das msg_id kann als die ErrorNumber Eigenschaft in der SqlException abgerufen werden, die in dem .NET-Code ausgelöst wird, der die gespeicherte Prozedur aufruft. Die Benutzerschnittstelle kann dann entscheiden, welche Art von Nachricht oder andere Anzeige angezeigt werden soll.

Die Fehler Argumente in die resultierenden Fehlermeldung jedoch ähnlich wie die printf Anweisung funktioniert in C ersetzt werden, wenn Sie nur die Argumente zurück auf die UI geben wollen, so dass der UI entscheiden kann, wie sie zu benutzen , machen Sie einfach die Fehlermeldungen keinen Text, nur Platzhalter für die Argumente. Eine Nachricht könnte sein: "% s" |% d ', um ein Zeichenfolgenargument (in Anführungszeichen) und ein numerisches Argument zurückzugeben. Der .NET-Code könnte diese aufteilen und sie in der Benutzeroberfläche verwenden, wie Sie möchten.

RAISERROR kann auch in einem TRY CATCH-Block in der gespeicherten Prozedur verwendet werden. Das würde es Ihnen ermöglichen, den doppelten Schlüsselfehler abzufangen und durch Ihre eigene Fehlernummer zu ersetzen, die "doppelten Schlüssel bei Einfügen" in Ihrem Code bedeutet, und es kann den tatsächlichen Schlüsselwert (die Schlüsselwerte) enthalten. Ihre Benutzerschnittstelle könnte dies verwenden, um "Bestellnummer existiert bereits" anzuzeigen, wobei "x" der Schlüsselwert war, der geliefert wurde. Dies ist

+0

Ich wünschte, die Person, die diese und http://stackoverflow.com/questions/581994/-net-coding-standards-and-framework-for-a-web-service/582429#582429 downvoted würde geben einen Grund. Zwei sehr unterschiedliche Antworten, gleiches Ergebnis, innerhalb einer Minute. –

+1

Der Code, der schließlich 'RAISERROR' aufruft, muss immer noch eine" dumme Überprüfung "wie' CHARINDEX ('duplicate key', @errorMessage)> 0' durchführen, und ich habe diese Frage gefunden, weil mein Beispielszenario einen * zwei * möglichen eindeutigen Schlüssel enthält Constraint-Verletzungen, und ich war neugierig, ob es einen Weg gab zu bestimmen, welcher Schlüssel verletzt wurde, ohne nach den Namen der Schlüssel oder den Namen der relevanten Spalten zu suchen. –

1

Ich habe viele Ajax-basierte Anwendungen gesehen, die eine Echtzeitprüfung von Feldern wie Benutzername durchführen (um zu sehen, ob sie bereits existiert), sobald der Benutzer das Bearbeitungsfeld verlässt. Es scheint mir ein besserer Ansatz zu sein, als der Datenbank eine Ausnahme basierend auf einer DB-Einschränkung zu geben - sie ist proaktiver, da Sie einen echten Prozess haben: den Wert abrufen, prüfen, ob er gültig ist, Fehler anzeigen, wenn nicht, Lassen Sie fortfahren, wenn kein Fehler vorliegt. So scheint es, dass Option 2 eine gute ist.

+0

Das ist schön für die UX, aber eine große Race-Bedingung ist da, dass die ui noch mit der Einreichung der Daten zu tun haben. –

+0

Wir kommen um die Race-Bedingung herum, indem wir eine vollständige serverseitige Validierung beim Senden durchführen. Die Validierung pro Feld ist nur eine Annehmlichkeit für den Benutzer –

+0

@Mark: sicher.Ich frage mich nur, wie die Leute die "vollständige serverseitige Validierung" in der UI handhaben (unter der Annahme, dass Ajax die Daten übermittelt), wenn ein Verstoß * auftritt * - was Sie in Ihrer anderen Antwort beantwortet haben, danke. –

3

Das Problem ist wirklich eine der Einschränkungen in der Architektur Ihres Systems. Indem Sie die gesamte Logik in die Datenbank einbringen, müssen Sie sie an zwei Stellen behandeln (im Gegensatz zum Erstellen einer Schicht Geschäftslogik, die die Benutzeroberfläche mit der Datenbank verbindet.) Sobald Sie eine Ebene der Geschäftslogik haben, verlieren Sie alle Vorteile der in gespeicherte procs Logik. nicht das eine oder andere befürworten. die beiden etwa gleich saugen. oder saugen nicht. Je nachdem, wie Sie es sehen.

Wo war ich? Richtig.

Ich denke, eine Kombination von 2 und 3 ist wahrscheinlich der Weg zu gehen.

Durch Vorwegnahme des Fehlers können Sie eine Reihe von Verfahren erstellen, die von der Benutzeroberfläche aufgerufen werden können, um detaillierte implementieren bereitzustellen aktionsspezifische Rückmeldung an den Benutzer. Sie müssen dies nicht unbedingt mit Ajax auf einer Feld-für-Feld-Basis tun, aber Sie könnten.

Die eindeutigen Integritätsregeln und anderen Regeln, die in der Datenbank enthalten sind, werden dann zur endgültigen Plausibilitätsprüfung für alle Daten und können davon ausgehen, dass die Daten gut sind, bevor sie gesendet werden, und Exceptions sind selbstverständlich diese Prozeduren sollten immer mit gültigen Daten aufgerufen werden und daher sind ungültige Daten ein Ausnahmefall).

+0

Das ist eigentlich eine Kombination aus 2 und 4, mit der Optimierung auf 4, anstatt Fehler auf dem App-Server zu erhöhen, werden sie im gespeicherten Prozess ausgelöst. Der Stored Proc löst also Exceptions für ALLE Business Rule-Verletzungen aus (einige haben sich selbst gefangen, einige die DB für sie abgefangen). Ich mag. –

+0

Sie haben Recht ... –

2

Zur Verteidigung von # 4 hat SQL Server eine ziemlich geordnete Hierarchie von vordefinierten Schweregradstufen. Da Sie darauf hinweisen, dass es gut ist, Fehler zu behandeln, wo die Logik ist, würde ich geneigt sein, dies vereinbarungsgemäß zwischen dem SP und der UI-Abstraktion zu handhaben, anstatt eine Menge zusätzlicher Kopplung hinzuzufügen. Zumal Sie Fehler sowohl mit einem Wert als auch mit einem String auslösen können.

5

Wir führen unsere Geschäftslogik nicht in der Datenbank aus, aber wir haben alle unsere Validierungs-Server-Seite, mit Low-Level-DB-CRUD-Operationen, getrennt von höherer Geschäftslogik und Controller-Code.

Wir versuchen intern, ein Validierungsobjekt mit Funktionen wie Validation.addError(message,[fieldname]) zu übergeben. Die verschiedenen Anwendungsschichten anhängen ihre Validierungsergebnisse auf diesem Objekt und wir Validation.toJson() rufen Sie dann ein Ergebnis zu erzeugen, das wie folgt aussieht:

{ 
    success:false, 
    general_message:"You have reached your max number of Foos for the day", 
    errors:{ 
     last_name:"This field is required", 
     mrn:"Either SSN or MRN must be entered", 
     zipcode:"996852 is not in Bernalillo county. Only Bernalillo residents are eligible" 
    } 
} 

Dies kann leicht verarbeitet Client-Seite Nachrichten an einzelne Felder als allgemeine als auch in Bezug auf Anzeigen werden Mitteilungen.

In Bezug auf Constraint-Verletzungen verwenden wir # 2, d. H. Wir prüfen vor dem Einfügen/Aktualisieren auf mögliche Verletzungen und hängen den Fehler an das Validierungsobjekt an.

+0

Ich merke, das ist, was SO für Schreibvorgänge wie Voting tut. Die Antwort hat die Felder "Erfolg" und "Nachricht". Aber wenn du Kommentare zu schnell eingibst, werfen sie eine 500-Server-Ausnahme und die ui fängt sie ab (zB was mein Code-Beispiel tut, was ich nicht mag). –

+0

Ich mag diesen Ansatz jedoch, und es wäre nicht unmöglich, sogar mit Business-Rule-Checks zu tun, die alle in der Datenbank behandelt werden. Der App-Server muss nur alle Antworten auf einen Standard übersetzen, den der Benutzer verarbeiten kann. Was auch immer nicht schlecht ist. –

1

wie ich die Dinge zu tun, wenn es nicht für Sie am besten kann:

Ich gehe allgemein für das Präventiv Modell, obwohl es eine Menge auf Ihrer Anwendungsarchitektur abhängt.

Für mich (in meiner Umgebung) ist es sinnvoll, die meisten Fehler in der mittleren Ebene (Geschäftsobjekte) zu überprüfen. Hier findet all die andere geschäftsspezifische Logik statt, also versuche ich auch hier den Rest meiner Logik zu behalten. Ich denke an die Datenbank als irgendwo, um meine Objekte zu erhalten.

Wenn es um die Validierung geht, können die einfachsten Fehler in Javascript gefangen werden (Formatierung, Feldlängen usw.), obwohl Sie natürlich nie davon ausgehen, dass diese Fehlerprüfungen stattgefunden haben. Diese Fehler werden auch in der sichereren, kontrollierteren Welt des serverseitigen Codes überprüft.

Geschäftsregeln (wie "Sie können nur so viele Foos pro Tag haben") werden im serverseitigen Code im Business-Objekt-Layer überprüft.

In der Datenbank werden nur Datenregeln überprüft (referenzielle Integrität, eindeutige Feldbedingungen usw.). Wir vermeiden es, alle diese Punkte auch auf der mittleren Ebene zu überprüfen, um nicht unnötig in die Datenbank zu gelangen.

So schützt sich meine Datenbank nur vor den einfachen, datenzentrischen Regeln, für die sie gut gerüstet ist; Die variablen, geschäftsorientierten Regeln leben im Land der Objekte und nicht im Land der Rekorde.

+0

Schön. Aber neugierig: Was machen Sie an der Race Condition, bei der ein Check in der mittleren Ebene passiert, aber an der Datenebene scheitert (weil ein anderer Thread es bis zum Schlag schlägt)? –

+0

Ja, das ist total die Schwäche in diesem Ansatz. In meiner speziellen Umgebung ist dies ein so seltener Fall, dass wir uns nicht darum kümmern müssen. Es würde zu einer ziemlich hässlichen Ausnahme führen, richtig, aber die Datenbank würde intakt bleiben. – teedyay