Basisprojekt

Last modified by mgrawunder on 2025/09/09 08:42

SoftwareprojektLogo.png

Hier folgen Erklärungen des neuen Basisprojekts. Es wird um die folgenden Themen gehen:

Basisprojekt mit IntelliJ einrichten

1755245956916-184.png

Clone

Achtung! In dem Screenshot wird das globale Basisprojekt verwendet. Für jede Gruppe existiert bereits ein eigenes Repository, welches verwendet werden sollte,

Sie finden die Clone-URL wie folgt:

  • Loggen Sie sich auf https://gitlab.swl.informatik.uni-oldenburg.de/ ein
  • Falls nicht vorausgewählt, wählen Sie auf der linken Seite "Projects"
    1757398628416-879.png
  • Da Sie bisher noch nichts gemacht haben, ist diese Seite leer. Wechseln Sie auf den Reiter Member
  • Dort sollte ihr Repository zu finden sein.
  • Oben rechts gibt es einen Button Code. Kopieren Sie dort die URL, die hinter "Clone with HTTPS" steht.
    1757398731084-704.png

1755245971657-468.png

Access Token

Beim ersten Mal clonen müssen Sie Ihre Gitlab-Zugangsdaten verwenden. Statt Name und Passwort ist es sinnvoll, einen Access-Token zu definieren. Das können Sie machen, in dem Sie auf ihre Profilbild klicken (auf der linken Seite) und dort "Edit Profile" auswählen. In dem nun folgenden Menü gibt es den Punkt Access tokens.

1757398899497-714.png

Dort können Sie mit 1757398947128-748.png ein neues Token anlegen.

Als Namen können Sie z.B. IntelliJ verwenden. Wichtig! Das Token ist aus Sicherheitsgründen nur eine bestimmte Zeit gültig. In diesem Gitlab kann dies maximal ein Jahr sein, standardmäßig ist ist hier aber ein Monat gewählt. Sie sollten also das Feld anpassen.

1757399088336-273.png

Bei den Scopes sollten die beiden Rechte "read_repository" und "write_repository" gewählt werden.

Danach wird das Access Token generiert

1757399184270-348.png

Jetzt ist es wichtig, dass Sie sich das Token sichern! 

Beim Einloggen in IntelliJ können Sie dieses Token im Passwort-Feld verwenden. Geben Sie ihren Account bei Name ein.

Nach dem Clonen

sollten Sie einen Bildschirm ähnlich zu dem folgenden sehen:

1755245980026-164.png

Auf dem main-Branch (master) kann keine Änderung gemacht werden, deswegen muss auf einen anderen Branch gewechselt werden. Im Beispiel development.

1755245996886-733.png

Initialer Build (Generierung)

Dem neuen Code fehlen einige generierte Dateien. Diese werden wie folgt erzeugt. 
Hinweis: Immer, wenn an dem OpenAPI-Dokument etwas geändert wird oder neue Abhängigkeiten für Maven definiert werden, muss diese Aktion durchgeführt werden.

1755246008466-477.png

1755246018789-616.png

Lombok Plugin

Lombok Plugin installiert? Wenn nein jetzt machen

1755248508652-523.png

Hinweis: Wenn man schon hier ist, kann man auch das Spring-(Boot)-Plugin installieren

1756886220468-891.png

Server laufen lassen

Den Serverbereich aufklappen und dort auf die Datei ServerApp mit der rechten Maustaste klicken.

1755246035428-328.png

... und ggf. Lombok aktivieren (Man wird nach dem Start der Anwendung gefragt). WICHTIG! Wenn das Lombok-Plugin nicht installiert und die folgende Funktion nicht aktiviert ist, muss jede Änderung mit Hilfe von Maven passieren.

1755246072443-191.png

1755246118807-452.png

Logging umstellen

Wenn man möchte, kann man das Logging umstellen.

1755246135109-325.png

1755246147827-679.png

1755246162330-595.png

Development Profil aktivieren

Damit das Testen einfacher geht, gibt es ein spezielles Spring-Profil. In diesem werden automatisch user1-user9 mit jeweils dem identischen Passwort angelegt und man spart sich die Registierung.

Wenn man die Anwendung einmal gestartet hat, kann man dies Configuration anpassen:

1757399848941-253.png

1755246173415-934.png

Wenn man IntelliJ nicht in der Pro-Version verwendet bzw. das Spring Boot Plugin nicht installiert hat, muss kann man ein Spring-Profil über eine Umgebungsvariable in der Konfiguration des Servers setzen: 

