Verweisen auf Datenbankwerte in der Geschäftslogik

43

Ich denke, dies ist eine weitere Frage zu Hardcodierung und Best Practices. Angenommen, ich habe eine Liste von Werten, beispielsweise Obst, die in der Datenbank gespeichert sind (sie muss in der Datenbank vorhanden sein, da die Tabelle für andere Zwecke wie SSRS-Berichte verwendet wird), mit einer ID:

1 Apple 
2 Banana 
3 Grapes

Ich kann sie dem Benutzer präsentieren, er wählt eine aus, sie wird in seinem Profil als FavouriteFruit gespeichert und die ID in seinem Datensatz in der Datenbank gespeichert.

Was sind die Empfehlungen für die Zuweisung von Logik zu bestimmten Werten, wenn es um Geschäftsregeln / Domänenlogik geht? Sagen Sie, wenn der Benutzer Trauben ausgewählt hat, die ich für eine zusätzliche Aufgabe ausführen möchte, wie lässt sich der Traubenwert am besten ermitteln:

// Hard coded name
if (user.FavouriteFruit.Name == "Grapes")

// Hard coded ID
if (user.FavoriteFruit.ID == 3) // Grapes

// Duplicate the list of fruits in an enum
if (user.FavouriteFruit.ID == (int)Fruits.Grapes)

oder etwas anderes?

Da FavouriteFruit natürlich in der gesamten Anwendung verwendet wird, kann die Liste hinzugefügt oder bearbeitet werden.

Jemand könnte entscheiden, dass "Trauben" in "Trauben" umbenannt werden soll, und dies würde natürlich die fest codierte Zeichenfolgenoption aufheben.

Die fest codierte ID ist jedoch nicht vollständig klar, wie gezeigt, können Sie einfach einen Kommentar hinzufügen, um schnell zu erkennen, um welchen Artikel es sich handelt.

Die Enum-Option beinhaltet das Duplizieren von Daten aus der Datenbank, was falsch zu sein scheint, da sie möglicherweise nicht mehr synchron sind.

Trotzdem vielen Dank im Voraus für Kommentare oder Vorschläge.

Kate
quelle
1
Vielen Dank an alle: Die Vorschläge und allgemeinen Ratschläge sind wirklich hilfreich. @RemcoGerlich Ihre Idee, die Bedenken einer Zeichenfolge, die für Anzeigezwecke verwendet wird, und einer separaten Zeichenfolge als Suchcode für besser lesbaren Code zu trennen, ist sehr gut.
Kate
1
Ich werde @Mike Nakis Ihre vorinstallierten Objekte vorstellen, da dies das Beste aus beiden Welten zu sein scheint.
Kate
1
Ich würde eine Variation Ihrer ersten Lösung vorschlagen. Stellen Sie sicher, dass Ihre Tabelle eine dritte Spalte enthält, in der angegeben ist, wie sie verarbeitet werden soll. In diesem Feld legen Sie fest, welcher Code ausgeführt werden soll. Kein Anzeigefeld und kann von mehreren Früchten gemeinsam genutzt werden.
Kickstart
1
Die Enum-Option beinhaltet das Duplizieren von Daten aus der Datenbank, was falsch zu sein scheint, da sie möglicherweise nicht mehr synchron sind. Ich mag das eigentlich. Es ist wie eine doppelte Buchführung. Wenn beide Seiten des Hauptbuchs nicht ausgeglichen sind, wissen Sie, dass etwas nicht stimmt. Es macht das Ändern von Dingen bewusster.
Radarbob
1
Hmmm ... Wenn es eine 1: 1-Beziehung von ID zu einem String gibt, ist dies überflüssig und es hat keinen Sinn, beide zu haben. Ein String kann sowohl als DB-Schlüssel als auch als Ganzzahl dienen. MyApplication.Grape.IDstottert sozusagen. Ein "Apple" ist kein "Red_Apple" und nicht mehr als ID 3 ist auch 4. Das Potenzial, "Apple" in "Red_Apple" umzubenennen, ist also nicht sinnvoller, als zu deklarieren, dass 3 4 ist (und vielleicht sogar 3). Ziel einer Aufzählung ist es, ihre numerische DNA zu abstrahieren. Vielleicht ist es an der Zeit, beliebige relationale DB-Schlüssel, die in den eigenen Geschäftsmodellen buchstäblich keine Bedeutung haben , wirklich zu entkoppeln.
Radarbob

