Geschäftslogik in der Datenbank

Datenbanken bestehen nicht nur aus Tabellen, sondern auch aus diversen Konstrukten, die der Sicherung der Datenqualität dienen und die dabei helfen, Daten abzufragen und zu pflegen. Mit Hilfe dieser Konstrukte – insbesondere Einschränkungen (Constraints), Sichten (Views), Funktionen (Functions), gespeicherten Prozeduren (Stored Procedures) und Triggern – lässt sich auch Geschäftslogik umsetzen. Aber inwieweit ist dies sinnvoll? Darf man Geschäftslogik in einer Datenbank unterbringen, anstatt in einer separaten Anwendungsschicht? Diesen Fragen gingen wir in einem Workshop unseres Kompetenzbereiches „Datenbanken“ am 08.04.2015 nach.

Das Datenmodell

Die erste Erkenntnis klingt banal, ist aber wichtig: Jede Geschäftslogik sollte auf einem fundierten Datenmodell basieren. Die Praxis zeigt, dass oft zu wenig Planung in das Datenmodell gesteckt wird, was die Implementation der Geschäftslogik unnötig aufbläht und sich langfristig extrem negativ auf die Zuverlässigkeit einer Anwendung auswirkt. Auch der Entwickler, der auf einem schlechten Datenmodell aufbauen muss, hat keinen Spaß, wenn er den Mehraufwand erklären soll. Typische Symptome: „Die Daten sind redundant, und ich muss erstmal die Abhängigkeiten ermitteln“, oder „Ich muss aus einer Zeichenkette erst das Datum herausparsen, um damit rechnen zu können“.

Daher ging dem eigentlichen Thema eine Diskussion zum „richtigen“ Datenmodell voraus, mit Fokus auf den Themen „Normalisierung“ und „Wahl der Datentypen“.

Normalisierung und Datentypen

Grundsätzlich sollte ein Datenmodell normalisiert vorliegen, i.d.R. bis zur 3. Normalform. Auf diese Weise wird redundante Datenhaltung vermieden und es lassen sich eindeutige Beziehungen zwischen den Entitäten/Tabellen erzeugen, die die referentielle Integrität sicherstellen. Dieses Prinzip ist im Wikipedia-Artikel zur Datenbank-Normalisierung beschrieben.

Ebenso wichtig ist es, die richtigen Datentypen anzuwenden. Im Fall von MS SQL Server bedeutet das vor allem:

  • Benutze die „kleinstmöglichen“ Typen (z.B. tinyint statt int, wenn die Werte nie den Bereich 0-255 unter- oder überschreiten).
  • Benutze decimal, wenn exakte Dezimalwerte benötigt werden.
  • Benutze real oder float, wenn ein sehr großer Wertebereich abgedeckt werden muss.
  • Der Nutzen von (small)money ist fraglich; benutze eher decimal mit definierter Genauigkeit.
  • Benutze Typen aus der datetime-Gruppe für Datums- und Zeitangaben.
  • Speichere Zeiträume in bigint als Anzahl Ticks (deckt einen Bereich von ca. 29.000 Jahren ab); oder aber als time (small)datetime.
  • Benutze Unicode-Varianten für Zeichenketten, die mehrere Zeichensätze abbilden müssen (z.B. nvarchar statt varchar).
  • Benutze uniqueidentifier für GUIDs (global eindeutige Schlüssel).

Beim Umgang mit unterschiedlichen numerischen Datentypen muss man einen möglichen Überlauf, Rundungsdifferenzen und ein paar Eigenheiten im Auge behalten. Im Normalfall erzeugt ein Überlauf einen Fehler (Abb. 1) und ein Verlust an Genauigkeit wird gerundet (Abb. 2), aber es gibt Ausnahmen. So werden bei der Umwandlung von real und float in eine Ganzzahl die Dezimalstellen abgeschnitten (Abb. 3) und bei der Umwandlung in decimal wird ein Wert kleiner als 5 * 10e18 zu 0.

Im Normalfall erzeugt ein Überlauf einen Fehler
Abb. 1

 

 

und ein Verlust der Genauigkeit wird gerundet
Abb. 2
So werden bei der Umwandlung von real und float in eine Ganzzahl die Dezimalstellen abgeschnitten
Abb. 3