SPRING_PROFILES_ACTIVE=dev

1755248752596-839.png

Danach muss man den Server neu starten!

Client starten

Wenn der Server gestartet ist, kann man mehrere Clients starten. Dafür auf jeden Fall die Klasse Main verwenden.

Sollte es nun Problem geben, schauen Sie bitte, ob Sie noch eine alte Java-Version auf ihrem System installiert haben. Wir brauchen mindestens Java 21!

1755246257400-525.png

1755246212916-883.png

1755246223246-834.png

Mehrere Instanzen des Clients ermöglichen

Standardmäßig erlaubt IntelliJ nicht das Starten mehrerer Clients. Man könnte nun mehrere Configurations für den Client anlegen. Man kann aber auch in der Konfiguration unter "Modify options" den Haken bei "Allow multiple instances" setzen. Dann kann eine beliebige Anzahl von Clients gestartet werden.

1755246233218-893.png

Wenn folgendes kommt, ist entweder das "dev" Profil nicht aktiviert, der Nutzer wurde nicht angelegt. Wenn der Server nicht läuft, gibt es eine andere Fehlermeldung. 

1755246292057-581.png

Kurzer Blick ins Basisprojekt

1755249096987-249.png

1755249136156-419.png

Screenshots

1755249228556-469.png

Kommunikation Client --> Server

1755249285866-367.png

Der Server verwendet das REST-Protokoll und als Austauschformat JSON

OpenAPI

(Hinweis: Ich bin nicht sicher, ob IntelliJ diese Darstellung auch hat, wenn man nicht die Ultimate Version verwendet. Ggf. muss man das OpenAPI-Plugin installieren)

1755250026156-269.png

1755250050031-304.png

  • Paths: Endpunkte der API (z.B. /users, /lobbies).
  • Operations: Spezifikation von Methoden wie GET, POST.
  • Definitions: Detaillierte Beschreibung von Eingabe- und Ausgabestrukturen.
  • Security: Authentifizierungsmechanismen.

1755250061990-172.png

Kann JSON oder YAML (Yet Another Markup Language) verwenden YAML ist wie JSON nur mit weniger Klammern

1755250157536-746.png

Die aktuelle Version des OpenAPI Dokumentes findet sich im Basisprojekt 2 https://gitlab.swl.informatik.uni-oldenburg.de/SPB/SWPBasisprojekt2/-/blob/master/openapi.yaml?ref_type=heads

Dort wird die Datei auch grafisch dargestellt.

Maven und OpenAPI

Die OpenAPI Datei kann verwendet werden, um sich die REST-Schnittstellen generieren  zu lassen. Diese Erzeugung erfolgt mit dem OpenAPI Generator https://github.com/OpenAPITools/openapi-generator

Man kann dabei jetzt einen Kommandozeilenaufruf verwenden oder man setzt auf das im Basisprojekt vorhandene MVN ()

Dafür ist in den Maven-Dateien bereits das OpenAPI Generator Plugin integriert. Da im Client und im Server unterschiedliche Arten verwendet werden, erfolgt die Konfiguration im Client und im Server unterschiedlich:

Client

Im Client werden die Apache Http Bibliothek verwendet.

1756887005209-855.png

Server

Im Server wird Spring (Boot) verwendet

1756887037619-847.png

TODO: Weitere Informationen zu

- Lombok

- Dependency Injection

- Spring (Boot), siehe auch https://www.marcobehler.com/guides/spring-framework 

Erweiterung der REST-Schnittstelle

In diesem Beispiel wird einmal gezeigt, wie die REST-Schnittstelle des Basisprojektes einfach erweitert werden kann.

In diesem Beispiel soll die aktuelle Schnittstelle um die Möglichkeit erweitert werden, alle Lobbies vom Server zu bekommen.

Schritt 1: Erweitere das OpenAPI-Dokument

Um diese neue Funktion sowohl im Client als auch im Server verwenden zu können, ist es notwendig, diese neue Funktion im OpenAPI-Dokument zu definieren.

Die Funktion soll sehr einfach sein und keine Parameter verlangen. Dafür bietet sich die GET-Funktion an.

Im folgenden Bild sind alle Anpassungen zu sehen:

1756887436525-790.png

Nach dem Speichern, sollte das OpenAPI-Dokument wie folgt aussehen

1756887488020-376.png

Jetzt kann man entweder in IntelliJ 

1756888245896-845.png

oder im Terminal (z.B. auch in IntelliJ)

1756888279902-777.png

Wobei hier auch clean compile reichen würde. 