Antworten:

31

Vermeiden Sie unbedingt Zeichenfolgen und magische Konstanten. Sie kommen überhaupt nicht in Frage, sie sollten nicht einmal als Optionen betrachtet werden. Dies scheint Ihnen nur eine praktikable Option zu lassen: Bezeichner, also Aufzählungen. Es gibt jedoch noch eine Option, die meiner Meinung nach die beste ist. Nennen wir diese Option "Vorinstallierte Objekte". Mit vorinstallierten Objekten können Sie Folgendes tun:

if( user.FavouriteFruit.ID == MyApplication.Grape.ID )

Was hier gerade passiert ist, ist, dass ich offensichtlich die gesamte Zeile von Grapein den Speicher geladen habe, so dass ich ihre ID bereit habe, um sie für Vergleiche zu verwenden. Wenn Sie zufällig Object-Relational Mapping (ORM) verwenden, sieht es noch besser aus:

if( user.FavouriteFruit == MyApplication.Grape )

(Deshalb nenne ich es "vorinstallierte Objekte".)

Was ich also tue, ist, dass ich während des Startvorgangs alle meine "Aufzählungstabellen" (kleine Tabellen wie Wochentage, Monate des Jahres, Geschlechter usw.) in die Hauptklasse der Anwendungsdomäne lade. Ich lade sie namentlich, weil offensichtlich MyApplication.Grapedie Zeile "Grape" erhalten muss, und ich behaupte, dass jeder von ihnen gefunden wird. Wenn nicht, haben wir einen garantierten Laufzeitfehler während des Startvorgangs, der von allen Laufzeitfehlern am wenigsten bösartig ist.

Mike Nakis
quelle
17
Ich bin mit der Antwort nicht einverstanden, aber ich denke, der Grundsatz "Vermeiden Sie unbedingt Zeichenketten und magische Konstanten" widerspricht dem Rest der Antwort, der tatsächlich voraussetzt, dass Sie mindestens eine Stelle haben, an der magische Konstanten oder Zeichenketten verwendet werden beim Auffüllen Ihrer "vorinstallierten Objekte". Das ist bemerkenswert, denke ich, denn es gibt Möglichkeiten , von „Strings und magischen Konstanten“ völlig zu vermeiden, obwohl es in der Regel mehr verschleiern als es wert ist ...
svidgen
2
@svidgen Würden Sie nicht zustimmen, dass es einen fundamentalen Unterschied zwischen der einmaligen Streuung der Namensbindung über den gesamten Ort und der einmaligen Streuung der Namensbindung gibt, um den Inhalt eines Datensatzes mit demselben Namen zu laden, und dies nur beim Start, wo Laufzeitfehler fast so harmlos sind wie Übersetzungsfehler? Wie auch immer, trotz der von Ihnen erwähnten Verschleierung sind Möglichkeiten, auch nur die geringste Namensbindung zu vermeiden, immer interessant. Daher wäre ich gespannt, was Sie davon halten.
Mike Nakis
Oh, ich stimme vollkommen zu. Und angesichts der Art des OP würde ich nur vorschlagen, dass diese Antwort davon profitiert, wenn "um jeden Preis" auf "wann immer möglich und machbar" oder ähnliches umgestellt wird. ... Wenn ich nur der Vollständigkeit halber mehr Zeit hätte, würde ich eine Antwort schreiben, die sich mit irgendeiner Art von Metaprogramm-Unsinn befasst ... aber das ist wahrscheinlich nicht das, was das OP (oder in den meisten Fällen jemand) braucht . Eine Metaprogammierungslösung würde jedoch eher Ihrer ersten Aussage entsprechen, wie sie ist.
Svidgen
1
@ user469104 Der Unterschied besteht darin, dass sich die IDs möglicherweise ändern und die Anwendung weiterhin alle Zeilen korrekt lädt und alle Vergleiche korrekt durchführt. Außerdem können Sie den Code umgestalten und die Zeilen nach Belieben umbenennen. Der einzige Ort, an dem Sie nach zu reparierenden Dingen suchen müssen, ist der Start der Anwendung, und es ist in der Regel sehr offensichtlich: Grape = fetchRow( Fruit.class, NameColumn, "Grape" ); Und wenn Sie Tun Sie etwas falsch, AssertionErrorund Sie werden es wissen lassen.
Mike Nakis
1
@grahamparks nicht mehr als enumeine magische Zeichenfolge gewesen wäre. Der Punkt ist die Konzentration aller Bindungen an nur einer Stelle , die Validierung aller Bindungen während des Startvorgangs und die Typensicherheit .
Mike Nakis
7

