Artikel

Das Datenmodell eines Discord-Bots strukturieren

Ein gutes Datenmodell hält Identität, Serverbezug und fachliche Geschichte auseinander. An Mitgliedsstatistiken und Tickets erklärt dieser Artikel Beziehungen, Eindeutigkeit, JSON-Felder und Löschregeln anhand von Yurnas Prisma-Schema und einem überprüfbaren Erweiterungsentwurf.

BlackZackBlackZack

1615 Wörter · 9 Min. Lesezeit

  • yurna
  • datenmodell
  • prisma
  • sqlite
Das Datenmodell eines Discord-Bots strukturieren

Schematische Illustration zur Artikelserie; keine privaten Betriebsdaten.

Ein Mitglied kann auf zwei Discord-Servern unterschiedliche Fortschritte besitzen und trotzdem dieselbe Person sein. Ein Ticket kann seine Kategorie verlieren, ohne dass die Unterhaltung verschwinden soll. Eine Einstellung kann flexibel sein, aber trotzdem regelmäßig gefiltert werden müssen. Solche Fragen bestimmen ein Datenmodell stärker als die Wahl schöner Tabellennamen. Wer sie zu spät beantwortet, verteilt Ausnahmen über sämtliche Botbefehle und Dashboardseiten. Ein gutes Schema macht die wichtigsten Zusammenhänge sichtbar und verhindert Zustände, die fachlich keinen Sinn ergeben.

Yurnas gemeinsames Prisma-Schema beschreibt viele Bereiche in einer Datenbank: Benutzerkonten, Sitzungen, Server, Mitgliedsstatistiken, Tickets, Giveaways und Verwaltungsdaten. Für diesen Artikel reichen zwei Ausschnitte. MemberStats verbindet Server und Nutzer mit XP, Reputation und Coins. Das Ticketmodell trennt den eigentlichen Vorgang von Nachrichten, Notizen, Aktionen und Transkripten. Diese vorhandenen Strukturen zeigen, wie globale Identität, serverbezogene Daten und Verlauf zusammenarbeiten können. Ihre konkrete Nutzung im laufenden Betrieb wird damit nicht vorausgesetzt.

Identität und Mitgliedschaft auseinanderhalten

Die Discord-Identität einer Person ist nicht ihr Anzeigename. Namen können sich ändern und müssen nicht überall eindeutig sein. Ein Datenmodell verwendet deshalb stabile Kennungen für Beziehungen und hält lesbare Namen als veränderliche Darstellungsinformationen. Das bedeutet nicht, dass technische Kennungen ständig in der Oberfläche erscheinen müssen. Die Oberfläche zeigt verständliche Namen, während der Datenzugriff die richtige Person unabhängig von einer späteren Umbenennung wiederfindet.

Serverbezogener Fortschritt gehört zu einer Mitgliedsbeziehung. Würden XP nur am globalen Benutzerkonto gespeichert, könnte Aktivität auf einem Server unbeabsichtigt die Rangliste eines anderen verändern. Yurnas MemberStats besitzt deshalb eine eindeutige Kombination aus Server und Nutzer. Diese Regel ist zugleich Dokumentation und technische Absicherung: Für dieselbe Kombination soll es genau einen aktuellen Statistikdatensatz geben. Die Anwendung muss nicht bei jeder Abfrage selbst erraten, welcher von mehreren widersprüchlichen Einträgen gültig ist.

Der Unterschied wirkt sich auch auf Lösch- und Austrittsregeln aus. Ein Mitglied kann einen Server verlassen, ohne dass sein globales Konto verschwunden ist. Ob der serverbezogene Fortschritt aufbewahrt, zurückgesetzt oder nach einer Frist entfernt wird, ist eine eigene Produktentscheidung. Sie sollte nicht zufällig daraus folgen, dass ein Entwickler eine Beziehung bequem kaskadierend gelöscht hat. Das Schema setzt eine gewählte Regel um; es ersetzt nicht die Entscheidung, welche Geschichte erhalten bleiben soll.

Beziehungen mit klaren Eigentümern

