Springboot zu Serverless: Probleme und Paradigmen

Julian Schniepp

Im Rahmen der Vorlesung „Software Development for Cloud Computing“ sollte jedes Team ein eigenes Cloud‑Projekt umsetzen. Unser Projekt Taskflow sollte dabei ein serverloses Todo‑Management‑System werden. Ziel war es dabei, neue und vor allem industrierelevante Cloud‑Technologien praktisch zu erlernen. Der Backend‑Teil ist mit Spring‑Boot realisiert, welcher über AWS Lambda und API Gateway bereitgestellt modular eingesetzt werden kann. Als Persistenz kommt DynamoDB zur Verewendung. Der Frontend‑Teil ist eine React‑Anwendung, die Aufgaben anzeigt, anlegt, bearbeitet und löscht. Zur Authentifizierung wird JWT verwendet, die CI/CD‑Pipeline basiert auf GitHub Actions.

Folgend beschreiben wir, wie wir von monolithischen Denken zu einem serverless‑orientierten Mindset gewechselt haben, welche Technologien zum Einsatz kamen, welche Stolpersteine es gab und unsere Learnings auf dem Weg.

Projektidee und grober Funktionsumfang

Taskflow ist ein „To‑Do‑Manager“ mit folgenden Kernfunktionen:

  • Benutzerregistrierung und ‑anmeldung über JWT: Das Backend stellt Endpunkte zum Registrieren, Anmelden und Aktualisieren von Tokens zur Verfügung. Nach erfolgreicher Anmeldung liefert der Server ein JWT zurück, das bei allen weiteren API‑Aufrufen im Authorization‑Header gesendet wird.
  • CRUD‑Operationen für Aufgaben: Es gibt Endpunkte zum Erstellen, Abrufen, Aktualisieren und Löschen von Todos. Aufgaben gehören zu einem Benutzer und beinhalten neben Titel und Beschreibung einen Status (offen/abgeschlossen) sowie Zeitstempel für Erstellung und Aktualisierung.
  • Health‑Endpunkte: Für das Monitoring stehen verschiedene Health‑Checks zur Verfügung, darunter ping, database und status. Sie lassen sich z. B. für Load‑Balancer‑Checks nutzen, vor allem kamen diese aber bei dem lokalen Testen zum Einsatz, um zu überprüfen, ob alle einzelnen Komponente individuell funktionieren.
  • React‑Frontend: Das Frontend bietet eine minimalistische Oberfläche mit Dark/Light‑Mode, Benutzer‑Anmeldung und ‑Registrierung, Aufgabenliste, einzelne Felder zur Aufgabenbearbeitung sowie einem Bereich für bereits erledigte Aufgaben.

Architektur und Technologieauswahl

Backend: Spring Boot + AWS Lambda + DynamoDB

Das Backend basiert auf Spring Boot und wurde so strukturiert, dass es sowohl als klassische REST‑Anwendung laufen kann als auch serverlos via AWS Lambda. In der Produktionsumgebung wird die Anwendung als Lambda‑Funktion mit einer REST‑API über API Gateway betrieben. Die template.yaml (SAM) definiert eine standard Lambda‑Konfiguration (1 GB RAM, 30 Sekunden Timeout) und erstellt DynamoDB‑Tabellen für Todos und Benutzer. Vorteile des serverlosen Ansatzes sind Skalierung, keine Serveradministration und die Möglichkeit, die AWS‑Free‑Tier zu nutzen.

Die Datenbank ist AWS DynamoDB, eine NoSQL‑Datenbank. Die Todos‑Tabelle verwendet eine ID als Partition‑Key und eine sekundäre Indexe auf userId. Als Neulinge im NoSQL‑Bereich mussten wir umdenken, dass es keine klassischen Joins gibt; stattdessen werden Daten eher denormalisiert gespeichert. Das Wegfallen eines festen Schemas machte das Hinzufügen neuer Attribute einerseits einfacher, erforderte aber sorgfältiges Design, um effiziente Abfragen zu ermöglichen.

Frontend: React + Context + JWT

Das Frontend basiert auf React. react-router-dom ermöglicht Routing (Startseite, erledigte Aufgaben, Login und Signup). Mehrere Context‑Provider kapseln Anwendungszustand:

  • AuthContext verwaltet JWT, hält den Anmelde­status und stellt Funktionen zum Login/Logout bereit.
  • TaskContext verwaltet eine Liste lokaler Aufgaben, lädt diese über todoAPI vom Backend und speichert zusätzliche Metadaten wie Fälligkeit oder Notizen in localStorage.
  • ThemeContext realisiert den Dark/Light‑Mode.
  • NotificationContext ermöglicht Benachrichtigungen.