Die Prüfung anhand der Zeichenfolge ist am besten lesbar, hat jedoch doppelte Funktion: Sie wird sowohl als Bezeichner als auch als Beschreibung verwendet (was sich aus nicht verwandten Gründen ändern kann).

Normalerweise teile ich beide Aufgaben in separate Felder auf:

id  code    description
 1  grape   Grapes
 2  apple   Apple

Wo sich die Beschreibung ändern kann (aber nicht "Trauben" zu "Banane"), darf sich der Code niemals ändern.

Dies liegt zwar hauptsächlich daran, dass unsere IDs fast immer automatisch generiert werden und daher nicht gut passen. Wenn Sie IDs frei wählen können, können Sie möglicherweise garantieren, dass sie immer korrekt sind, und diese verwenden.

Wie oft bearbeitet jemand "Trauben" wirklich in "Trauben"? Vielleicht ist nichts davon notwendig.

RemcoGerlich
quelle
8
Ich glaube nicht, dass noch mehr Redundanz die Antwort ist ...
Robbie Dee
4
Ich habe auch über diese Option nachgedacht und sie ausprobiert, aber das ist passiert: Irgendwann musste "apple" in "green_apple" und "red_apple" unterschieden werden. Aber da "apple" bereits an unzähligen Stellen im Code verwendet wurde, konnte ich es nicht umbenennen, also musste ich "apple" und "green_apple" haben. Infolgedessen verhinderte der Sheldon in mir, dass ich mehrere Nächte lang schlief, bis ich dort hineinging und alles zu "vorgeladenen Objekten" umgestaltete. (Siehe meine Antwort.)
Mike Nakis
1
Ich mag deine vorinstallierten Objekte auf jeden Fall, aber wenn dein "Apfel" differenziert ist, musst du nicht trotzdem alles durchgehen, egal welche Methode du wählst?
RemcoGerlich
Möglicherweise haben Sie sogar eine separate Tabelle für den Beschreibungsnamen, um die Internationalisierung zu unterstützen.
Erik Eidt
1
@MikeNakis und das Refactoring ist im Wesentlichen ein Suchen und Ersetzen über Ihre gesamte Codebasis, wobei Fruit.Apple durch Fruit.GreenApple ersetzt wird. Wenn ich Hardcoded String-Werte verwende, würde ich ein Search & Replace über die gesamte Codebasis durchführen, um "apple" durch "green_apple" zu ersetzen, was ungefähr dasselbe ist. - Refactoring fühlt sich einfach besser an, weil die IDE das Ersetzen vornimmt.
Falco
4

Was Sie hier erwarten, ist, dass die Programmierlogik automatisch an sich ändernde Daten angepasst werden kann. Einfache statische Optionen wie Enum funktionieren hier nicht, weil Sie in der Laufzeit keine zusätzlichen Enums hinzufügen können.

Ein paar Muster, die ich gesehen habe:

  • Enums + Standard zum Schutz vor einem brandneuen Datenbankeintrag, der den Tag Ihres Programms ruiniert.
  • Kodierung der auszuführenden Aktionen (Geschäftslogik) in der Datenbank selbst. In vielen Fällen ist dies sehr gut möglich, da viele Logiken wiederverwendet werden. Die Umsetzung der Logik sollte im Programm sein.
  • Zusätzliche Attribute / Spalten in der Datenbank, um den brandneuen Wert im Programm als "zu ignorierend" zu kennzeichnen, bis das Programm ordnungsgemäß implementiert wurde.
  • Fehlgeschlagene schnelle Mechanismen um den Codepfad, der die Werte aus der Datenbank lädt / neu lädt. (Wenn die entsprechende Aktion nicht im Programm enthalten ist UND sie nicht als zu ignorierend markiert ist, aktualisieren Sie sie nicht).

Im Allgemeinen finde ich es gut, wenn Daten vollständig sind, wenn sie sich auf implizite Aktionen beziehen - auch wenn die Aktionen selbst an anderer Stelle implementiert werden könnten. Jeder Code, der Aktionen unabhängig von den Daten bestimmt, hat gerade Ihre Datendarstellung zerstört, was höchstwahrscheinlich zu Abweichungen und Fehlern führen wird.