Ein Ticket gehört zu einem Server. Seine Nachrichten gehören zu diesem Ticket. Eine Kategorie ordnet das Ticket, ist aber nicht zwingend dessen Lebensgrundlage. Im betrachteten Schema kann eine entfernte Kategorie ihren Bezug verlieren, während das Ticket bestehen bleibt. Daran lässt sich eine hilfreiche Frage ablesen: Kann dieses Objekt ohne das andere noch sinnvoll existieren? Für eine Nachricht ohne Ticket lautet die Antwort eher nein. Für ein abgeschlossenes Ticket ohne noch aktive Kategorie kann sie durchaus ja lauten.

Fremdschlüssel können solche Beziehungen technisch absichern. Die SQLite-Dokumentation zu Fremdschlüsseln beschreibt Existenzbeziehungen und unterschiedliche Reaktionen auf Änderungen. Für den Entwurf sollten diese Reaktionen einzeln betrachtet werden. Automatisches Mitlöschen, Verhindern der Löschung und Entfernen eines optionalen Bezugs haben verschiedene fachliche Folgen. Eine pauschale Wahl für alle Tabellen ist selten passend, gerade wenn aktuelle Konfiguration und historische Vorgänge miteinander verbunden sind.

Ein Beispiel ist eine stillgelegte Ticketkategorie „Technische Hilfe“. Alte Tickets sollen weiter verständlich bleiben, neue Anfragen werden jedoch anders einsortiert. Der aktuelle Bezug kann entfallen, während ein bewusst gespeicherter historischer Kategoriename die damalige Einordnung erklärt. Diese Momentaufnahme ist etwas anderes als eine versehentlich veraltete Kopie. Sie wird ausdrücklich als damaliger Wert geführt. Damit bleibt klar, welche Information aktuell gepflegt wird und welche eine frühere Entscheidung dokumentiert.

Zustände und Ereignisse erfüllen andere Aufgaben

Der aktuelle Ticketstatus beantwortet, was jetzt gilt. Eine Aktionsliste beantwortet, wie dieser Zustand entstand. Beides ist nützlich, aber nicht austauschbar. Ohne aktuellen Status müsste jede Übersicht die gesamte Geschichte auswerten. Ohne Geschichte wäre eine Wiederöffnung kaum von einem nie abgeschlossenen Ticket zu unterscheiden. Das Modell kann deshalb einen kompakten aktuellen Zustand mit einer begrenzten Folge fachlicher Ereignisse verbinden, solange beide konsistent gepflegt werden.

Nicht jedes technische Ereignis gehört in diese Geschichte. Ein fehlgeschlagener HTTP-Aufruf kann für die Diagnose wichtig sein, ist aber nicht automatisch eine neue Ticketaktion. Fachliche Ereignisse sollten Handlungen wie Übernahme, Abschluss oder Wiederöffnung ausdrücken. Technische Protokolle folgen anderen Aufbewahrungs- und Auswertungsregeln. Werden beide ungetrennt in einer Liste gesammelt, wird die menschlich relevante Geschichte zwischen Routinefehlern und Wiederholungen schwer erkennbar.

Für einen Erweiterungsentwurf erhält ein Ticket eine Versionsnummer. Jede fachlich relevante Änderung erhöht sie. Ein offenes Dashboard sendet die gelesene Version beim Speichern mit. Stimmen alte und aktuelle Version nicht überein, wird ein Konflikt gemeldet. Diese Zahl ist keine vollständige Ereignisgeschichte, sondern ein Mittel gegen stilles Überschreiben. Sie ergänzt den Status, statt ihn zu ersetzen. Ein einzelnes updatedAt kann ähnliche Aufgaben unterstützen, erfordert aber eine sehr sorgfältige Behandlung von Zeitauflösung und Vergleich.

-- Illustrativer Entwurf für einen begrenzten Ticketzustand.
CREATE TABLE ticket_example (
  id TEXT PRIMARY KEY,
  guild_id TEXT NOT NULL,
  status TEXT NOT NULL,
  version INTEGER NOT NULL DEFAULT 1,
  CHECK (version > 0),
  CHECK (status IN ('open', 'waiting', 'closed'))
);