Nicht ganz intuitiv ist außerdem das Verhalten von decimal-Werten, bei denen die Nachkommastellen (Scale) gerundet werden, bevor die Gesamtlänge (Precision) geprüft wird (Abb. 4). Kurios ist auch, dass Integer-Konstanten größer als 2.147.483.647 implizit nicht zu bigint, sondern zu decimal konvertiert werden (Abb. 5).

Nicht ganz intuitiv ist außerdem das Verhalten von decimal-Werten, bei denen die Nachkommastellen (Scale) gerundet werden, bevor die Gesamtlänge (Precision) geprüft wird
Abb. 4

 

 

Kurios ist auch, dass Integer-Konstanten größer als 2.147.483.647 implizit nicht zu bigint, sondern zu decimal konvertiert werden
Abb. 5

Geschäftslogik

Im Folgenden sind unterschiedliche Möglichkeiten aufgeführt, mit denen sich Geschäftslogik in einer Datenbank umsetzen lässt – mit jeweiligen Vor- und Nachteilen.

Einschränkungen (Constraints)

Einschränkungen sind Regeln für die Daten in einer Datenbank und zielen darauf ab, die Datenqualität sicherzustellen. Damit sind Einschränkungen unser erster Kandidat zur Implementation von Geschäftsregeln. Wir unterscheiden zwischen Fremdschlüsseln (Foreign Keys) und Attribut-Einschränkungen.

Fremdschlüssel erzwingen die referentielle Integrität bei Entitäten/Tabellen, die in einer logischen Beziehung zueinander stehen (Abb. 6). So kann z.B. ein neuer Datensatz mit einem Fremdschlüssel nur dann in eine Tabelle eingefügt werden, wenn die referenzierte Tabelle einen Datensatz mit entsprechendem Wert in der zugeordneten Spalte enthält.

Fremdschlüssel erzwingen die referentielle Integrität bei Entitäten/Tabellen, die in einer logischen Beziehung zueinander stehen
Abb. 6

Attribut-Einschränkungen beziehen sich hingegen nur auf eine einzelne Spalte einer Tabelle und können z.B. die Eingabe leerer Werte oder von Werten außerhalb eines definierten Bereichs verhindern (Abb. 7).

Attribut-Einschränkungen beziehen sich hingegen nur auf eine einzelne Spalte einer Tabelle und können z.B. die Eingabe leerer Werte oder von Werten außerhalb eines definierten Bereichs verhindern
Abb. 7

Die Vor- und Nachteile in Bezug auf eine Implementation von Geschäftslogik sind:

Pro

  • Sicherstellung der Datenintegrität
  • Durchsetzung von einfachen Geschäftsregeln ohne jegliche Programmierung

Contra

  • Die Entwicklung wird erschwert (z.B. ist ein Zurückspielen einzelner Tabellen nur in vorgegebener Reihenfolge möglich und es können keine beliebigen Datensätze zu Testzwecken angelegt werden)

Sichten (Views)

Eine Sicht ist eine gespeicherte Datenbankabfrage. Sie kann zur Laufzeit ausgeführt oder materialisiert sein. Sichten bieten vor allem einen erleichterten Zugriff auf Daten, weil Spalten ad hoc umbenannt, umsortiert, neu kombiniert und versteckt werden können. In begrenzter Weise können Sichten auch zur Umsetzung von Geschäftsregeln genutzt werden, da es möglich ist, bei der Datenrückgabe Datentypen zu konvertieren und berechnete Spalten hinzuzufügen.

Pro

  • Vereinfacht die Nutzung von Daten
  • (Begrenzte) Implementation einfacher Geschäftsregeln für die Anzeige von Daten

Contra

  • Nur sehr eingeschränkte Funktionalität (i.d.R. nur Lesezugriff auf Daten)

Funktionen (Functions)

Benutzerdefinierte Funktionen geben entweder einen Skalarwert oder einen Datensatz zurück. Da sie deterministisch sein müssen und keine Werte in der Datenbank ändern dürfen, ist ihr Einsatz ebenfalls eingeschränkt. Sie sind allerdings etwas flexibler als Sichten, da sie Argumente entgegennehmen können.

Pro

  • Keine unerwünschten Seiteneffekte möglich
  • Flexibler als eine Sicht