Die Komponente Home zeigt offene Aufgaben, erlaubt das Hinzufügen via Input‑Feld und verwendet TaskModal für die Bearbeitung einer Aufgabe. Erledigte Aufgaben werden mit Übergangsanimationen ausgeblendet und können in einer eigenen Route eingesehen werden.

CI/CD‑Pipeline

Einer der Schwerpunkte des Projekts war eine GitHub‑Actions Pipeline. Das Ziel war, möglichst viele Praktiken aus dem DevOps‑Bereich auszuprobieren, und den Entwicklungs- und Deploymentaufwand einfacher zu machen. Die Pipeline ist in drei Hauptphasen unterteilt :

  1. Continuous Integration (CI) – Jeder Push und Pull‑Request triggert eine Build‑Pipeline. Diese führt Unit‑Tests aus, cached Maven‑Abhängigkeiten, generiert Test‑Reports, führt eine Sicherheitsscanner (OWASP Dependency Check) aus und erstellt einen JAR‑Artifact. Anschließend analysiert SonarCloud den Code auf Qualität und Testabdeckung täglich.
  2. Continuous Deployment (CD) – Abhängig vom Ziel‑Branch wird die Anwendung automatisiert mit SAM CLI nach AWS deployt. Ein Push auf develop erzeugt eine Development‑Umgebung, ein Push auf main eine Staging‑Umgebung, während Produktion manuell per Workflow Dispatch ausgelöst wird. Jede Umgebung nutzt separate AWS‑Secrets und hat eigene CORS‑Konfigurationen. Nach dem Deployment werden Smoke‑ oder Integrationstests ausgeführt und das Ergebnis in die Pull‑Request kommentiert. Dies basiert auf vorherbestehende CD Templates, bei uns kam nur eine (Production) Umgebung zum Einsatz
  3. Performance‑Tests – Es existiert ein optionaler Workflow für Lasttests mit k6. Dieser kann manuell gestartet werden, erzeugt Berichte und überprüft, ob 95 % der Anfragen unter zwei Sekunden bleiben .
    Weitere GitHub‑Features wie Dependabot, Secrets Scanning und branch protection rules sind aktiviert. Aufgrund von Problemen mit AWS‑Secrets schlagen aktuell einzelne Tests fehl (das Deployment selbst funktioniert lokal).

Lokales Testen

Für das lokale Testen gibt es zwei Ansätze:

  • LocalStack simuliert AWS‑Services komplett in Docker. Ein Script startet LocalStack, DynamoDB Local und erstellt die nötigen Tabellen. Danach können die Lambda‑Funktion und API Gateway lokal getestet werden, z. B. per curl auf die Health‑Endpunkte.
  • SAM Local ermöglicht das Ausführen und Debuggen einzelner Lambda‑Funktionen oder der gesamten REST‑API. Über das Script sam-local.sh kann die API auf Port 3000 gestartet werden; anschließend lassen sich Endpunkte für Registrierung, Login und Todos testen.

Learnings – Missverständnisse und Erkenntnisse

  1. Serverless‑Mindset statt Monolith

Was ist Serverless?

Serverless‑Architekturen basieren auf Funktionen, die durch Ereignisse wie HTTP‑Anfragen nur “on demand” ausgelöst werden. Der Cloud‑Provider skaliert automatisch und verrechnet nur die tatsächliche Nutzung. Die bekannte monolithische Struktur mit dauerhaft laufendem Server entfällt.

Missverständnis: Zunächst gingen wir davon aus, dass man einfach eine komplette Spring‑Boot‑Anwendung schreibt und diese dann als Lambda „hochlädt“. Das hätte bedeutet, dass alle Controller immer mitstarten und der Cold‑Start sehr lange dauert, quasi ein normaler Webserver der Endpunkte offenlegt.

Learning und Umsetzung: Tatsächlich kapselt man nur die benötigten Handler in einer speziellen Lambda‑Klasse (AwsLambdaHandler::handleRequest). API Gateway fungiert als Router; einzelne Controller‑Methoden werden durch Pfad‑Pattern aufgerufen. Dadurch ist die Startzeit akzeptabel. Auch das Thema Cold Start war neu – beim ersten Aufruf einer Lambda vergehen 10–15 Sekunden. Später ist die Funktion „warm“ und reagiert schnell.

  1. Environment‑Variablen und Secrets

Was sind Environment-Variable und Secrets?

Konfigurationsparameter und geheime Schlüssel sollten nicht im Code landen, sondern über Umgebungsvariablen bereitgestellt werden. In Spring Boot können sie via @Value oder application-*.yml annotiert und gelesen werden.

Missverständnis: Anfangs waren unsere Zugangsdaten in application.yml. Das ist offensichtlich unsicher und erschwert den Wechsel zwischen Umgebungen, falls auf anderen Computern andere Konfigurationen vorliegen.