ACHTUNG! Falls maven Problem macht, kann das auch an einer falschen Java-Version im System liegen (siehe auch FAQ)

Es werden durch den Aufruf neue Inhalte generiert (bzw. die alten überschrieben).

1756888428042-802.png

Hinweis: Niemals Änderungen unterhalb des target-Ordners machen. Das wird von Maven bei clean gelöscht.

Wie bekommt man dann aber nun die Funktionalität rein?

Für jeden Endpunkt (also aktuell lobbies und users) werden drei Interfaces/Klassen erzeugt:

  • *Api (z.B, LobbiesApi): Beschreibung der REST-Methoden, vor allem auch das Mapping von z.B. /lobbies/join auf die Methode lobbyJoin(String)
  • *ApiController implements *Api (Für Spring) (z.B. LobbiesApiController)

  • *ApiDelegate (z.B. LobbiesApiDelegate): Macht die eigentliche Arbeit und muss im eigenen Code-Bereich erweitert werden!

Schritt 2: Erweiterung auf Server-Seite

Da es schon Funktionen für die Lobbies gibt, gibt es auch bereits eine Implementierung, die LobbiesApiDelegate überschreibt

1756888762381-912.png

Wenn man einen neuen Endpunkt definiert, muss man auch einen neuen Service definieren. (Hinweis: Der Service muss eine Spring Komponenten sein, damit sie in den Spring Context aufgenommen wird).

In der Klasse muss man dann die neue Methode lobbyList aus der API überschreiben.

1756888929507-312.png

Dabei wird folgendes gemacht:

  1. Es wird ein Rückgabeobjekt vom Typ Liste erzeugt
  2. Es wird über alles Lobbies auf dem Server gegangen (lobbyManagement.getLobbies())
  3. Da der Client u.U. nicht die vollständigen Informationen über die Lobbies bekommen soll, gibt es zwei unterschiedliche Klassen: ServerLobby und LobbyDTO.
  4. Die Foreach-Schleife sorgt dafür, dass in das Rückgabeobjekt nur die LobbyDTOs eingefügt werden. 
  5. Dafür wird eine Funktion mit dem Namen lobbyMapping verwendet
  6. Schließlich wird am Ende gesagt, dass alles ok ist und eine Antwort ResponseEntity.ok mit dem Rückgabeobjekt (lobbies) gesendet.

Anmerkung: Das Basisprojekt ist aktuell so eingerichtet, dass Spring Exceptions auffängt und entsprechend an den Client leitet. Diese findet in der Klasse  GlobalExceptionHandler statt

Auf Server-Seite fehlt jetzt noch die Methode getLobbies im LobbyManagement

1756889590500-656.png

LobbyMapping

Da man relativ oft Server-Objekt in DTO umwandeln muss gibt es im Basisprojekt MapStruct. Damit muss man nur die DTO-Klasse anlegen (i.d.R. über OpenAPI!!)

Also z.B.

1756889440395-856.png

und definiert ein Interface mit einer Annotation

1756889472103-847.png

und damit kann man die Funktion aufrufen. Hinweis: Der Mapper ist im LobbyService über die Spring Dependency Injection gebunden.

Schritt 3: Erweiterung auf Client-Seite (Java)

Hinweis: Das Beispiel bezieht sich hier auf eine Client mit Java. Für andere Clients wie Angular ist das Vorgehen anders.

Auf der Client-Seite wird die komplette Kommunikation mit dem Server in der generierten Klasse DefaultApi gekapselt.

1756889795622-530.png

Dort gibt es eine neue Methode lobbyList. Die sorgt dafür, dass der REST-Aufruf auf die Server-Seite geht und liefert das passende Objekt List<LobbyDTO> zurück

Im Client gibt es auch eine Klasse LobbyService. Dort ist die DefaultApi Klasse über Dependency Injection gebunden.

1756889917681-650.png

Dort kann man nun eine neue Methode getLobbies() integrieren:

1756889979252-910.png

Und das Ganze dann z.B. im MainMenuPresenter verwenden:

1756890010118-149.png

Anmerkung: Obwohl DefaultApi alle Funktionen zum Server kapselt, sollte man im Client spezifische Services für bestimmte Bereich haben, die diese Klasse verwenden.  Das führt zu einer besseren Trennung von Funktionalitäten im Code.

Kommunikation: Server --> Client (WebSockets)

1756890800817-370.png

Da man mit REST nicht Nachrichten vom Server an den Client schicken kann, werden im Basisprojekt dafür WebSockets verwendet.