Contra

  • Kein Try/Catch möglich

Gespeicherte Prozeduren (Stored Procedures)

Gespeicherte Prozeduren sind vorkompilierte, abgespeicherte Abfolgen an SQL-Befehlen. Sie ermöglichen einen sehr flexiblen Lese- und Schreibzugriff auf die Daten und bieten damit das größte Potenzial für die Implementation von Geschäftslogik.

Pro

  • Unlimitierter Schreibzugriff
  • Hochflexibel, z.B. Rückgabe mehrerer Datensätze möglich
  • Höhere Datensicherheit (SQL-Injection wird verhindert)

Contra

  • Müssen ggf. im Betrieb neu kompiliert werden (bei Verschachtelung und bei großen Datenänderungen)

Trigger

Trigger sind gespeicherte Prozeduren, die bei bestimmten Tabellenereignissen (INSERT, UPDATE, DELETE) ausgeführt werden. Innerhalb eines Triggers ist es möglich, auf die ursprünglichen und geänderten Daten zuzugreifen und darauf zu reagieren.

Pro

  • Direkte Kopplung datennaher Änderungen mit auslösendem Ereignis
  • Kann nicht durch Anwendungen/Anwender umgangen werden

Contra

  • Datensätze bleiben bei Änderungen länger gesperrt
  • Für Entwickler „unsichtbar“ aktiv (sehr problematisch, wenn der Anwendungsentwickler nicht auch der Datenbankentwickler ist)

Allgemein

Neben den zuvor aufgeführten Gründen ergeben sich bei der Implementation von Geschäftslogik in der Datenbank noch folgende generelle Vor- und Nachteile:

Pro

  • Sicherung der Datenintegrität nahe an den Daten
  • Wegkapseln komplexer Logik
  • Gute Performance (SQL-Code ist kompiliert und optimiert)

Contra

  • Schlechte Performance, wenn keine SQL-Expertise vorhanden ist (z.B. bei Iteration durch Datensätze, Anwendung von Cursor)
  • Geschäftslogik ist evtl. verteilt auf mehrere Schichten
  • Schlechte Portierbarkeit wg. proprietärer SQL-Dialekte
  • Fehlende Integration in die Entwicklerumgebung

Empfehlungen

Als Ergebnis des Workshops lassen sich folgende Punkte zusammenfassen:

  1. Das Datenmodell sollte immer genau durchdacht werden. Normalisierung und die Wahl der korrekten Datentypen ist wichtig. Bei numerischen Daten auf Überlauf und Rundung achten.
  2. Einschränkungen (sowohl Fremdschlüssel als auch Attribut-Einschränkungen) sollten in jedem Fall eingesetzt werden, um die Datenqualität zu gewährleisten.
  3. Sichten sind nur begrenzt für die Umsetzung von Geschäftsregeln nützlich; sie eignen sich vor allem zur Vereinfachung von Abfragen.
  4. Funktionen und gespeicherte Prozeduren können eingesetzt werden:
    1. Für Operationen / Geschäftsregeln, die direkt die Daten betreffen. Einfache Operationen, die ohne Datenänderung auskommen, lassen sich mit Funktionen realisieren. Komplexere Operationen, die auch schreibend auf Daten zugreifen sollen, müssen mit gespeicherten Prozeduren umgesetzt werden.
      Geschäftslogik, die nicht im Zusammenhang mit Daten steht, sollte möglichst nicht in der Datenbank realisiert werden, um eine Verteilung über mehrere Schichten zu minimieren.
    2. Wenn keine (ausreichend komplexe) Schicht für die Geschäftslogik existiert (z.B. Zugriff über MS Access oder Excel, oder Direktzugriff per SQL).
  5. Trigger sollten eingesetzt werden:
    1. Zur Sicherstellung der Datenintegrität, wenn dies nicht anders (Wahl der Datentypen oder Einschränkungen) erreicht werden kann.
    2. Zum Befüllen von Revisionsfeldern (Abb. 8).
    3. Zur Historisierung von Datensätzen (Abb. 9).
    4. Zur sofortigen Denormalisierung von geänderten Daten (z.B. für anschließendes Reporting).
Zum Befüllen von Revisionsfeldern
Abb. 8
Zur Historisierung von Datensätzen
Abb. 9

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.