Learning und Umsetzung: Das Projekt verwendet .env‑Dateien und das Spring‑Profile‑System. Die README erklärt, welche Variablen für die Profile local, docker und prod gesetzt werden müssen (u. a. jwt_secret, aws_access_key_id, aws_secret_access_key, cors_allowed_origins) . Für GitHub Actions werden Secrets pro Umgebung hinterlegt und in den Workflows referenziert. Durch diese Trennung konnten wir die Anwendung lokal ohne echte AWS‑Zugangsdaten ausführen und bei Bedarf production Schlüssel verwenden.

  1. JWT‑Authentifizierung

Was ist JWT?

JSON Web Tokens sind selbstsignierte Tokens, die einen Benutzer identifizieren. Ein Nutzer meldet sich mit Username und Passwort an, das Backend erzeugt ein JWT und sendet es dem Client, der es bei zukünftigen Anfragen mitsendet. Das Backend validiert das Token, ohne bei jedem Request die Datenbank zu befragen.

Missverständnis: Wir waren unsicher, wie das Token generiert und sicher gespeichert wird, und wie man es entsprechend sicher lagert, verwaltet und wie ein kompletter Authetifizierungshandshake aussieht.

Learning und Umsetzung: Die Anwendung nutzt BCrypt zum Hashen von Passwörtern in der Datenbank und erstellt JWTs über einen eigenen JwtService. Die Controller‑Tests belegen, dass Registrierung und Login ein gültiges Token zurückgeben. Im Frontend wird das Token im AuthContext gespeichert; bei Ablauf kann über einen Refresh‑Endpoint ein neues Token geholt werden.

  1. NoSQL mit DynamoDB

Was ist NoSQL?

DynamoDB ist eine NoSQL‑Datenbank. Daten werden in Tabellen mit primärem Schlüssel (Partition‑Key) und optional Sort‑Key gespeichert. Es gibt keine Joins und keine relationalen Constraints. Stattdessen werden Entitäten häufig denormalisiert.

Missverständnis: Als Postgres‑Nutzer wollten wir klassisch Relationen definieren und JOINs definieren bei der Planung unser Schemata. Bei DynamoDB ist dies nicht möglich; man plant den Zugriff anhand der Abfragepfade und modelliert entsprechend.

Learning und Umsetzung: Die Todos‑Tabelle speichert neben ID, Titel, Beschreibung, Status und Zeitstempeln eine userId zur Zuordnung. Ein sekundärer Index ermöglicht das Abrufen aller Todos eines Benutzers. Die Frontend‑Metadaten (Fälligkeit, Notizen) werden lokal gespeichert, da sie momentan noch nicht im Backend Code implementiert sind.

  1. Docker Compose, LocalStack & SAM Local

Was versteht man darunter?

Docker Compose ermöglicht das Starten mehrerer Container (z. B. DynamoDB Local, LocalStack). LocalStack simuliert AWS‑Services, während SAM Local die Lambda‑Funktion lokal ausführt.

Missverständnis: Anfangs versuchte ich, alles alternativ lokal zu testen, um mit der Geschäftslogik abschließen zu können, und dann separat AWS lernen zu können.

Learning und Umsetzung: Nach vielen alternativen Lösungen und Implementationen von z.B. JPA, welche sehr viel Zeit in Anspruch genommen haben, habe wir einen Schritt zurück genommen, und uns nochmals auf LocalStack konzentriert.

  1. AWS SAM und CI/CD

Was ist SAM und CI/CD?

SAM (Serverless Application Model) erweitert CloudFormation und vereinfacht das Deployment von Lambda‑Funktionen und API Gateways. SAM CLI bietet Befehle zum Bauen (sam build), Lokalen Testen (sam local start-api) und Deployen (sam deploy).

Learning und Umsetzung: Mit SAM CLI und GitHub Actions ließ sich das Deployment automatisieren. Die Datei CI-CD-ARCHITECTURE.md beschreibt, dass für develop automatisch in eine Dev‑Umgebung, für main in eine Staging‑Umgebung und per manueller Aktion in die Produktion deployt wird. Parameter wie JwtSecret und CorsAllowedOrigins können per Kommandozeile entsprechend übergeben werden.

Problem: Wir hatte zwar bereits Berührugspunkte mit CI/CD Pipelines, jedoch mit anderen Technologien, und außerdem haben wir sie noch nicht selber aufgesetzt und eingerichtet. Somit war dies quasi ein komplett neues Gebiet.

  1. Modulare React Frontends

Was ist zu beachten wenn man ein Frontend baut für eine solche App?

Wichtig ist, mittels Kontext geschützte Inhalte sichtbar, oder nicht sichtbar zu machen, und dass alle Anfragen entsprechende Header beinhalten, sodass auch die korrekten Daten geladen werden können.

