
Otis: Zentrale Spielerstammdaten für Minecraft (Java & Micronaut)
Otis: Zentrale Spielerstammdaten für Minecraft (Java & Micronaut)
Vorwort
Otis ist ein Java Micronaut Service (Otis) und ein gleichnamiges Projekt, welches Spielerstammdaten zentral zur Verfügung stellt. Zu Spielerstammdaten gehören Daten wie zum Beispiel die Java UUID (v4) von Mojang in Minecraft, eine interne UUID (v7) für die Verwaltung der externen Software unseres Ökosystems, der eigentliche Minecraft Spielername, die Sprache des Spielers und wann der Spieler das erste und letzte Mal dem Minecraft Server beigetreten ist.
Da Otis als zentrale Anlaufstelle für Spielerstammdaten (als Schnittstelle) unseres Ökosystems verwendet wird, kommen weitere Anwendungsfälle wie ein MetaData Service, Ban Service, Discord Bot usw. infrage. Wir möchten uns auf eine zentrale Spielerdatenbank verlassen können, denn aktuell werden Prozesse wie Datenerhebung und -löschung dezentral - wenn existent - in verschiedenen Stellen teils manuell ausgeführt, was dem Datenschutz (Datenschutzgrundverordnung in Deutschland / EU) im Weg steht. Otis als Microservice und Minimum Viable Product (MVP) ermöglicht es uns zudem Fehler und Erfahrung mit der aktuellen Infrastruktur zu sammeln. Bedenkenswert daran ist, dass diese zentrale Komponente in unserer Infrastruktur deshalb systemkritisch ist, da die darauf abhängigen Services ohne Daten unter Umständen nicht funktionieren können und deshalb einen hohen Schutz- und Verfügbarkeitsbedarf (Hochverfügbarkeit, Ausfallsicherheit, Vertraulichkeit) hat.
Problem
Aus Entwicklersicht haben wir keine eindeutige Datenquelle (sogenannte “Single source of Truth”) für die Spielersuche, für die Konvertierung von Spielernamen zu ihrer UUID und zur Verarbeitung von internen Daten (externe, abhängige Systeme). Durch fehlende zentralen Datenquellen wie Otis sie zur Verfügung stellt, sind Entwickler gezwungen, ihre Daten von verschiedenen Quellen (Datenbanken von eigenen oder third party plugins und Software) zusammenzutragen oder ihre eigenen Datenquellen zu erzeugen. Folgend verliert man als Team den Überblick über schon vorhandene Daten, hat möglicherweise duplizierte Daten und -sätze und entwickelt am Datenschutz vorbei, da eine zentrale Löschung nicht mehr möglich ist. Fehler und komplexe Abhängigkeiten sind eine weitere Folge - eine zerstreute Infrastruktur, bei der man wiederum als Systemadministrator bei Migrationen der Datenbanken und -quellen leicht den Überblick verliert.
Administrator und Moderation haben in ihren täglichen Prozessen ohne Otis mit einem erhöhten Organisationsaufwand zu kämpfen. Darunter fällt indirekt die Verwaltung von Spielern auf Servern, zum Beispiel globale Freischaltungen und Banns auf Servern oder die Erkennung von Spielern nach Namenswechseln und ein erhöhter Kommunikationsaufwand gegenüber anderen Teammitgliedern und Spielern.
Die Entstehung von dezentralen Datenquellen ist nicht unbedingt durch technologische Probleme verursacht, denn auch soziale Komponenten spielen einen entscheidenden Faktor. Zu Beginn eines Software Projektes wie zum Beispiel eines Minecraft Servers gibt es wenig bis keine Entwickler, es werden auch Drittanbieter für Features verwendet (“heruntergeladene Plugins”) und gegebenenfalls Minispiele von ein oder mehreren Entwicklern entwickelt, die prototypenähnliche Merkmale haben. In vielen Fällen fehlen regelmäßige Termine für diese Entwickler, um sich auf Standards und gemeinsame Schnittstellen, Systeme zu einigen. Dadurch kann es vorkommen, dass Minispiel 1: “Bedwars” und Minispiel 2: “SkyPvP” Daten über Spieler teilen, wie zum Beispiel:
Spielername, UUID
IP Addresse
Spracheinstellungen
Texturen vom Spieler
Kills und Tode des Spielers im Scoreboard in den Spielmodis
Score (meist Punkte)
Globales Spielgeld / Währung,…
Meist sind Entwickler an einzelnen Projekten beschäftigt und wissen nicht unbedingt, was ihr Vorgänger oder ein weiteres Teammitglied an Datenquellen zur Verfügung hat, weshalb diese Überlegung einer zentralen Anlaufstelle für Spielerdaten selten überlegt wird. Wenn sie doch etabliert wird, findet man diese in “Core” Systemen, die meist ein weiteres Plugin sind und eine feste Abhängigkeit voraussetzen - jeder Entwickler muss sie implementieren, da diese Systeme sehr viel beherbergen, meist über das Ziel einer zentralen Datenquelle hinaus. Zudem sind diese Systeme sehr wartungsintensiv, da sie als Plugin selbst auch up to date mit der Paper / Spigot oder Minestom API sein müssen und ihre zusätzlichen Abhängigkeiten auch sogenannte “breaking changes” mit sich bringen können, wenn sie ein Update bringen. Dadurch entsteht unter Entwicklern der Unmut, dieses System zu verwenden, weil sie nicht auf ein Update des “Core Systems” in ihrem Plugin / System warten können und erfinden ihre eigene Datenquelle oder basieren auf ein anderes Plugin, welches ihre benötigten Spielerdaten in Teilen liefert. Aus unseren Beobachtungen scheitern diese Versuche der zentralen Daten- und Codequelle auch daran, dass Entwicklerteams eine hohe Fluktuation an Entwicklern haben können und Dokumentation nicht zum passenden Zeitpunkt vorhanden ist.
Zu den technische Komponenten, die eine zentrale Quelle von Spielerstammdaten verhindern, gehören unterschiedliche Wissenstände der Entwickler und eine fehlende gemeinsame Wissensdatenbank, auf die basierend auch auf (API-) Standards gesetzt werden kann. Diese Faktenlage ist wichtig für zukünftige Entwicklungen und Diskurse. Ist so eine Wissensdatenbank nicht vorhanden, zum Beispiel als Plattform, wird technisch entweder alleine entwickelt (jeder für sich, in der gleichen Quelle) oder sogenannte “bad practice” etabliert - die zentrale Quelle wird technisch komplex, nicht skalierungssicher oder die API wird unnutzbar, da sie Fachkenntnis über den internen Ablauf voraussetzt → schlechtes API Design.
Die genaue Definition der Fachlichkeit ist nicht Teil dieses Blogs, dennoch beschränken wir uns hier auf das Wissen über die uns von eingesetzten Technologien und Standards außerhalb des Minecraft “Kosmos”.
TLDR: Eine zentralisierte Software ist abhängig von fachlichen, technischen und sozialen Entscheidungen und Kenntnissen, der erste Schritt ist die Kommunikation miteinander!
Otis kann nicht das Problem einer universellen Spielerdatenbank lösen. Primär sammelt es nur spezifische Daten, die von unserem Team als fachlich relevant eingestuft wurden. Darüber hinaus besteht ein Kommunikationsproblem. Unter spezifischen Umständen ist nicht direkt ersichtlich, dass Otis die zentrale Lösung für Spielerdaten darstellt. Ohne konkrete Einarbeitung besteht die Gefahr, dass beispielsweise bei neuen Minispielen auf eigene Lösungen gesetzt wird, anstatt die bereits existierende zu nutzen.
Eine andere Alternative zu unserem Projekt stellt eine zentrale Datenbank dar, die entweder mit einer SQL- oder NoSQL-Struktur betrieben wird. Hier verbinden sich die Plugins als Clients, um Anfragen zu stellen und die benötigten Daten abzurufen. Diese Alternative scheitert jedoch an mehreren Punkten. Sie setzt eine starke Fachkenntnis voraus, benötigt eine Lösung für duplizierte Daten (beispielsweise unterschiedliche UUID-Felder wie BUUID für Bedwars-spezifische Daten in verschiedenen Spielmodi) und erfordert ein durchdachtes Konzept für die Datenarchivierung. Weitere OpenSource-Alternativen, die unseren Anwendungsfall innerhalb des Minecraft-Rahmens abdecken könnten, lassen sich nicht finden.
Lösung
Otis stellt eine Schnittstelle zur Verwaltung von Spielerstammdaten zur Verfügung. Dazu verwaltet (erstellen, updaten) es selbstständig bestimmte Stammdaten wie die Spieler UUID, erster und letzter Netzwerkbeitritt und den Namen des Spielers.
Jetzt wird es technisch - Otis setzt auf eine geschichtete (layered) Architektur mit dem Microservice Prinzip im zustandslosen (stateless) Design. Dabei unterteilen wir im Gradle Projekt zwischen Backend, Client und Velocity Plugin. Das Velocity Plugin basiert auf den Client und der Client wird anhand der OpenAPI Dokumentation aus dem Backend generiert. Das Backend ist intern in sogenannten drei Schichten aufgeteilt. Die erste Schicht repräsentiert DTOs und Controller und dient zur Kommunikation mit dem Client. Der Controller übergibt Anfragen über DTOs via Dependency Injection an die Serviceschicht, diese übernimmt Validierung sowie andere Aufgaben und übergibt diese an die Datenbankschicht. Die Datenbankschicht hat auch manchmal das Schlüsselwort “Repository” enthalten.
DTOs
Data Transfer Objects sind mit OpenAPI Annotationen dokumentiert und erlauben das automatische Generieren von Dokumentation für OpenAPI / Swagger. Als netter Nebeneffekt können wir über sogenannte Validierungsgruppen Inhalte vorvalidieren. Vorvalidieren bedeutet, dass überprüft wird, ob zum Beispiel ein Spielername auch nur maximal 16 Zeichen lang ist oder die Zeiten (Unix Time) im negativen Bereich liegt. Als großes Plus können wir aufgrund der OOP Struktur explizite Antworten (Responses) geben, wenn es zu Fehlern kommt ohne dass wir Stacktraces zurückgeben. Damit ist eine erhöhte Sicherheit gegeben, da Stacktraces nur serverseitig geloggt werden. Stacktraces verraten Code Strukturen und erleichtern Reverse Engineering ohne Code.
Serviceschicht
Nachdem eine Anfrage bei dem Controller angekommen ist, landet sie in der Serviceschicht. Die Serviceschicht hat die Möglichkeit, Daten zu validieren, anzureichen und zu verändern bevor sie in die Datenbank gelangen. Dadurch können wir in der Datenbank Felder setzen, die wir nicht nach außen preisgeben. In einfachen Worten ausgedrückt: Es ist das Zug Stellwerk für Weichen.
Datenbankschicht
Die Datenbankschicht ist im Grunde eine Sammlung an Modellen (Entitäten) und Datenbank Queries, die die Struktur zur Datenverarbeitung vorgeben.
Warum ist Otis eine geschichtete Architektur und warum ein Microservice?
Durch die geschichtete Architektur hat man schnellere und saubere Resultate und bleibt so nah wie möglich bei dem MVP. Man muss nicht immer Domain Driven Design (DDD) oder eigene Prinzipien erfinden um ein gutes Ergebnis zu erlangen. Dazu können wir aufgrund der Microservice Architektur plattformunabhängig bleiben, Minestom, Bedrock, Paper, Fabric / Forge, usw. stellen keine Hürde dar, da wir über ein einheitliches REST Protokoll kommunizieren.
Ein gutes Einsatzgebiet ist Minecraft, in unserem Anwendungsfall, aber muss es nicht. Solange ein Spiel eine eindeutige ID pro Spieler besitzt, könnte man das Backend auch für andere Spieleplattformen verwenden, bei denen man Verwendung sieht - Foren könnten angebunden werden, Discord Bots, usw…
Mögliche Probleme
Dadurch das wir zustandlos arbeiten, sind Anfragen als kostbar zu betrachten, da alle Anfragen mit 1 zu n an die Datenbank durchgereicht werden. An sich sind Datenbankzugriffe immer kostbar, da Tabellen kurz gesperrt werden, um sie atomar zu halten. Durch viele parallele Zugriffe aufgrund von Skalierung kann die Datenbank an Verfügbarkeit verlieren und die Antwortzeiten erhöhen sich. Normal sollten die Antwortszeiten von der Datenbank 5-15 Millisekunden unabhängig vom Netzwerk betragen, alles darüber wird auf lange Sicht zum Problem. Caching würde das Problem verringern und gegebenenfalls lösen. Zu Beachten ist dabei, dass es ein Cache ist, der zwischen Instanzen geteilt werden kann, um eine horizontale Skalierung zu gewährleisten. Eine vertikale Skalierung ist dann nur möglich, wenn die Ressourcen erhöht werden (zum Beispiel: 1 → 2 CPU Einheiten, 4GB → 8GB RAM), was wir in unserem Fall nicht haben wollen. Eine horizontale Skalierung besteht aus mehreren Instanzen, die parallel Anfragen verarbeiten, währendessen eine vertikale Skalierung nur aus einer oder weniger großen Instanz bersteht.
Abschluss
Durch dieses Projekt haben wir eine gute Basis geschaffen, andere Projekte anzugehen und tieferes Verständnis aufzubauen für Architektur und Kommunikation. Gerne kann Otis als Projekt für andere (Minecraft) Projekte verwendet werden, um Stabilität, Einheitlichkeit und Wartbarkeit zu erreichen. Sollte es Fragen geben, kann man sich gerne auf dem Discord unterhalten. Über konstruktives Feedback für diesen Blog Beitrag sind wir froh, da er diesen Blog Beitrag verbessert.
Projekt Links
- Repository: https://github.com/OneLiteFeatherNET/Otis
- Issue-Tracker: https://github.com/OneLiteFeatherNET/Otis/issues
- Discord: https://discord.onelitefeather.net
- Releases: https://github.com/OneLiteFeatherNET/Otis/releases