Datenbankzugriffsschicht ohne mit Singletons um sich zu schmeißen?



  • Hi,

    ich habe mir Mal Gedanken gemacht. Ich arbeite gerade an einem Java-Projekt (ja, das ist das C++-Forum, komm ich gleich zu) und dort nutzen wir für DB-Zugriffe halt JDBC und dann haben wir uns so eine eigene, simple Datenbankzugriffsschicht gebastelt. Wir haben quasi für jede Tabelle (bzw. jeden Komplex von zusammengehörigen Tabellen) eine Klasse, welche ein Singleton ist und über die man DB-Zeilen lesen und schreiben kann.

    Lädt man was aus der DB, merkt sich die Klasse auch alle Vorkommnisse davon.

    So, das ist jetzt erstmal nicht wirklich schön. Wir haben für jede DB-Klasse ein Singleton, was ja erstmal unglaublich hässlich erscheint. Andererseits gibt es diese DB-Tabellen ja eben nur einmal und eine globale Zugriffsschicht ist schon nötig, da von anderen "Schichten" eben immer Zugriffe erfolgen.

    Frage: Wie würde man so etwas schön in C++ lösen?

    Da gibt es ja Frameworks und alles, aber trotzdem interessiert mich, wie das alles aufgebaut ist. Wenn man keine Singletons nutzt, muss man ja ständig alle Instanzen wild herumreichen, damit alle, die damit arbeiten wollen, die auch zur Verfügung haben. Und das ist nicht wirklich schön.

    Hoffe, mein Problem wird klar. Wer schon Mal mit so was programmiert hat, weiß das vermutlich... Rückfragen zu meiner Frage beantworte ich natürlich auch gern.



  • Wie sieht´s denn mit einer Datenbankklasse aus, die intern Repräsentationen der Tabellen hält? Damit wären die Tabellen zumindest für die Datenbank einzigartig, ohne dass man sie als Singleton implementieren müsste.



  • Das wäre eine Möglichkeit. Jetzt müssten aber alle Zugriffe zentral über diese eine Klasse laufen. Wir hätten eine gesamte Schicht auf nur einer Klasse abgeliefert. Hm... klingt nicht sehr flexibel. Wenn wir etwas an der Klasse ändern wollen, betrifft das direkt hunderte/tausende von anderen Klassen.

    Man kann natürlich argumentieren, dass wir diese eine Klasse so schlicht machen, dass die wesentlichen Änderungen nur dahinter passieren, aber lösen wir das Problem wirklich, wenn wir immer über die eine Klasse gehen müssen, um uns die einzelnen Tabelleninstanzen zu holen? Erscheint mir etwas a la Gottklasse.



  • Wieso Gottklasse? Ich habe ja nicht behauptet, dass du daras ein monolithisches Monster bauen sollst. Wenn man das vernünftig designt designed entwirft kann man da ein sehr flexibles Framework machen.

    Aus der Hüfte geschossen:

    class DBField:
    - name
    - type
    - DBTable
    - Constraints
    - etc.

    class DBValue
    - DBField
    - DBRow
    - Status

    class DBRow
    - DBTable
    - Status

    class DBTable
    - name
    - DBFields[]
    - DBRows[]

    class DB
    - Connection (z.B. Odbc/Native)
    - DBTable[]

    Komplex wird das Ganze natürlich dann, wenn man Caches implementieren will, z.B. WriteBehind.



  • Eisflamme schrieb:

    Wenn man keine Singletons nutzt, muss man ja ständig alle Instanzen wild herumreichen, damit alle, die damit arbeiten wollen, die auch zur Verfügung haben. Und das ist nicht wirklich schön.

    1. Singleton ist nicht und war nie dafür gedacht als bunte Verpackung für globale Variablen zu dienen, auch wenn alle Welt das zu glauben scheint.
    2. Doch das ist schön, schöner als Singletons weil keine Abhängigkeiten verschleiert werden. Wenn ein Objekt ein andres benötigt dann bekommts eine Referenz drauf und fertig. Wenn das "Instanzen herumreichen" zuviel Arbeit wird dann ist das Problem das es zu lösen gilt nicht der Aufwand mit dem Herumreichen sondern die Tatsache dass die Klassen viel zu eng miteinander verbandelt sind. Der Aufwand ist nur ein Symptom dieses Problems. Mit dem üblichen "Singleton" lindert man die Symptome aber löst nicht das Problem. Das führt dann dazu dass man erstmal munter weitermacht weils ja im Moment nichtmehr wehtut. Bis zu dem Punkt wo die Schmerzmittel auch nichtmehr helfen...



  • Die Idee, Tabellen der DB auf Tabellenobjekte abzubilden, ist meiner Meinung nach ziemlicher Blödsinn. JDBC/ODBC kennen ein Recordset, das eben die Ergebnismenge einer SQL-Abfrage enthält. Ich kann aber keinen Sinn darin sehen, dieses Recordset global zugreifbar zu machen.
    Man könnte die SQL-Anweisung global machen, um bei einer Abfrage nur noch die Bindevariablen setzen zu müssen. Man spart sich das Parsen auf der Datenbankseite, die Abfrage kann also insgesamt schneller ausgeführt werden. Moderne Datenbanksysteme enthalten aber ihrerseits Cachingmechanismen für Abfragen, sodass der Vorteil eher marginal ist.
    Als einziges sinnvolles globales Objekt bleibt die Connection, die man dann sicher als Singelton ausführen könnte.



  • manni66 schrieb:

    Als einziges sinnvolles globales Objekt bleibt die Connection, die man dann sicher als Singelton ausführen könnte.

    Was ich nicht tun würde. Wer sagt dass es nur eine Connection geben darf!?





  • dot schrieb:

    manni66 schrieb:

    Als einziges sinnvolles globales Objekt bleibt die Connection, die man dann sicher als Singelton ausführen könnte.

    Was ich nicht tun würde. Wer sagt dass es nur eine Connection geben darf!?

    Das hängt von der Anwendung ab. Wenn sie mehrere Connections benötigt, funktioniert es eben nicht. Benötigt sie nur eine oder eine fest vorgegebene Anzahl, kann man es mit einem Singleton lösen, man muss aber nicht. Benötigt man viele verschiedene Connections, so kann man einen Connection Pool verwenden, der letzten Endes wieder eine globale Verwaltung darstellt.



  • Es gibt einen wesentlichen Unterschied zwischen benötigt nur eine und es darf bis ans Ende aller Tage um keinen Preis der Welt jemals mehr als eine geben. Singleton ist für letzteres gedacht 😉



  • dot schrieb:

    Es gibt einen wesentlichen Unterschied zwischen benötigt nur eine und es darf bis ans Ende aller Tage um keinen Preis der Welt jemals mehr als eine geben. Singleton ist für letzteres gedacht 😉

    Du weißt aber schon, dass man seinen Code auch ändern kann, wenn sich die Anforderungen ändern 😉



  • ...und genau dann offenbart sich das große Problem mit Singleton 😉



  • dot schrieb:

    ...und genau dann offenbart sich das große Problem mit Singleton 😉

    Welches?



  • Du musst nun sämtlichen Code anfassen der den "Singleton" verwendet und den "Singleton" rausfactoren und durch ne Referenz ersetzen. Da der "Singleton" ja so einfach von überall aus erreichbar ist ist das vermutlich sehr viel Code da du dir beim Schreiben des Codes mit "Singleton" nicht viele Gedanken darüber gemacht hast was, wann, wie, wo wirklich Zugriff auf welche Schnittstelle braucht (99.99% aller "Singletons" werden ja von vornherein von faulen Programmierern verwendet um sich genau keine Gedanken machen zu müssen). Dein Code ist also an allen Ecken und Enden übersäht mit Zugriffen auf den "Singleton" und das so gewachsene Design vermutlich an einigen Stellen gar nicht wirklich für eine ordentlichen Lösung über Dependency Injection geeignet. D.h. den Singleton loszuwerden bedeutet ein komplettes redesign großer Teile des Systems. Wenn du es stattdessen einfach von vornherein so gebaut hättest dass Objekte die Objekte benötigen diese über entsprechende Referenzen explizit kennen wäre das einzige was du nun tun müsstest einfach eine zweite Datenbankverbindung zu instanzieren, den entsprechenden Objekten das andere Datenbankverbindungsobjekt zu reichen und das wars.
    Ich muss dazu sagen dass ich selber wenig Erfahrung mit dem Problem habe da ich Singleton schon immer rein gefühlsmäßig aus dem Weg gegangen bin, noch bevor ich eigentlich genau wusste warum. Ein paar Mal habe ich aber auch mit dem Gedanken gespielt einen Singleton einzusetzen und ich war noch jedesmal später froh es nicht getan zu haben da es jedesmal später im Projekt genau zu diesem Problem gekommen wär. Natürlich könnte man sagen ich hatte einfach Pech. Aber wir hatten auf der Uni auch mal eine lustige Dikussion über genau dieses Problem und ich kann dir sagen dass da in einer Gruppe von 10 Leuten mindestens 5 dabei waren deren Erzählungen genau so begonnen haben: "Wir hatten da mal eine Datenbankverbindung..."

    "Singletons" sind wie Krebs. Wenn du merkst dass du sie hast ist es meist zu spät, sie haben schon überall in deinen Code metastasiert...



  • dot schrieb:

    Du musst nun sämtlichen Code anfassen der den "Singleton" verwendet und den "Singleton" rausfactoren und durch ne Referenz ersetzen. Da der "Singleton" ja so einfach von überall aus erreichbar ist ist das vermutlich sehr viel Code da du dir beim Schreiben des Codes mit "Singleton" nicht viele Gedanken darüber gemacht hast was, wann, wie, wo wirklich Zugriff auf welche Schnittstelle braucht (99.99% aller "Singletons" werden ja von vornherein von faulen Programmierern verwendet um sich genau keine Gedanken machen zu müssen). Dein Code ist also an allen Ecken und Enden übersäht mit Zugriffen auf den "Singleton" und das so gewachsene Design vermutlich an einigen Stellen gar nicht wirklich für eine ordentlichen Lösung über Dependency Injection geeignet. D.h. den Singleton loszuwerden bedeutet ein komplettes redesign großer Teile des Systems. Wenn du es stattdessen einfach von vornherein so gebaut hättest dass Objekte die Objekte benötigen diese über entsprechende Referenzen explizit kennen wäre das einzige was du nun tun müsstest einfach eine zweite Datenbankverbindung zu instanzieren, den entsprechenden Objekten das andere Datenbankverbindungsobjekt zu reichen und das wars.

    Ehrlich gesagt weiß ich nicht wieso das besser sein sollte. Woher weißt du jetzt welches Objekt welche Datenbankverbindung bekommen soll und wieviel Connections du schon hast? Wahrscheinlich brauchst du ein Singleton, das mitzählt. Die Connection sollte man aber innerhalb von Transaktionen rumreichen und nicht überall das Singleton verwenden.



  • aaaaaaaaaüö schrieb:

    Woher weißt du jetzt welches Objekt welche Datenbankverbindung bekommen soll und wieviel Connections du schon hast?

    Wenn du das nicht weißt ist doch sowieso alles sinnlos?

    aaaaaaaaaüö schrieb:

    Wahrscheinlich brauchst du ein Singleton, das mitzählt.

    wofür?

    aaaaaaaaaüö schrieb:

    Die Connection sollte man aber innerhalb von Transaktionen rumreichen und nicht überall das Singleton verwenden.

    Wenn du das tust dann hast du das Problem natürlich nichtmehr. Das ist aber eben genau nicht wie 99.99% aller "Singletons" da draußen verwendet werden...



  • dot schrieb:

    aaaaaaaaaüö schrieb:

    Woher weißt du jetzt welches Objekt welche Datenbankverbindung bekommen soll und wieviel Connections du schon hast?

    Wenn du das nicht weißt ist doch sowieso alles sinnlos?

    aaaaaaaaaüö schrieb:

    Wahrscheinlich brauchst du ein Singleton, das mitzählt.

    wofür?

    Ich geh mal davon aus, dass man mit mehreren Threads arbeitet und nicht jeder Thread für immer eine Connection offen halten kann, also die Anzahl der Connections begrenzt ist, sonst ist es relativ simpel. Dann kannst du wirklich alle Connections beim Programmstart verteilen und rumreichen.



  • Und wo genau profitiert man in diesem Szenario von Singleton? Und wer sagt dass alle Connections überhaupt zur selben Datenbank laufen?



  • Wenn du 100 Threads hast und nur 10 Connections, kannst du immer 10 Threads die gleiche Connection geben und die anderen 9 dann warten lassen solange ein Thread die Connection verwendet. Oder du hast ein Singleton, dass die 10 Connections verwaltet und eine an den Thread gibt der gerade eine braucht. Dann müssen die Threads erst warten wenn alle Connections verwendet werden und nicht wenn einer zufällig gerade die gleiche braucht.



  • Ja, aber das hat doch absolut nichts mit dem Singleton Pattern zu tun!? Die entsprechende Funktionalität würd ich hinter einem DatabaseConnection Objekt kapseln das ich einfach weiterreiche an wen auch immer es braucht. Ich seh jedenfalls wirklich nicht wo genau man da jetzt den Singleton Pattern brauchen sollte!?


Anmelden zum Antworten