Subu Sankara Subramanian
quelle
4

Das Speichern an beiden Orten (in einer Tabelle und in einem ENUM) ist nicht so schlecht. Die Begründung lautet wie folgt:

Durch Speichern in einer Datenbanktabelle können wir die referenzielle Integrität in der Datenbank über Fremdschlüssel erzwingen. Wenn Sie also eine Person oder eine beliebige Entität einer Frucht zuordnen, ist dies nur eine Frucht, die in der Datenbanktabelle vorhanden ist.

Das Speichern als ENUM ist auch deshalb sinnvoll, weil wir Code ohne magische Zeichenfolgen schreiben können und der Code dadurch besser lesbar wird. Ja, sie müssen synchron bleiben, aber wie schwierig wäre es wirklich, der ENUM eine Zeile und der Datenbank eine neue insert-Anweisung hinzuzufügen.

Wenn eine ENUM definiert ist, darf der Wert nicht mehr geändert werden. Zum Beispiel, wenn Sie:

  • Apfel
  • Traube

Benennen Sie Trauben NICHT in Trauben um. Fügen Sie einfach eine neue ENUM hinzu.

  • Apfel
  • Traube
  • Trauben

Wenn Sie Daten migrieren müssen, wenden Sie ein Update an, um alle Trauben in Trauben zu verschieben.

Jon Raynor
quelle
Als weiteren Schritt habe ich in Shops gearbeitet, in denen Metadatenwerte ein Löschflag in der Tabelle haben, um anzuzeigen, dass sie nicht verwendet werden sollten (entweder wurden sie nicht mehr unterstützt oder es gibt eine neuere Version).
Robbie Dee
1

Sie haben Recht, diese Frage zu stellen. Eigentlich ist es eine schöne Frage, wenn Sie versuchen, sich gegen die Bewertung ungenauer Bedingungen zu verteidigen.

Das heißt, die Bewertung (Ihre ifBedingungen) muss nicht unbedingt im Mittelpunkt der Art und Weise stehen, wie Sie damit umgehen. Achten Sie stattdessen darauf, wie Sie die Änderungen verbreiten, die zu einem nicht synchronen Problem führen würden.

String-Ansatz

Wenn Sie Zeichenfolgen verwenden müssen, können Sie die Funktionalität zum Ändern der Liste über die Benutzeroberfläche bereitstellen. Konzipieren Sie das System so , dass bei einem Wechsel Grapeauf Grapes, zum Beispiel, aktualisieren Sie alle Datensätze zur Zeit verweisen Grape.

ID-Ansatz

Ich würde es immer vorziehen, auf eine ID zu verweisen, obwohl die Lesbarkeit eingeschränkt ist. The list may be added tokann wieder etwas sein, über das Sie benachrichtigt werden, wenn Sie eine solche UI-Funktion verfügbar machen. Wenn Sie sich mit der Neuordnung von Elementen befassen, die die ID ändern, geben Sie diese Änderung erneut an alle abhängigen Datensätze weiter. Ähnlich wie oben. Eine andere Option (gemäß der Normierungskonvention wäre, eine Spalte mit Ihrer Enumeration / ID zu haben - und auf eine detailliertere FruitDetailTabelle zu verweisen , die eine Spalte mit der Bezeichnung "Reihenfolge" enthält, die Sie nachschlagen können).

Wie auch immer, Sie sehen, ich schlage vor, die Änderung oder Aktualisierung Ihrer Liste zu kontrollieren. Ob Sie dies mithilfe eines ORM oder eines anderen Datenzugriffs tun, hängt von den Besonderheiten Ihrer Technologie ab. Was Sie im Wesentlichen tun, ist, dass Leute, die sich von der DB entfernen, solche Änderungen vornehmen müssen - was meiner Meinung nach in Ordnung ist. Die meisten Haupt-CRMs stellen die gleichen Anforderungen.