Das Beispiel zeigt nur einen kleinen Ausschnitt. Es ist keine Migration für Yurnas bestehendes Schema. Seine Bedingungen drücken aus, dass eine Version positiv und der Status aus einer begrenzten Menge stammen muss. Weitere Beziehungen und Felder würden separat ergänzt. Solche Regeln sollten möglichst nahe an der Speicherung abgesichert sein, wenn ein ungültiger Wert auch durch andere Anwendungen oder spätere Werkzeuge entstehen könnte. Eine schöne Formularprüfung allein schützt nicht alle Schreibwege.

JSON dort verwenden, wo die Form tatsächlich flexibel ist

Yurnas Schema enthält sowohl eigene Spalten als auch als Text gespeicherte strukturierte Daten. Das kann sinnvoll sein, wenn kleine zusätzliche Informationen gemeinsam gelesen und geschrieben werden. Ein frei erweiterbarer Konfigurationsblock ist ein anderer Fall als ein Feld, nach dem jede Verwaltungsübersicht filtert. Die Entscheidung für JSON sollte deshalb aus den tatsächlichen Zugriffen folgen. „Vielleicht brauchen wir später mehr Felder“ ist allein kein ausreichender Grund, sämtliche fachlichen Informationen in einen undurchsichtigen Block zu verschieben.

Im Ticketmodell wurde der Kanalbezug als eigene Spalte hervorgehoben; ein Kommentar nennt den Bedarf an gezielten Abfragen. Das ist ein konkretes Beispiel dafür, wie Nutzung das Schema verändert. Sobald ein Wert für Suche, Sortierung, Eindeutigkeit oder Beziehungen wichtig wird, lohnt sich ein ausdrückliches Feld. Dadurch können Datenbank und Entwicklungswerkzeuge mehr über seine Bedeutung wissen. Die Anwendung muss ihn nicht bei jeder Suche aus vielen unabhängigen Textblöcken herauslösen.

Flexible Felder brauchen trotzdem eine Formprüfung. Ein erfolgreiches JSON-Parsing bestätigt nur die syntaktische Gültigkeit. Es sagt nicht, dass ein erwartetes Array tatsächlich ein Array ist oder ein benötigter Schlüssel vorhanden ist. Ein Entwurf versieht größere flexible Strukturen deshalb mit einer Formatversion und einer begrenzten Prüfung beim Lesen. Unbekannte Versionen werden nicht still als aktuelles Format interpretiert. Das erleichtert spätere Änderungen und verhindert, dass beschädigte Daten als gültige Standardkonfiguration weiterlaufen.

Abfragen vor Indizes beschreiben

Ein Index sollte eine konkrete häufige Abfrage unterstützen. Bei Tickets könnte das die Liste offener Vorgänge eines Servers sein. Im Yurna-Schema gibt es dafür einen kombinierten Index auf Server und Status. Die Reihenfolge der Felder und zusätzliche Sortierungen müssen zum tatsächlichen Zugriff passen. Ein Index auf jedem einzelnen Feld ist keine automatische Leistungsverbesserung. Zusätzliche Indizes benötigen Platz und müssen bei Änderungen mitgepflegt werden.

Die SQLite-Dokumentation zur Abfrageplanung erläutert, wie Abfragen und Indizes zusammenwirken. Für die praktische Modellierung beginnt die Arbeit trotzdem mit verständlichen Fragen: Welche offenen Tickets gehören zu diesem Server? Welche Einträge liegen vor einem bestimmten Zeitpunkt? Welche Statistik gehört genau zu diesem Mitglied? Erst danach werden die passenden Zugriffspfade entworfen. So bleibt der Index eine begründete Entscheidung und kein vorsorglicher Schmuck im Schema.