Spring bietet eine native Unterstützung von WebSockets. Für eigene Funktionen kann man sich in die Kommunikation über die Serverklasse WebSocketHandler einklinken

1756890924024-346.png

Sobald sich jemand beim Server für WebSockets angemeldet hat wird von Spring ein org.springframework.web.socket.messaging.SessionConnectedEvent
geworfen, welches in der folgenden Methode (im WebSocketHandler) aufgefangen wird

1756890958794-603.png

Die Methode ist Observer für das Event SessionConnectedEvent

Der WebSocketServer kennt die Nutzer und erlaubt das Einloggen nur, wenn Login und Passwort stimmen (durch Spring Security)

1756891019715-621.png

1)Aus dem Event kann der Nutzer gelesen werden (der sollte nie leer sein)

2) Dann wird sich aus dem Repository (später mehr) der Nutzer geholt, der durch den Namen identifiziert ist (z.B. „test1“)

3) Schließlich werden allen anderen darüber informiert, dass ein neuer Nutzer da ist

STOMP

WebSockets haben kein Protokoll (wie z.B. http)

Es können entweder binäre oder textuelle Daten verarbeitet werden (die jeweiligen Gegenstellen müssen das wissen!)

Wenn man jetzt mehr als nur Text verschicken möchte, muss man sich überlegen, wie man Objekte z.B. mit JSON serialisiert (analog zu REST)

STOMP: Streaming Text Oriented Messaging Protocol

Definiert ein einfaches Protokoll, welches es erlaubt, sinnvoll über WebSockets zu kommunizieren

Ist ein Teil von Spring

Methoden sind z.B. CONNECT, SEND oder SUBSCRIBE

STOMP arbeitet mit Topics

Ein Client registriert (SUBSCRIBE) sich für bestimmte Ereignistypen

§Wenn auf der Server-Seite dieser Typ veröffentlich wird dann wird dies an die jeweils interessierten Clients geschickt

Publish/Subscribe-Pattern

https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html

§Der Server definiert unterschiedliche Topics (je nach Modul)

§Beim Nutzermanagement aktuell:

§/topic/users/loggedIn: Es hat sich ein neuer Nutzer angemeldet

§/topic/users/loggedOut: Ein Nutzer hat sich ausgeloggt

1756891125969-748.png

§Topic-Namen sind Strings, sollte aber Aufbau von oben entsprechen

§In der Lobby würde es stattdessen /topic/lobbies/* heißen

WebSockets: Versenden von Nachrichten

1756891180516-843.png

1756891216134-578.png

Nachrichteninhalt

1756891254830-647.png

  • message kann grundsätzlich alles sein, was serialisiert werden kann
  • Man könnte nun einfach die Java-Serialisierung verwenden (im alten Basisprojekt ist das auch so)
  • Das hat aber eine Reihe von Nachteilen
    • Der Empfänger muss dafür unbedingt auch ein Java-Client sein und er muss exakt dieselbe Klasse bei sich haben, damit der das Objekt wieder zurück in ein Java-Objekt umwandeln kann
    • Es gibt eine Reihe von Sicherheitsproblemen
  • Besser: Definiere ein gemeinsames Austauschformat, was viele verstehen --> Im Basisprojekt (und in vielen anderen Projekten auch) JSON verwenden
  • Insbesondere Web-Clients (JavaScript) bieten hervorragende Möglichkeiten, an JSON zu verarbeiten
  • Client und Server haben sich damit auf Format für den Austausch geeinigt
    • Topic: Strings
    • Message: JSON

An den Clilent werden auch bei WebSockets nur DTOs verschickt! (userMapping)

1756891375095-158.png

Wie verbindet sich ein Client mit dem Server?

UserService bietet eine Methode zum Login an. Diese ruft nun aber keine REST-Endpunkt auf (da man sich sowieso bei JEDEM Aufruf authentifizieren muss, macht so ein Endpunkt kein Sinn). Stattdessen wird die Verbindung mit dem WebSocket hergestellt und dort Nama und Passwort überprüft.

1756891512330-186.png

Auf Server Seite (WebSocketConnectionManager):

1756891551794-161.png

1) Variablen definieren

2) WebSocketClient erzeugen

3) Daraus WebSocketStompClient machen

4) Jackson als Mapper definieren (DTO-Object <-> JSON)

1756891617399-232.png

1) Asynchron die Verbindung zum Server aufbauen

2) Wenn erfolgreich in das Hauptmenü wechseln (showScene à später mehr)

3) Über den Kontext ein Event pushen LoggedInEvent

4) Jede Serververbindung hat eine Session