JᴀʏMᴇᴇ
quelle
1
In der Datenbank wird die numerische ID für untergeordnete Datensätze gespeichert, um dieses Problem zu vermeiden. Diese Frage bezieht sich auf die Schnittstelle zu einer Programmiersprache.
Clockwork-Muse
1
@ Clockwork-Muse - um welches Problem zu vermeiden? Das ergibt keinen Sinn.
3.
Ich benutze den ID-Ansatz ziemlich oft, aber die ID ist gesperrt und kann nicht geändert werden. Die angehängte Zeichenfolge kann natürlich, weil die Leute häufig Dinge wie "LKW" in "LKW" usw. umbenennen, während sich das Ding selbst (dargestellt durch ID) nicht ändert.
Brian Knoblauch
Wie gehen Sie beim ID-Ansatz mit Entwicklungs- und Produktionsdatenbanken um? Bei automatisch inkrementierten IDs führt das Hinzufügen von Elementen zu beiden DBs in unterschiedlicher Reihenfolge zu unterschiedlichen IDs.
Beschützer ein
Muss nicht automatisch erhöht werden? Dies sollte in diesem Fall nicht der Fall sein, insbesondere wenn es sich um den von uns verwendeten ganzzahligen Wert der zugrunde liegenden Aufzählung handelt.
18.
0

Ein sehr häufiges Problem. Das Duplizieren der Daten-Client-Seite scheint zwar gegen die DRY- Prinzipien zu verstoßen , ist jedoch auf den Paradigmenunterschied zwischen den Ebenen zurückzuführen.

Es ist auch nicht ungewöhnlich, dass die Aufzählung (oder was auch immer) nicht mit der Datenbank Schritt hält. Möglicherweise haben Sie einen anderen Wert in eine Metadatentabelle verschoben, um eine neue Berichtsfunktion zu unterstützen, die im clientseitigen Code noch nicht verwendet wird.

Manchmal passiert es auch anders herum. Ein neuer Aufzählungswert wird auf der Clientseite hinzugefügt, aber das DB-Update kann erst durchgeführt werden, wenn der DBA die Änderungen übernehmen kann.

Robbie Dee
quelle
Ja, Sie haben das Problem beschrieben. Was ist Ihre Lösung?
Beschützer ein
1
@Protectorone Sie nehmen an, dass es eine Silberkugel-Lösung gibt, die meiner Erfahrung nach eine falsche Annahme ist. Das Beste, auf das Sie hoffen können, ist, dass eine Geschäftseinheit die Problemdomäne besitzt, sodass Sie zumindest sehen können, welche Seite zurückliegt - Clientseite oder Datenbankseite. Bank- und Finanzwesen sind in dieser Hinsicht in der Regel sehr effizient, da der Einzelhandelssektor diesbezüglich merklich weniger zu tun hat ...
Robbie Dee,
0

Unter der Annahme, dass es sich im Wesentlichen um eine statische Suche handelt, ist die dritte Option - die Aufzählung - im Wesentlichen die einzig vernünftige Wahl. Es ist das, was Sie tun würden, wenn die Datenbank nicht involviert wäre, also macht es Sinn.

Die Frage ist dann die, wie man Enums und statische / Nachschlagetabellen in der Datenbank synchron hält, und leider ist das kein Problem, auf das ich noch eine vollständige Antwort habe.

Wahlweise führe ich alle Schemawartungen im Code durch und kann daher eine Beziehung zwischen einem Build der Anwendung und einer erwarteten Schemaversion aufrechterhalten, sodass es einfach ist, die Suche und die Aufzählung synchron zu halten, aber es ist etwas, an das man sich erinnern muss machen. Es wäre besser, wenn es automatisierter wäre (und auch ein automatisierter Integrationstest, um sicherzustellen, dass die Aufzählungen und Nachschlageergebnisse übereinstimmen), aber das habe ich noch nie implementiert.

Murph
quelle
1
Ich glaube nicht, dass dies nur statische Suchvorgänge sind, da sie sonst einfach aus der Datenbank abgerufen und unverändert verwendet werden könnten. Das Problem, wie ich es verstehe, ist, wenn die Geschäftslogik abhängig vom verwendeten Nachschlagewert angewendet werden soll. Abgesehen davon werden im Allgemeinen Ja-Enums für diesen Zweck verwendet.
Robbie Dee
Ok, ich brauche einen besseren Begriff für "statisches Nachschlagen". Der von Ihnen beschriebene Kontext ist das, was ich gemeint habe. :) Der Schlüssel ist "statisch". Dies sind Werte, die das Problem nicht ändern. aber nicht die Absicht) für bestehende Werte.
Murph