Problem: Der Umgang mit API Gateway war noch unbekannt, und somit waren tatsächliche Tests eingeschränkt. Da das Frontend eher Nebensache unseres Projekts war, mussten wir auch lernen wie wir Kompetenzen und Zeit einteilen, sodass beide Seiten des Projekts effektiv unabhängig voneinander vorangetrieben werden.

Learning und Umsetzung: Wir haben gelernt, dass ein Cloud-Frontend nicht nur UI ist, sondern aktiv in Authentifizierung und Datenfluss eingebunden wird; durch die konsequente Nutzung von React Contexts für Auth und Tasks sowie das Mitgeben der JWT-Header bei allen Requests konnten wir geschützte Inhalte zuverlässig steuern, und obwohl uns API Gateway für echte End-to-End-Tests noch fehlte, halfen lokale Mockups, Postman und eine klare Arbeitsteilung zwischen UI-Entwicklung und Backend-Schnittstellen dabei, das Frontend modular und unabhängig voranzutreiben.

Vor‑ und Nachteile der gewählten Architektur

VorteilNachteil
Serverless mit AWS Lambda & API GatewayKein Serverbetrieb, automatische Skalierung, Abrechnung pro Nutzung; ideal für geringe Last und schnelles Prototyping Vendor Lock‑in, eingeschränkte Laufzeit und Konfiguration, Cold‑Start‑Latenz, Alptraumgeschichten von hohen AWS Rechnungen
DynamoDB (NoSQL)Schemalos, hohe Skalierbarkeit, vollständig verwalteter ServiceKein ACID‑Support wie bei relationalen Datenbanken, Planung der Datenstruktur anhand der Abfragen erforderlich
JWT‑AuthentifizierungStateless, einfach zu validieren, kein Session‑Store nötigToken müssen sicher gespeichert werden, bei Verlust kein Widerruf; benötigt Refresh‑Mechanismus, Risiken auf Entwickler Seite, kein Social Sign-In
CI/CD mit GitHub ActionsAutomatisierte Tests, Security‑Scans und Deployments erhöhen Codequalität und reduzieren manuelle Fehler Komplexe Konfiguration; abhängig von korrekter Verwaltung der Secrets
LocalStack & SAM LocalSchnelle lokale Entwicklung ohne Cloud‑Kosten, Tests laufen offlineSimulieren nicht alle AWS‑Edgecases (z. B. Latenz), erfordern Einarbeitung

Reflexion und Ausblick

Die Arbeit an Taskflow war ein starker Lernprozess. Zunächst hatten wir eine starke monolithische Denkweise und wollte „einfach ein Spring‑Boot‑Projekt in AWS deployen“. Der Umstieg auf serverless jedoch erforderte ein Umdenken: Code wird als Funktion bereitgestellt, die nur für einen einzelnen Request lebt, und alles, was persistent bleiben soll, liegt außerhalb in Services wie DynamoDB. Dieses Paradigma hat mir verdeutlicht, was serverless tatsächlich bedeutet.

Die Einrichtung der CI/CD‑Pipeline war weniger komplex als erwartet, und hat sich ausgezahlt. Automatische Tests und Security‑Scans geben Sicherheit; Fehlermeldungen weisen frühzeitig auf Probleme hin. Hierzu gab es schon fertige Konfigurationen für unsere Verwendung, die in unserem Fall eher umfassender als notwendig waren, jedoch aber gute Einblicke geboten haben. Die größte Herausforderung hier war der Umgang mit Secrets. In der lokalen Umgebung funktionierte alles, in GitHub Actions fehlten jedoch anfangs die nötigen AWS‑Zugangsdaten – daher sind aktuell einige Tests rot.

Aus Zeitgründen ist das Deployment in die Cloud (S3 für das Frontend, Lambda für das Backend) noch nicht erfolgt. Die Pipeline ist jedoch vorbereitet; mit gültigen Secrets könnten Dev‑, Staging‑ und Prod‑Stacks erzeugt werden.

Insgesamt haben wir folgende Kernkompetenzen aufgebaut:

  • Verständnis für Serverless‑Architekturen und die Unterschiede zu klassischen Monolithen.
  • Nutzung von AWS SAM, LocalStack und Docker Compose zur lokalen Entwicklung.
  • Implementierung von JWT‑Authentifizierung und sicheren Passwortspeicher mit BCrypt.
  • Grundlagen von NoSQL und Datenmodellierung in DynamoDB.
  • Aufbau einer CI/CD‑Pipeline mit Tests, Sicherheitsanalysen und automatisiertem Deployment.

Dieses Projekt wurde durchgeführt von: Julian Schniepp, Lian Krabel und Raphael Pulffermüller


Posted

in

by

Julian Schniepp

Tags:

Comments

Leave a Reply