Auch die Menge geladener Daten zählt. Eine Ticketübersicht braucht möglicherweise Titel, Status, Zuständigkeit und letzte Aktivität, aber nicht sämtliche Nachrichten und Anhänge. Das Modell sollte solche begrenzten Sichten ermöglichen. Werden große Beziehungen automatisch überall mitgeladen, kann eine gut indizierte Abfrage trotzdem unnötig teuer werden. Die fachliche Grenze zwischen Übersicht und Detailansicht hilft hier unmittelbar bei der technischen Gestaltung. Daten werden passend zur Entscheidung geladen, die gerade getroffen werden soll.

Eine Schemaänderung in mehreren Schritten denken

Angenommen, ein bislang im JSON gespeicherter Bearbeitungsgrund soll künftig filterbar sein. Eine sichere Umstellung beginnt mit einem neuen ausdrücklichen Feld und einer definierten Übersetzung der alten Werte. Danach werden vorhandene Datensätze kontrolliert übertragen. Erst wenn Leser und Schreiber mit dem neuen Feld umgehen können, wird die alte Darstellung entfernt. Ein sofortiges Löschen der bisherigen Struktur kann einen Rückweg unnötig erschweren und ältere Anwendungsversionen überraschend unbrauchbar machen.

Die Übertragung muss fehlende und ungewöhnliche Werte berücksichtigen. Ein alter leerer Eintrag ist nicht automatisch derselbe Zustand wie ein ausdrücklich gewählter Grund „keine Angabe“. Die Migrationsregel benennt solche Fälle und zählt sie in einem Prüfbericht. Dadurch wird sichtbar, ob die Annahmen über die alten Daten stimmen. Eine Änderung, die formal ohne Fehler durchläuft, kann fachlich trotzdem viele Werte falsch zuordnen. Deshalb ist die Kontrolle der Bedeutung ebenso wichtig wie die erfolgreiche Ausführung.

Für gemeinsame Datenbanken gilt zusätzlich, dass mehrere Anwendungen das Schema verwenden. Bot, Dashboard und Admin-API können zu unterschiedlichen Zeitpunkten aktualisiert werden. Eine Übergangsphase sollte daher mit den tatsächlich möglichen Versionskombinationen funktionieren. Eine neue Pflichtspalte ohne passenden Standard kann beispielsweise alte Schreibwege unterbrechen. Solche Abhängigkeiten werden vor der Veröffentlichung aufgeschrieben. Das Datenmodell ist eine gemeinsame Schnittstelle, auch wenn alle Anwendungen im selben Repository liegen.

Das Modell mit kleinen widersprüchlichen Beispielen prüfen

Ein guter Modelltest versucht nicht nur gültige Datensätze anzulegen. Er versucht auch, dieselbe Mitgliedsstatistik doppelt zu erstellen, eine Nachricht ohne Ticket einzufügen und einen unzulässigen Status zu speichern. Danach werden die vorgesehenen Löschregeln geprüft: Kategorie entfernen, Ticket schließen, Serverdaten gezielt bereinigen. Für jeden Fall muss vorher feststehen, welche Daten erhalten bleiben. Unerwartete Kaskaden fallen in einem kleinen künstlichen Bestand deutlicher auf als zwischen vielen echten Einträgen.

Der Erweiterungsentwurf wird außerdem mit zwei konkurrierenden Bearbeitungen getestet. Beide lesen dieselbe Version; nur eine Änderung darf auf dieser Grundlage erfolgreich sein. Die andere erhält den aktuellen Zustand. Anschließend wird eine alte flexible Datenstruktur geladen und in die neue Darstellung überführt. Die Prüfung vergleicht fachliche Werte, nicht nur die Anzahl der Zeilen. So wird aus dem Schema ein überprüfbarer Vertrag zwischen den Anwendungen.

Ein gutes Datenmodell reduziert die Zahl stiller Annahmen. Es hält fest, welche Identität global ist, welche Werte zu einem Server gehören und welche Geschichte nach Änderungen weiter verständlich bleiben muss. Yurnas vorhandenes Schema bietet dafür konkrete Beispiele. Der wichtigste Fortschritt liegt jedoch nicht in möglichst vielen Tabellen. Er liegt in Beziehungen und Grenzen, die auch dann verständlich bleiben, wenn zwei Personen gleichzeitig arbeiten oder eine Funktion Jahre später erweitert wird.