{"id":28550,"date":"2026-02-21T12:48:56","date_gmt":"2026-02-21T11:48:56","guid":{"rendered":"https:\/\/blog.mi.hdm-stuttgart.de\/?p=28550"},"modified":"2026-02-21T12:48:58","modified_gmt":"2026-02-21T11:48:58","slug":"entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels","status":"publish","type":"post","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/","title":{"rendered":"Entwicklung einer verteilten Cloud-Anwendung am Beispiel eines Multiplayer Spiels"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Einleitung<\/h2>\n\n\n\n<p>Den meisten sollte das Spielprinzip von \u201cCookie Clicker\u201d bekannt sein: Ein Klick auf einen Keks erh\u00f6ht den Spielstand um einen Punkt. Das Spiel ist endlos, hat keine Punktegrenze. Es geht darum, im Leaderboard nach oben zu klettern. Im Rahmen der Vorlesung \u201cSystem Engineering and Management\u201d (143101a)&nbsp; erweiterten wir das Konzept zu einem Echtzeit-Multiplayer Spiel. Das Spiel nannten wir \u201cOvercookied\u201d.<\/p>\n\n\n\n<p>In Overcookied treten zwei zuf\u00e4llig ausgew\u00e4hlte Spieler in einem 60 Sekunden Match gegeneinander an. Wer am Ende die meisten Klicks hat, gewinnt. Zus\u00e4tzlich spawnt alle 5-10 Sekunden ein \u201cGolden Cookie\u201d. Der Spieler, der zuerst auf den Golden Cookie klickt, erh\u00e4lt einen kurzen \u201eDouble Click&#8221;-Bonus, bei dem jeder Klick doppelt gewertet wird.<\/p>\n\n\n\n<p>Das Ziel des Projekts war nicht die Entwicklung eines ausgereiften Spiels, sondern das Kennenlernen und Verstehen von Echtzeit-Gameloops in verteilten Systemen sowie moderne Web- und Cloud-Technologien. Wir wollten Amazon Web Services (AWS) durch praktische Implementierung kennenzulernen, indem das Spiel als verteiltes System betrieben wird, horizontal skalierbar ist und gleichzeitig geringe Latenzen, konsistente Zust\u00e4nde sowie eine zuverl\u00e4ssige Synchronisation zwischen den Clients bietet. Zus\u00e4tzlich war das Projekt durch einen Budgetrahmen begrenzt. Die Umsetzung erfolgte innerhalb eines AWS-Free-Tier-Accounts mit einem verf\u00fcgbaren Guthaben von 200 US-Dollar. Aufgrund dieser Einschr\u00e4nkung musste die Infrastruktur jederzeit automatisiert auf- und abgebaut werden k\u00f6nnen, um Kosten zu kontrollieren und unn\u00f6tige Ausgaben zu vermeiden. Also musste unsere Infrastruktur zus\u00e4tzlich als &#8220;Infrastructure as Code\u201d aufgebaut werden, um reproduzierbare und kosteneffiziente Deployments zu erm\u00f6glichen.&nbsp;<\/p>\n\n\n\n<p>In den folgenden Kapiteln des Blogeintrags werden die einzelnen Umsetzungsschritte, Architekturentscheidungen und Herausforderungen beschrieben.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lernziele<\/h2>\n\n\n\n<p>Unser Ziel war es, praktische Erfahrungen mit Cloud-nativer Softwareentwicklung zu sammeln und die wichtigsten Konzepte verteilter Systeme kennenzulernen und in einem realen Projekt anzuwenden. Den Fokus legten wir dabei auf den Cloud-Anbieter Amazon Web Services, wo wir unsere Anwendung in einem Kubernetes Cluster deployen wollten, sowie die Verwendung von Infrastructure as Code. Au\u00dferdem wollten wir lernen, wie ein Echtzeit- Multiplayer Gameloop funktioniert und wie er sich in einem verteilten System realisieren l\u00e4sst.<br><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Anforderungen<\/h2>\n\n\n\n<p>Aus unseren Lernzielen ergaben sich folgende funktionale und nichtfunktionale Anforderungen an das Projekt Overcookied:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Funktionale Anforderungen<\/strong><\/td><td><strong>Nicht-funktionale Anforderungen<\/strong><\/td><\/tr><tr><td>&#8211; Login \u00fcber Google OAuth 2.0Zuf\u00e4lliges Matchmaking zwischen 2 Spielern<br>&#8211; Echtzeit-Multiplayer-Gameloop (Begrenzung der Spielzeit auf 1min pro Match)<br>&#8211; Live Anzeige der Klicks und Scores (Eigene und des Gegners)<br>&#8211; Goldener Cookie mit 5s begrenztem Doppelte-Punkte-Bonus<br>&#8211; Leaderboard und Spielhistorie<\/td><td>&#8211; Geringe Latenz<br>&#8211; Konsistente Spielst\u00e4nde\u00a0<br>&#8211; Hohe Verf\u00fcgbarkeit (Horizontale Skalierbarkeit)<br>&#8211; Kostenkontrolle (Reproduzierbarer Infrastruktur Auf- und Abbau)<br>&#8211; Trennung von Frontend, Backend und Infrastruktur<br>&#8211; Ansprechendes UI<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\"><em>Tab 1. Funktionale und nichtfunktionale Anforderungen an &#8220;Overcookied&#8221;<\/em><\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Echtzeit Gameloop<\/h2>\n\n\n\n<p>Die Anwendung hat drei Schichten: ein Next.js-Frontend, ein Go-Backend mit WebSocket-Support und eine Datenhaltungsschicht. Im Frontend nutzten wir TypeScript sowie Next.js, wo wir bereits Erfahrung hatten. Mithilfe von Tailwind konnte so schnell das UI f\u00fcr das Spiel erstellt werden. Das Frontend besteht aus diesen Pages:<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"848\" data-attachment-id=\"28554\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/frontend-menu-page-2\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1.png\" data-orig-size=\"3172,2628\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"frontend menu-page\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1-1024x848.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1-1024x848.png\" alt=\"\" class=\"wp-image-28554\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1-1024x848.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1-300x249.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1-768x636.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1-1536x1273.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-menu-page-1-2048x1697.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n\n\n\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"933\" data-attachment-id=\"28555\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/frontend-game-page-2\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1.png\" data-orig-size=\"1482,1350\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"frontend game-page\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1-1024x933.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1-1024x933.png\" alt=\"\" class=\"wp-image-28555\" style=\"width:441px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1-1024x933.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1-300x273.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1-768x700.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/frontend-game-page-1.png 1482w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n<\/div>\n<\/div>\n\n\n\n<p><em>Fig. 1 &#8211; Main-Menu-Page (links): Game History und Leaderboard | Fig. 2 &#8211; Game-Page (rechts): Cookie + Golden Cookie, sowie ein HUD \u00fcber den aktuellen Spielstand<\/em><\/p>\n\n\n\n<p>F\u00fcr das Backend w\u00e4hlten wir die Programmiersprache Go, die f\u00fcr uns neu war. Durch AI-assisted Coding und den vielf\u00e4ltigen guten Dokumentationen aufgrund der Popularit\u00e4t von Go, wurde der Einstieg in neue Programmiersprache deutlich erleichtert. Go eignet sich aufgrund seiner Performance und seiner einfachen Concurrency mittels Goroutines und Channels besonders gut f\u00fcr serverseitige Echtzeit-Anwendungen und wurde daher f\u00fcr den Multiplayer-Gameloop eingesetzt.\u00a0<\/p>\n\n\n\n<p><br><\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"1007\" data-attachment-id=\"28556\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/flow\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow.png\" data-orig-size=\"2984,2935\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Flow\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow-1024x1007.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow-1024x1007.png\" alt=\"\" class=\"wp-image-28556\" style=\"width:582px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow-1024x1007.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow-300x295.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow-768x755.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow-1536x1511.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Flow-2048x2014.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\"><em>Fig. 3 &#8211; Schematische Abbildung des Spielablaufs<\/em><\/figcaption><\/figure>\n\n\n\n<p>Wenn ein Spieler die Game-Seite betritt, \u00f6ffnet das Frontend eine WebSocket-Verbindung. Im Frontend ist die echtzeit Spiellogik in einem Custom Hook gekapselt. Er verwaltet die WebSocket-Verbindung, den Spielstatus (IDLE \u2192 MATCHMAKING \u2192 PLAYING \u2192 FINISHED), Scores, Timer und Power-Ups. Das Frontend ruft nur exponierte Methoden auf (<code class=\"\" data-line=\"\">sendClick<\/code>, <code class=\"\" data-line=\"\">claimGoldenCookie<\/code>, <code class=\"\" data-line=\"\">quitGame<\/code>) und reagiert auf State-\u00c4nderungen.\u00a0<\/p>\n\n\n\n<p>Das Backend registriert die Websocket Verbindung und erstellt einen Client mit den Benutzerdaten. F\u00fcr jeden Client werden zwei parallele Goroutinen gestartet, sogenannte \u201cPumps\u201d. Der Name kommt aus der Analogie zu einer Wasserpumpe: Eine Pump-Funktion l\u00e4uft in einer Endlosschleife und &#8220;pumpt&#8221; Daten kontinuierlich von einer Quelle zu einem Ziel. Sie blockiert, wartet auf den n\u00e4chsten Datensatz, leitet ihn weiter, und wartet erneut.<\/p>\n\n\n\n<p>Es werden zwei Goroutines ben\u00f6tigt, da Websocket Verbindungen gleichzeitig lesen und schreiben. Ein Spieler kann jederzeit klicken (Read), w\u00e4hrend der Server ihm gleichzeitig Timer-Updates schickt (Write). Ein einzelner Loop m\u00fcsste zwischen Lesen und Schreiben wechseln und blockiert sich so selbst: \u200b\u200bWenn gerade kein Click ansteht, wartet die \u201cRead-Routine\u201d auf unbestimmte Zeit und in dieser Zeit k\u00f6nnen keine Server-Events gesendet werden. Umgekehrt blockiert ein langsamer Client das Schreiben und staut eingehende Nachrichten auf. Die Trennung in zwei Goroutines l\u00f6st das Problem:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>\u201creadPump\u201d<\/strong>: blockiert auf dem Read-Channel und leitet empfangene Nachrichten sofort an den GameManager weiter. Bei Fehlern (Disconnect, Timeout) l\u00f6st sie die Deregistrierung aus.<\/li>\n\n\n\n<li><strong>\u201cwritePump\u201d<\/strong>: blockiert auf dem Send-Channel und schreibt Nachrichten auf die Verbindung. Parallel sendet sie periodisch Ping-Frames. Fehlt die Pong-Antwort innerhalb von 60 Sekunden, gilt der Client als tot und wird getrennt.<\/li>\n<\/ul>\n\n\n\n<p>Zwischen beiden Goroutinen sitzt ein gepufferter Channel. Er entkoppelt die Geschwindigkeit von Sender und Empf\u00e4nger. L\u00e4uft der Channel voll, weil ein Client nicht mehr empf\u00e4ngt, wird die Verbindung geschlossen. Das verhindert Memory-Leaks durch blockierte Clients.<\/p>\n\n\n\n<p>Alle Client-Verbindungen laufen beim GameManager zusammen. Der GameManager ist die zentrale Komponente im Backend: eine permanente Goroutine, die \u00fcber drei Channels (<code class=\"\" data-line=\"\">register<\/code>, <code class=\"\" data-line=\"\">unregister<\/code>, <code class=\"\" data-line=\"\">broadcast<\/code>) alle Client-Verbindungen, die Matchmaking-Queue und die Zuordnung von Clients zu aktiven Spielen verwaltet.<\/p>\n\n\n\n<p>Sobald die WebSocket-Verbindung steht, sendet der Hook automatisch eine <code class=\"\" data-line=\"\">JOIN_QUEUE<\/code>-Nachricht. Der GameManager speichert den Spieler in einer Warteliste. Betritt ein zweiter Spieler die Queue, erkennt der GameManager: <code class=\"\" data-line=\"\">waiting<\/code> ist nicht leer.\u00a0 Damit ist ein Match gefunden. Dann wird ein GameRoom erstellt, der den gesamten Spielstand beider Spieler synchron kapselt: Scores, Timer, Power-Ups. Eine Timer-Goroutine wird gestartet, und beide Clients erhalten eine <code class=\"\" data-line=\"\">GAME_START<\/code>-Nachricht mit der Gegner-Info und ihrer Rolle (Spieler 1 oder 2).<\/p>\n\n\n\n<p>Jetzt startet das Spiel. Wenn ein Spieler auf seinen Cookie klickt, wird ein blaues &#8220;+1&#8221;-Partikel angezeigt, bevor die Server Antwort eintritt. Das Frontend nimmt an, dass der Click erfolgreich ist (Optimistic UI) und korrigiert erst bei der n\u00e4chsten Server-Antwort. So f\u00fchlt sich das Spiel trotz Netzwerklatenz wie Echtzeit an.<\/p>\n\n\n\n<p>Im Backend empf\u00e4ngt die readPump die Nachricht und leitet sie an den GameManager weiter. Dieser delegiert an den GameRoom, der den Score erh\u00f6ht. Zwar werden die Spieler-Klicks unabh\u00e4ngig voneiner getrackt, jedoch kann es zu einer Race-Condition kommen, wenn beide Spieler gleichzeitig auf den \u201cGolden Cookie\u201d klicken. Bei beiden readPumps von Spieler 1 und 2 kommt die Nachricht an, dass sie auf den goldenen Cookie geklickt haben. Beide w\u00fcrden das Power-Up vergeben, obwohl nur einer es erhalten darf. Das wird durch einen <code class=\"\" data-line=\"\">sync.Mutex<\/code> im GameRoom verhindert. Jede Funktion, die den State liest oder schreibt (Click-Handler, Golden-Cookie-Claim, Timer-Broadcast) sperrt zuerst den Mutex und gibt ihn erst nach Abschluss wieder frei. Nach der Score-Erh\u00f6hung sendet das Backend eine <code class=\"\" data-line=\"\">OPPONENT_CLICK<\/code>-Nachricht an den Gegner (rotes &#8220;+1&#8221;-Partikel auf dessen Seite) und eine <code class=\"\" data-line=\"\">UPDATE<\/code>-Nachricht an beide Spieler mit den aktuellen Scores.<\/p>\n\n\n\n<p>Wenn der Timer null erreicht, stoppt der Gameloop. Der GameRoom bestimmt den Gewinner anhand der Scores oder stellt ein Unentschieden fest. Beide Spieler erhalten eine\u00a0 <code class=\"\" data-line=\"\">GAME_OVER<\/code>-Nachricht mit der Gewinner-ID und werden wieder zum Main Menu geroutet, wo sie erneut ein Spiel starten k\u00f6nnen. W\u00e4hrenddessen persistiert das Backend die Ergebnisse: F\u00fcr jeden Spieler wird ein Game-Record in der Datenbank gespeichert (Score, Gegner, Gewinner) und die Gesamtstatistik f\u00fcr das Leaderboard aktualisiert. Der GameRoom wird aufger\u00e4umt und die Client-Room-Zuordnungen entfernt.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"872\" data-attachment-id=\"28560\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/screenshot-2026-02-18-at-19-30-14-3\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2.png\" data-orig-size=\"3000,2554\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Screenshot 2026-02-18 at 19.30.14\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2-1024x872.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2-1024x872.png\" alt=\"\" class=\"wp-image-28560\" style=\"width:821px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2-1024x872.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2-300x255.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2-768x654.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2-1536x1308.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-18-at-19.30.14-2-2048x1744.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\"><em>Fig. 4 &#8211; Visualisierung des Gameloops (ohne AWS)<\/em><\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Infrastructure as Code<\/h2>\n\n\n\n<p>Um das Spiel als ein verteiltes System zu deployen, entschieden wir uns f\u00fcr den AWS Service Elastic Kubernetes Service (EKS). So k\u00f6nnen Frontend und Backend containerisiert, skalierbar und hochverf\u00fcgbar betrieben werden. Persistente Daten wie Spielst\u00e4nde und Leaderboards werden in DynamoDB gespeichert, w\u00e4hrend Redis f\u00fcr Session-Management und die Synchronisation von Echtzeit-Zust\u00e4nden eingesetzt wird. Die Docker-Images der Anwendung werden in der Amazon Elastic Container Registry (ACR) verwaltet.\u00a0Ein wesentlicher Faktor war zudem der effiziente Umgang mit Cloud-Kosten. Da das Projekt innerhalb eines AWS-Free-Tier-Accounts umgesetzt wurde, musste die gesamte Infrastruktur automatisiert auf- und abbaubar sein, um die laufenden Kosten zu minimieren. Deshalb entschieden wir uns, f\u00fcr den Aufbau und Betrieb dieser Cloud-Infrastruktur das Infrastructure-as-Code-Tool Terraform einzusetzen. Ziel war es, s\u00e4mtliche AWS-Ressourcen deklarativ zu definieren und reproduzierbar bereitzustellen.<\/p>\n\n\n\n<p>Warum Terraform?<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Etablierter Standard in Industrie und Lehre<\/li>\n\n\n\n<li>Klare, deklarative Struktur ohne zus\u00e4tzliche Programmlogik<\/li>\n\n\n\n<li>Besonders geeignet f\u00fcr teamf\u00e4hige, skalierbare Cloud-Setups<\/li>\n<\/ul>\n\n\n\n<p>Alternative: Pulumi (Infrastruktur as Code in klassischen Programmiersprachen aber h\u00f6here Komplexit\u00e4t).<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Layered Terraform-Architektur<\/h3>\n\n\n\n<p>Die Infrastruktur wurde in zwei voneinander getrennte Terraform-Layer aufgeteilt:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Base Layer:<\/strong> Definiert langlebige Grundressourcen wie VPC, Subnets und ECR-Repositories. Dieser Layer stellt das stabile Fundament der Infrastruktur dar und bleibt auch bei einem vollst\u00e4ndigen Neuaufbau des Kubernetes-Clusters erhalten. Die Kosten hier sind nur minimal.<\/li>\n\n\n\n<li><strong>EKS Layer: <\/strong>Definiert den kurzlebigen Teil der Infrastruktur, darunter EKS-Cluster, Managed Node Groups, ElastiCache sowie alle zugeh\u00f6rigen IAM-Rollen. Dieser Layer wurde w\u00e4hrend der Entwicklung mehrfach zerst\u00f6rt und neu erstellt.<\/li>\n<\/ul>\n\n\n\n<p>Durch diese Trennung konnte der kostenintensive Teil der Infrastruktur gezielt kontrolliert werden, ohne grundlegende Ressourcen oder Container-Images zu verlieren.<br><\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"586\" data-attachment-id=\"28562\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/screenshot-2026-02-21-at-11-22-03\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03.png\" data-orig-size=\"1654,946\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Screenshot 2026-02-21 at 11.22.03\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03-1024x586.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03-1024x586.png\" alt=\"\" class=\"wp-image-28562\" style=\"width:407px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03-1024x586.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03-300x172.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03-768x439.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03-1536x879.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Screenshot-2026-02-21-at-11.22.03.png 1654w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\"><em>Fig.5 &#8211; Kostenaufteilung der AWS Services<\/em><\/figcaption><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Terraform State<\/h3>\n\n\n\n<p>Der Terraform-State wird in einem S3-Bucket gespeichert und durch DynamoDB-Locking abgesichert, um parallele \u00c4nderungen an der Infrastruktur zu verhindern. In der Praxis kam es jedoch vor, dass State Locks nach abgebrochenen <em><code class=\"\" data-line=\"\">terraform apply<\/code><\/em>-Runs h\u00e4ngen blieben und manuell gel\u00f6st werden mussten. Zus\u00e4tzlich traten Probleme mit verwaisten IAM-Rollen auf, die bei fehlgeschlagenen Deployments bereits in AWS existierten, aber nicht im Terraform-State erfasst waren. Diese Ressourcen mussten entweder importiert oder manuell bereinigt werden, um weitere Deployments durchf\u00fchren zu k\u00f6nnen.<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-large is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"991\" data-attachment-id=\"28563\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/unbenanntes-diagramm-drawio\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio.png\" data-orig-size=\"1882,1822\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Unbenanntes Diagramm.drawio\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio-1024x991.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio-1024x991.png\" alt=\"\" class=\"wp-image-28563\" style=\"width:555px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio-1024x991.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio-300x290.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio-768x744.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio-1536x1487.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Unbenanntes-Diagramm.drawio.png 1882w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><figcaption class=\"wp-element-caption\"><em>Fig. 6 &#8211; \u00dcberblick \u00fcber AWS Infrastruktur<\/em><\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Herausforderungen<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Herausforderungen mit der Queue (Distributed Matchmaking)<\/h3>\n\n\n\n<p>Solange die Anwendung nur in einer Instanz lief, funktionierte das Spiel. Der gesamte Flow \u00fcber Login, Matchmaking und der Gameloop mit Cookie-Klicks und Timer war in sich geschlossen, weil alle Spieler auf demselben Prozess landeten. Doch Kubernetes skaliert horizontal. Sobald wir das Backend auf zwei Replicas hochfuhren, wurden Spieler nicht mehr gemachted. Hier kam die Idee auf, dass es an der In-Memory Queue liegen k\u00f6nnte.&nbsp;<\/p>\n\n\n\n<p>Im Single-Pod funktionierte das Matchmaking so, dass ein Spieler eine <code class=\"\" data-line=\"\">JOIN_QUEUE<\/code>-Nachricht sendete, die vom Backend in einer Variable gespeichert wurde. Betrat ein zweiter Spieler die Queue, war das Match gefunden. Das Problem trat auf, sobald das Backend in Kubernetes in zwei Pods gestartet wurde. Der Load Balancer verteilt eingehende WebSocket-Verbindungen per Round-Robin. Spieler A landete auf Pod A, Spieler B auf Pod B. Beide schickten eine <code class=\"\" data-line=\"\">JOIN_QUEUE<\/code>-Nachricht, aber jeder Pod hatte seine eigene Queue. Pod A wartete auf einen zweiten Spieler, Pod B ebenfalls. Nach Durchsuchen der Logs, best\u00e4tigte sich der Verdacht: &#8220;Host pod missing players: hasP1=true, hasP2=false&#8221;. Die Hypothese war damit best\u00e4tigt: Das Matchmaking braucht einen gemeinsamen, Pod-\u00fcbergreifenden State-Store.<\/p>\n\n\n\n<p>Um das Umzusetzen, evaluierten wir mehrere Ideen:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Sticky Sessions am Load Balancer:<\/strong> Das w\u00e4re die einfachste Variante, indem alle Spieler auf denselben Pod geroutet werden. Das h\u00e4tte das Problem umgangen, aber nicht gel\u00f6st. Bei Skalierung auf mehrere Pods w\u00e4re das Matchmaking trotzdem nicht erfolgreich gewesen. Zudem sind in Kubernetes Pods tempor\u00e4r, da sie jederzeit \u201csterben\u201d und neu starten k\u00f6nnen und damit der State innerhalb des Containers verloren gehen k\u00f6nnte.<\/li>\n\n\n\n<li><strong>Matchmaking \u00fcber die Datenbank (DynamoDB):<\/strong> DynamoDB war bereits im Projekt f\u00fcr Spielerprofile und Leaderboards integriert. Man h\u00e4tte eine Queue-Tabelle anlegen und per Polling abfragen k\u00f6nnen. DynamoDB ist f\u00fcr Lese-\/Schreiblatenzen im einstelligen Millisekundenbereich optimiert, was schnell genug w\u00e4re. Jedoch fehlen bei DynamoDB die passenden Primitive f\u00fcr eine Matchmaking-Queue. DynamoDB bietet keinen Pub\/Sub, keinen nativen Lock und keine Sorted-Set-Semantik.<\/li>\n\n\n\n<li><strong>Selbst gehostetes Redis auf einem Kubernetes Pod: <\/strong>Redis ist eine In-Memory-Datenbank, d.h. alle Daten werden im RAM gespeichert, nicht auf der Festplatte. Dadurch ist Redis extrem schnell mit Latenz im Sub-Millisekunden Bereich. Au\u00dferdem bietet Redis die Datenstrukturen sorted Sets, Pub\/Sub und atomische Operationen, die f\u00fcr die Matchmaking-Queue ben\u00f6tigt werden. Man k\u00f6nnte einen eigenen Redis-Container im Cluster deployen. Damit h\u00e4tte man die volle Kontrolle, aber es m\u00fcsste die Persistenz konfiguriert, Monitoring einrichtet, Failover manuell gebaut und Updates selbst eingespielt werden. F\u00fcr ein Uni-Projekt mit begrenztem Betriebsbudget war das ein zu gro\u00dfer Overhead.<\/li>\n\n\n\n<li>A<strong>WS ElastiCache als Managed Service:<\/strong> ElastiCache \u00fcbernimmt die gesamte Infrastruktur: Provisioning, Patching, Monitoring, Backups. Seit 2024 unterst\u00fctzt ElastiCache neben Redis auch Valkey, den Open-Source-Fork von Redis, der nach der Lizenz\u00e4nderung von Redis Labs entstand. Valkey ist zu 100% API-kompatibel mit Redis, jeder Redis-Befehl funktioniert identisch. Und AWS r\u00fcckt Valkey als Standard-Engine in ElastiCache in den Vordergrund und bietet es g\u00fcnstiger an als Redis.<\/li>\n<\/ol>\n\n\n\n<p>Die Entscheidung fiel auf Option 4. Der Managed Service eliminiert operativen Aufwand, Valkey liefert alle Redis-Primitiven, und die Terraform-Integration erlaubt Infrastructure as Code.<\/p>\n\n\n\n<p>F\u00fcr die Umsetzung der Queue standen zwei Redis-Datenstrukturen zu Auswahl:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Feature<\/strong><\/td><td><strong>List (LPUSH\/RPOP)<\/strong><\/td><td>S<strong>orted Set (ZADD)<\/strong><\/td><\/tr><tr><td><strong>FIFO-Reihenfolge\u00a0<\/strong><\/td><td>Nativ<\/td><td>Via Score = Timestamp&nbsp;<\/td><\/tr><tr><td><strong>Duplikat-Schutz<\/strong><\/td><td>Nein<\/td><td>Ja<\/td><\/tr><tr><td><strong>Flexibles Entfernen von Eintr\u00e4gen<\/strong><\/td><td>Nur von den Enden<\/td><td>ZREM auf jedes Element&nbsp;<\/td><\/tr><tr><td><strong>Suchen nach User<\/strong><\/td><td>O(n)<\/td><td>via ZSCORE\/ZRANGE<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\"><em>Tab 2. &#8211; Vergleich List &amp; Sorted Set<\/em><\/figcaption><\/figure>\n\n\n\n<p>Die entscheidenden Punkte waren das flexible Entfernen und der Duplikat-Schutz. Wenn ein Spieler disconnected, soll er aus der Queue entfernt werden, egal an welcher Position. Eine Liste erlaubt nur <code class=\"\" data-line=\"\">LPOP<\/code> (links) oder <code class=\"\" data-line=\"\">RPOP<\/code> (rechts), ein Element aus der Mitte zu entfernen erfordert <code class=\"\" data-line=\"\">LREM<\/code> mit O(n)-Kosten und ohne Garantie auf Eindeutigkeit. Ein Sorted Set entfernt mit: <code class=\"\" data-line=\"\">ZREM<\/code> jedes Element in O(log n), und doppeltes Einf\u00fcgen desselben Spielers \u00fcberschreibt den Score und erzeugt keinen doppelten Eintrag. Jeder Spieler wird mit einem Unix-Timestamp als Score in die Queue eingef\u00fcgt. Dadurch entsteht eine FIFO-Reihenfolge. Der Matchmaking-Loop pr\u00fcft alle 500ms, ob gen\u00fcgend Spieler in der Queue stehen. <code class=\"\" data-line=\"\">ZRANGE 0 1<\/code> liest die zwei \u00e4ltesten Eintr\u00e4ge, <code class=\"\" data-line=\"\">ZREM<\/code> entfernt sie. Dann kann das Match erstellt werden.<\/p>\n\n\n\n<p>Mit der gemeinsamen Queue war das Problem jedoch nicht behoben. Wenn Pod A zwei Spieler matcht und den Game Room erstellt, muss Pod B, auf dem einer der Spieler per WebSocket verbunden ist, davon erfahren. Daf\u00fcr kamen zwei Ans\u00e4tze in Frage:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Polling:<\/strong> Jeder Pod fragt regelm\u00e4\u00dfig Redis, ob ein neues Match f\u00fcr seine lokalen Spieler existiert. Simpel, aber verschwenderisch, da 99% der Abfragen nichts liefern und die Latenz immer im halben Polling-Intervall liegt.<\/li>\n\n\n\n<li><strong>Pub\/Sub:<\/strong> Das ist Redis&#8217; eingebautes Publish\/Subscribe-System. Ein Pod published nach dem Fire-and-Forget Prinzip eine Nachricht auf einem Channel, alle subscribten Pods empfangen sie. Der Nachteil ist hier jedoch, dass verpasste Nachrichten (wenn ein Pod kurz offline ist) verloren gehen. Deshalb wird der Game State zus\u00e4tzlich als Key in Redis gespeichert.<\/li>\n<\/ol>\n\n\n\n<p>Wir verwenden zwei Channels:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Channel<\/strong><\/td><td><strong>Zweck<\/strong><\/td><\/tr><tr><td>`overcookied:match:notify`&nbsp;<\/td><td>Match gefunden \u2192 alle Pods pr\u00fcfen, ob ein lokaler Spieler betroffen ist<\/td><\/tr><tr><td>`overcookied:game:events`<\/td><td>Laufende Game-Events: Clicks, Timer-Ticks, Golden Cookie Spawns, Game Over<\/td><\/tr><\/tbody><\/table><figcaption class=\"wp-element-caption\"><em>Tab. 3 &#8211; Implementierte Sub\/Pub Channels<\/em><\/figcaption><\/figure>\n\n\n\n<p>Beim Start subscribed jeder Pod auf beide Channels. Wenn ein Match gefunden wird, published der matchende Pod eine <code class=\"\" data-line=\"\">MatchNotification<\/code>. Alle Pods empfangen die Notification und \u00fcberpr\u00fcfen, ob einer der Spieler lokal verbunden ist. Falls ja, sendet er eine <code class=\"\" data-line=\"\">GAME_START<\/code>-Nachricht\u00a0 an dessen WebSocket. Im Gegensatz zum Single-Pod-Modell muss so kein Pod mehr beide Spieler kennen. Jeder Pod k\u00fcmmert sich nur um seine lokalen WebSocket-Clients und reagiert auf Redis-Events.<\/p>\n\n\n\n<p>Da alle Pods gleichzeitig alle 500ms den Matchmaking-Loop durchf\u00fchren, kann es zu einer Race-Condition f\u00fchren: Zwei Pods lesen gleichzeitig dieselben Spieler und erstellen zwei unabh\u00e4ngige R\u00e4ume f\u00fcr dieselben beiden Spieler. Um das zu verhindern, wurde ein Distributed Lock eingesetzt, was mit Redis\u2019 <code class=\"\" data-line=\"\">SetNX<\/code> mit einer TTL von 2s umgesetzt wurde. <code class=\"\" data-line=\"\">SetNX<\/code> schreibt den Key nur, wenn er noch nicht existiert. Der erste Pod, der den Lock erlangt, darf matchen. Alle anderen \u00fcberspringen und versuchen es beim n\u00e4chsten Tick. Die TTL von 2s ist ein Sicherheitsmechanismus. Falls der \u201clockende\u201d Pod abst\u00fcrzt, bevor er den Lock freigibt, l\u00e4uft der Key automatisch ab. Damit werden Deadlocks verhindert.<\/p>\n\n\n\n<p>Der Game State (Scores, Timer, Golden Cookies, Power-Ups) liegt jetzt nicht mehr im Arbeitsspeicher eines einzelnen Pods, sondern als JSON-String in Redis, mit einer TTL von 10 Minuten. Jede Game State \u00c4nderung liest den State aus Redis, modifiziert ihn und schreibt ihn atomar zur\u00fcck. Die TTL stellt sicher, dass Game States nach Spielende automatisch aufger\u00e4umt werden, auch wenn der Pod abst\u00fcrzt. F\u00fcr die atomaren Score-Updates wird Redis&#8217; Optimistic-Locking-Mechanismus <code class=\"\" data-line=\"\">WATCH\/MULTI\/EXEC<\/code> verwendet: <code class=\"\" data-line=\"\">WATCH<\/code> beobachtet den Key. <code class=\"\" data-line=\"\">MULTI<\/code> er\u00f6ffnet eine Transaktion und <code class=\"\" data-line=\"\">EXEC<\/code> f\u00fchrt alle in <code class=\"\" data-line=\"\">MULTI<\/code> gesammelten Befehle atomar aus, aber nur wenn der beobachtete Key seit <code class=\"\" data-line=\"\">WATCH<\/code> unver\u00e4ndert geblieben ist. \u00c4ndert ein anderer Pod den State zwischen <code class=\"\" data-line=\"\">WATCH<\/code> und <code class=\"\" data-line=\"\">EXEC<\/code>, schl\u00e4gt die Transaktion fehl. Der Go-Redis-Client wiederholt sie anschlie\u00dfend automatisch. So gehen keine Klicks verloren, selbst wenn zwei Spieler auf verschiedenen Pods im selben Moment klicken.<\/p>\n\n\n\n<p>Dasselbe Prinzip wird beim Golden-Cookie-Claim angewendet: Beide Spieler klicken gleichzeitig, aber innerhalb der Transaktion wird \u00fcberpr\u00fcft, ob der Cookie noch aktiv ist. Nur der erste Claim geht durch, der zweite wird verworfen.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Herausforderungen bei Deployment &amp; Authentication<\/h3>\n\n\n\n<h4 class=\"wp-block-heading\">Hilfsskripte f\u00fcr Deployment<\/h4>\n\n\n\n<p>Zur Vereinfachung des Infrastruktur- und Deployment-Prozesses wurden mehrere Hilfsskripte erstellt. Diese kapseln wiederkehrende Aufgaben, stellen die korrekte Reihenfolge sicher:<\/p>\n\n\n\n<p><strong>Build-&amp;-Push-Skript: <\/strong>Baut die Docker-Images f\u00fcr Frontend und Backend und pushed diese automatisiert in die Amazon Elastic Container Registry. Dadurch stehen versionierte Images f\u00fcr das Kubernetes-Deployment zur Verf\u00fcgung.<\/p>\n\n\n\n<p><strong>Application-Deployment-Skript: <\/strong>&nbsp;Deployed die Anwendung vollst\u00e4ndig in das Kubernetes-Cluster. Das Skript erstellt Namespace, Secrets, Deployments, Services und Ingress-Ressourcen und wartet auf abh\u00e4ngige Komponenten wie den Application Load Balancer.<\/p>\n\n\n\n<p><strong>Destroy-Skript: <\/strong>F\u00e4hrt den EKS-Layer kontrolliert herunter. Kubernetes-Ressourcen werden zuerst entfernt, bevor Terraform den Cluster zerst\u00f6rt, sodass Cloud-Ressourcen sauber aufgel\u00f6st und Kosten vermieden werden.<\/p>\n\n\n\n<p>Weitere Skripte unterst\u00fctzen den Bootstrap der Terraform-State-Infrastruktur sowie die Aktualisierung von DNS- und OAuth-Konfigurationen nach einem Cluster-Neuaufbau.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Externe Authentifizierung dynamischer Infrastruktur<\/h4>\n\n\n\n<p>F\u00fcr die Authentifizierung wurde Google OAuth 2.0 eingesetzt, um eine sichere Benutzeranmeldung zu erm\u00f6glichen und gleichzeitig auf die Implementierung eines eigenen Login-Systems verzichten zu k\u00f6nnen. Die App wurde in der Google Cloud Console registriert, feste Redirect-Routen definiert und das Backend so aufgebaut, dass nach erfolgreicher Authentifizierung ein JWT ausgestellt wird, welches im Frontend gespeichert und f\u00fcr weitere API-Zugriffe verwendet wird. Im lokalen Entwicklungsumfeld lief dieser Ablauf problemlos. Frontend und Backend wurden jeweils als einzelne Instanz betrieben, Redirect-URLs waren statisch und der gesamte OAuth-Flow wurde innerhalb eines Prozesses abgewickelt.<\/p>\n\n\n\n<p><strong>Problem: <\/strong>Mit dem \u00dcbergang in den produktionsnahen Betrieb \u00e4nderte sich diese Ausgangslage. Die Infrastruktur wurde vollst\u00e4ndig \u00fcber Terraform verwaltet und der EKS-Cluster regelm\u00e4\u00dfig neu aufgebaut. Dabei wurde bei jedem Cluster-Neustart automatisch ein neuer Application Load Balancer (ALB) erstellt, dessen \u00f6ffentliche URL sich \u00e4nderte. Zus\u00e4tzlich lief das Backend nun mit mehreren Pods, sodass Requests nicht mehr garantiert von derselben Instanz verarbeitet wurden. Zus\u00e4tzlich waren JWTs nicht pod \u00fcbergreifend validierbar, da jeder Pod ein eigenes Secret verwendete.<\/p>\n\n\n\n<p><strong>L\u00f6sung:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Einf\u00fchrung einer <strong>eigenen Domain <\/strong>(overcookied.de), die unabh\u00e4ngig von der dynamischen ALB-Adresse bleibt und als stabiler Endpunkt f\u00fcr OAuth-Redirects dient.<\/li>\n\n\n\n<li>Verwaltung der <strong>Domain \u00fcber Route 53<\/strong> und HTTPS-Terminierung \u00fcber ACM, sodass die \u00f6ffentliche URL dauerhaft konstant bleibt.<\/li>\n\n\n\n<li>Umstellung des OAuth-State-Handlings auf <strong>HTTP-Cookies<\/strong>, um den Authentifizierungsprozess stateless und pod unabh\u00e4ngig zu gestalten.<\/li>\n\n\n\n<li><strong>JWT-Secrets<\/strong> als Kubernetes Secret, dass alle Backend-Pods Tokens konsistent signieren und validieren k\u00f6nnen.<\/li>\n<\/ol>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1011\" height=\"1015\" data-attachment-id=\"28565\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/21\/entwicklung-einer-verteilten-cloud-anwendung-am-beispiel-eines-multiplayer-spiels\/auth-flow-diagram-2\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1.png\" data-orig-size=\"1011,1015\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Auth Flow Diagram\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1.png\" alt=\"\" class=\"wp-image-28565\" style=\"width:556px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1.png 1011w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1-300x300.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1-150x150.png 150w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/Auth-Flow-Diagram-1-768x771.png 768w\" sizes=\"auto, (max-width: 1011px) 100vw, 1011px\" \/><\/a><figcaption class=\"wp-element-caption\"><em>Fig. 7 &#8211; Authentication Flow<\/em><\/figcaption><\/figure>\n\n\n\n<p>Auf diese Weise lie\u00df sich Google OAuth zuverl\u00e4ssig in eine dynamische, verteilte Cloud-Infrastruktur integrieren und das urspr\u00fcngliche Ziel eines automatisierten, reproduzierbaren Betriebs ohne manuelle Nacharbeit bei jedem Cluster-Neustart beibehalten.<\/p>\n\n\n\n<p>R\u00fcckblickend w\u00e4re eine selbst implementierte Benutzeranmeldung mit verschl\u00fcsselter Speicherung der Nutzerdaten vermutlich schneller umzusetzen gewesen. Ebenso h\u00e4tte man den Application Load Balancer als langlebige Komponente behandeln und bei Cluster-Neustarts beibehalten k\u00f6nnen, um stabile \u00f6ffentliche URL zu haben. Die Entscheidung f\u00fcr die Custom Domain erm\u00f6glichte jedoch wertvolle praktische Erfahrungen mit weiteren AWS-Services.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons Learned<\/h2>\n\n\n\n<p><strong>Next.js <code class=\"\" data-line=\"\">NEXT_PUBLIC_*<\/code> .env Variablen sind Build-Zeit-Konstanten:<\/strong> Die Variablen werden beim Build fest in den JavaScript-Bundle kompiliert und k\u00f6nnen nicht mehr durch Umgebungsvariablen, bspw. w\u00e4hrendes des Kubernetes-Deployments, gesetzt werden. Unser Frontend sprach in &#8220;Production&#8221; immer noch <code class=\"\" data-line=\"\">localhost:8080 <\/code>an, weil erst im Kubernetes-Deployment eine Umgebungsvariable\u00a0<code class=\"\" data-line=\"\">NEXT_PUBLIC_API_URL=https:\/\/api.overcookied.de<\/code>\u00a0gesetzt wurde. Alle &#8220;Production&#8221; Variablen m\u00fcssen vor dem Build gesetzt werden.<\/p>\n\n\n\n<p><strong>Go-Maps und Concurrency: Fatal Crash ohne Warnung<\/strong>: Go-Maps sind nicht thread-safe. Concurrent Read + Write crasht den Prozess sofort und unwiderruflich, ohne Recover-M\u00f6glichkeit. Jede Map, die von mehreren Goroutines ber\u00fchrt wird, braucht einen <code class=\"\" data-line=\"\">sync.Mutex<\/code>.<\/p>\n\n\n\n<p><strong>Nur weil es lokal l\u00e4uft, hei\u00dft das noch lange nicht, dass es in einem Kubernetes Cluster l\u00e4uft: <\/strong>Mit einem einzigen Pod funktioniert alles wie bei einem lokalen Build. Sobald wir auf 2+ Replicas skalierten, funktionierte das Matchmaking nicht mehr.  Wir h\u00e4tten Von Anfang an &#8220;distributed-first&#8221; denken und uns h\u00e4ufiger fragen sollen: &#8220;Was passiert, wenn ein zweiter Pod l\u00e4uft?&#8221;. Au\u00dferdem h\u00e4tten wir fr\u00fchzeitig mit Minikube und mehreren Replicas testen m\u00fcssen, was uns sehr viel Arbeit gespart h\u00e4tte. So mussten wir einen gro\u00dfteil unserer Backend Architektur \u00e4ndern und von grundauf neu programmieren.<\/p>\n\n\n\n<p><strong>Terraform ist Komplex, aber der Aufwand rentiert sich<\/strong>: Terraform hat eine steile Lernkurve, aber einmal aufgesetzt ist die gesamte Infrastruktur reproduzierbar, versioniert und per Befehl zerst\u00f6rbar. Zudem ist es so einfacher, den Anbieter zu wechseln (bspw. von AWS zu Azure).<\/p>\n\n\n\n<p><strong>WebSockets in Kubernetes = Stateful vs Stateless:<\/strong> WebSockets sind langlebige, stateful Verbindungen, wohingegen Kubernetes f\u00fcr Stateless HTTP optimiert ist. Pod-Neustarts killen Verbindungen, Load Balancing wird ungleichm\u00e4\u00dfig, und zwei Spieler im selben Game k\u00f6nnen auf verschiedenen Pods landen.<\/p>\n\n\n\n<p><strong>Komplexit\u00e4t von AWS:<\/strong> AWS bietet f\u00fcr jedes Problem einen Managed Service, aber die Komplexit\u00e4t ist enorm. IAM allein mit Policies, Roles, Trust Relationships, IRSA und OIDC Providern f\u00fchlt sich wie eine eigene Ausbildung an. Dazu kommen noch die endlose Anzahl an veschiedenen Services von AWS. Wir haben uns ein Semester mit AWS besch\u00e4ftigt und haben das Gef\u00fchl, immernoch and der Oberfl\u00e4che zu kratzen. Jedoch hat sich AWS zum Lernen von Cloud-Engineering gelohnt.<\/p>\n\n\n\n<p><strong>Security by Design:<\/strong> Jede Architekturentscheidung hat Security-Implikationen, die sich sp\u00e4ter nur schwer korrigieren lassen. Wir h\u00e4tten ein Security-Review als festen Schritt vor jedem Deployment einplanen und generell fr\u00fcher und mehr Testen m\u00fcssen. Au\u00dferdem geh\u00f6ren Secrets nie in Code oder ConfigMaps, sondern immer in Kubernetes Secrets oder AWS Secrets Manager.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Einleitung Den meisten sollte das Spielprinzip von \u201cCookie Clicker\u201d bekannt sein: Ein Klick auf einen Keks erh\u00f6ht den Spielstand um einen Punkt. Das Spiel ist endlos, hat keine Punktegrenze. Es geht darum, im Leaderboard nach oben zu klettern. Im Rahmen der Vorlesung \u201cSystem Engineering and Management\u201d (143101a)&nbsp; erweiterten wir das Konzept zu einem Echtzeit-Multiplayer Spiel. [&hellip;]<\/p>\n","protected":false},"author":1293,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1,650,22,651],"tags":[84,7,3,394,154],"ppma_author":[1143,1173],"class_list":["post-28550","post","type-post","status-publish","format-standard","hentry","category-allgemein","category-scalable-systems","category-student-projects","category-system-designs","tag-aws","tag-cloud","tag-docker","tag-game","tag-kubernetes"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":23517,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2022\/08\/29\/multiplayer-game-with-aws-stadtlandfluss\/","url_meta":{"origin":28550,"position":0},"title":"Multiplayer Game with AWS |\u00a0StadtLandFluss","author":"gi004","date":"29. August 2022","format":false,"excerpt":"Dieser Blogbeitrag soll einen Einblick in die Entwicklung unserer Webanwendung mit den unten definierten Funktionen geben sowie unsere L\u00f6sungsans\u00e4tze, Herausforderungen und Probleme aufzeigen.\u00a0 Cloud Computing VorlesungProjekt Idee & InspirationZielEinblick in das Spiel \u2013 DemoFrameworks - Cloud Services - InfrastructureArchitekturCloud KomponentenAWS ServicesDynamo DBS3LambdaAmazon API- GatewayAmazon CloudWatchTestingCI\/CD PipelineSchwierigkeitenFazit Cloud Computing Vorlesung Ziel\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/08\/image.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/08\/image.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/08\/image.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2022\/08\/image.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":28654,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/28\/multiplayer-arena-dynamische-game-sessions-mit-docker-fastapi\/","url_meta":{"origin":28550,"position":1},"title":"Multiplayer-Arena: Dynamische Game-Sessions mit Docker &amp; FastAPI","author":"Emre Kalkan","date":"28. February 2026","format":false,"excerpt":"Game: https:\/\/46.101.127.20.sslip.io\/ (online bis 31. M\u00e4rz 2026) (beachte Readme im Git!) Git Repo: https:\/\/github.com\/ek101-collab\/ArenaGame-with-ContainerSessions Einleitung und Motivation Im Rahmen der Vorlesung \"System Engineering und Management\" bestand die Kernaufgabe darin, ein Projekt zu konzipieren und umzusetzen, das moderne Web- und Cloud-Technologien nutzt. Zu Beginn der Projektphase lag mein Fokus auf einer\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":28021,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2025\/09\/13\/multiplayer-web-game-mit-aws-schiffe-versenken\/","url_meta":{"origin":28550,"position":2},"title":"Multiplayer Web-Game mit AWS | Schiffe versenken","author":"Leon Obertopp","date":"13. September 2025","format":false,"excerpt":"Projektidee: Im Rahmen der Vorlesung \"Software Development for Cloud Computing\" sollen die Studierenden in Gruppen ein eigenes Projekt, mit Hilfe von in der Vorlesung gezeigten Cloud Technologien umsetzen. Wir hatten Anfangs Probleme ein geeignetes Thema zu finden, da unser Wissenstand im Thema Cloud nicht besonders gro\u00df war. Letztendlich haben wir\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/09\/image-4.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/09\/image-4.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/09\/image-4.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":28823,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/28\/morehuehner-ein-moorhuhn-remake-als-cloud-native-multiplayer-browsergame\/","url_meta":{"origin":28550,"position":3},"title":"Morehuehner: Ein Moorhuhn-Remake als Cloud-Native Multiplayer-Browsergame","author":"Michael Dick","date":"28. February 2026","format":false,"excerpt":"1. Einleitung \u201cMoorhuhn\u201d, wer erinnert sich nicht? Damals auf Windows XP, in der Mittagspause oder nach der Schule, mit dem Fadenkreuz \u00fcber den Bildschirm und auf pixelige H\u00fchner geballert. F\u00fcr uns war Moorhuhn eines dieser Spiele, das man eigentlich nie alleine spielen wollte. Man sa\u00df vor dem Rechner, jemand schaute\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/redis.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/redis.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/redis.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/redis.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/redis.png?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/redis.png?resize=1400%2C800&ssl=1 4x"},"classes":[]},{"id":24062,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2023\/02\/28\/concurrent-game-play\/","url_meta":{"origin":28550,"position":4},"title":"Concurrent Game Play","author":"Philipp Rapp","date":"28. February 2023","format":false,"excerpt":"Concurrent Game Play mit AWS am Beispiel des Videospiels \"New World\" Amazon Games ver\u00f6ffentlichte 2021 mit dem Videospiel New World ein Massive Multiplayer Online Role Playing Game (MMORPG), das Amazon Web Services (AWS) f\u00fcr s\u00e4mtliche serverseitigen Dienste nutzt. Trotz eher mittelm\u00e4\u00dfiger Rezensionen des Spiels mit durchschnittlich 3,2 von 5 Sternen\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/02\/image-11.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/02\/image-11.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/02\/image-11.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/02\/image-11.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/02\/image-11.png?resize=1050%2C600&ssl=1 3x"},"classes":[]},{"id":24368,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2023\/03\/07\/how-classical-mmo-rpgs-work-starring-final-fantasy-xiv\/","url_meta":{"origin":28550,"position":5},"title":"How classical MMO-RPGs work, starring Final Fantasy XIV","author":"Marc Schillke","date":"7. March 2023","format":false,"excerpt":"Promotion Material Final Fantasy XIV [2] Einleitung MMO-RPGs oder Massiv-Multi-Player-Role-Playing-Games standen schon lange vor Facebook, Twitter und Co. vor der Herausforderung ein System f\u00fcr m\u00f6glicherweise Millionen von Usern zeitgleich zu designen, dass ein Concurrent Gameplay und eine Concurrent Gameworld erm\u00f6glicht. Doch wie konnten Spiele wie World of Warcraft, Everquest und\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/03\/1-uBGMSA3-scaled.jpg?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/03\/1-uBGMSA3-scaled.jpg?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/03\/1-uBGMSA3-scaled.jpg?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/03\/1-uBGMSA3-scaled.jpg?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/03\/1-uBGMSA3-scaled.jpg?resize=1050%2C600&ssl=1 3x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/03\/1-uBGMSA3-scaled.jpg?resize=1400%2C800&ssl=1 4x"},"classes":[]}],"jetpack_sharing_enabled":true,"authors":[{"term_id":1143,"user_id":1293,"is_guest":0,"slug":"tom_bestvater","display_name":"Tom Bestvater","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/87d68395150bdcade4f727bb7575ef1671af77025ffa6605eea84278c6b8516e?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""},{"term_id":1173,"user_id":1302,"is_guest":0,"slug":"maurice_dolibois","display_name":"Maurice Dolibois","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/ef9c26568c4c62538b8f73ceebd75f04100149e17ae1eff5ad495eadd13ea7cb?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/28550","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/users\/1293"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/comments?post=28550"}],"version-history":[{"count":6,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/28550\/revisions"}],"predecessor-version":[{"id":28568,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/28550\/revisions\/28568"}],"wp:attachment":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media?parent=28550"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/categories?post=28550"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/tags?post=28550"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/ppma_author?post=28550"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}