Wiki source code of Basisprojekt

Version 55.1 by mgrawunder on 2025/09/09 08:21

Hide last authors
Marco Grawunder 2.1 1 [[image:Main.Organisatorisches.WebHome@softwareprojekt_logo_transparent.png||alt="SoftwareprojektLogo.png" data-xwiki-image-style-alignment="end" height="136" width="309"]]
2
Marco Grawunder 28.1 3 Hier folgen Erklärungen des neuen Basisprojekts. Es wird um die folgenden Themen gehen:
4
Marco Grawunder 24.1 5 {{toc/}}
Marco Grawunder 23.2 6
Marco Grawunder 2.2 7
Marco Grawunder 7.2 8 = Basisprojekt mit IntelliJ einrichten =
9
10 [[image:1755245956916-184.png]]
11
Marco Grawunder 24.2 12 == Clone ==
Marco Grawunder 7.2 13
mgrawunder 52.2 14 Achtung! In dem Screenshot wird das globale Basisprojekt verwendet. Für jede Grupp
15
Marco Grawunder 7.2 16 [[image:1755245971657-468.png]]
17
18
mgrawunder 52.2 19
Marco Grawunder 7.2 20 [[image:1755245980026-164.png]]
21
22 Auf anderen Branch wechseln (hier development)
23
24 [[image:1755245996886-733.png]]
25
Marco Grawunder 24.2 26 == Initialer Build (Generierung) ==
Marco Grawunder 7.2 27
Marco Grawunder 10.2 28 [[image:1755246008466-477.png]]
Marco Grawunder 7.2 29
30
Marco Grawunder 10.2 31 [[image:1755246018789-616.png]]
32
33
Marco Grawunder 24.2 34 == Lombok Plugin ==
35
Marco Grawunder 23.1 36 **Lombok Plugin installiert? Wenn nein jetzt machen**
37
Marco Grawunder 25.2 38 [[image:1755248508652-523.png]]
Marco Grawunder 24.2 39
mgrawunder 37.2 40 **Hinweis: Wenn man schon hier ist, kann man auch das Spring-(Boot)-Plugin **installieren
Marco Grawunder 23.1 41
mgrawunder 37.2 42 [[image:1756886220468-891.png]]
mgrawunder 36.2 43
Marco Grawunder 25.2 44 == Server laufen lassen ==
Marco Grawunder 23.1 45
Marco Grawunder 10.2 46 [[image:1755246035428-328.png]]
47
48
Marco Grawunder 25.3 49 ... und ggf. Lombok aktivieren (Man wird nach dem Start der Anwendung gefragt)
Marco Grawunder 10.2 50
Marco Grawunder 23.1 51 [[image:1755246072443-191.png]]
Marco Grawunder 10.2 52
53
Marco Grawunder 23.1 54 [[image:1755246118807-452.png]]
Marco Grawunder 10.2 55
Marco Grawunder 2.2 56
Marco Grawunder 25.3 57 == Logging umstellen ==
Marco Grawunder 3.1 58
Marco Grawunder 23.1 59 [[image:1755246135109-325.png]]
60
61
62 [[image:1755246147827-679.png]]
63
64
65 [[image:1755246162330-595.png]]
66
Marco Grawunder 25.3 67 == Development Profil aktivieren ==
Marco Grawunder 23.1 68
Marco Grawunder 25.3 69 damit dann user1 - user9 angelegt werden und man nicht jedesmal neu registrieren muss
70
Marco Grawunder 23.1 71 [[image:1755246173415-934.png]]
72
73
mgrawunder 37.3 74 **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: **
Marco Grawunder 23.1 75
76 **SPRING_PROFILES_ACTIVE=dev**
77
Marco Grawunder 26.2 78 [[image:1755248752596-839.png]]
79
80
81 == Client starten ==
82
Marco Grawunder 23.1 83 [[image:1755246257400-525.png]]
84
85
86 [[image:1755246212916-883.png]]
87
88
89 [[image:1755246223246-834.png]]
90
91
Marco Grawunder 26.2 92 === Mehrere Instanzen des Clients ermöglichen ===
93
Marco Grawunder 23.1 94 [[image:1755246233218-893.png]]
95
96
Marco Grawunder 27.1 97 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.
Marco Grawunder 26.2 98
Marco Grawunder 23.1 99 [[image:1755246292057-581.png]]
100
Marco Grawunder 30.2 101
102 = Kurzer Blick ins Basisprojekt =
103
104 [[image:1755249096987-249.png]]
105
106
107 [[image:1755249136156-419.png]]
Marco Grawunder 33.1 108
109
110 == Screenshots ==
111
112 [[image:1755249228556-469.png]]
113
114
115 = Kommunikation Client ~-~-> Server =
116
117 [[image:1755249285866-367.png]]
118
Marco Grawunder 34.1 119 Der Server verwendet das REST-Protokoll und als Austauschformat JSON
120
121 = OpenAPI =
122
mgrawunder 37.3 123 (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)
124
Marco Grawunder 34.1 125 [[image:1755250026156-269.png]]
126
127 [[image:1755250050031-304.png]]
128
129 * **Paths**: Endpunkte der API (z.B. /users, /lobbies).
130 * **Operations**: Spezifikation von Methoden wie GET, POST.
131 * **Definitions**: Detaillierte Beschreibung von Eingabe- und Ausgabestrukturen.
132 * **Security**: Authentifizierungsmechanismen.
133
134 [[image:1755250061990-172.png]]
Marco Grawunder 36.1 135
136 Kann JSON oder YAML (Yet Another Markup Language) verwenden YAML ist wie JSON nur mit weniger Klammern
137
138 [[image:1755250157536-746.png]]
mgrawunder 37.3 139
140 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>>https://gitlab.swl.informatik.uni-oldenburg.de/SPB/SWPBasisprojekt2/-/blob/master/openapi.yaml?ref_type=heads]]
141
142 Dort wird die Datei auch grafisch dargestellt.
143
144
mgrawunder 37.4 145 = Maven und OpenAPI =
146
mgrawunder 38.2 147 Die OpenAPI Datei kann verwendet werden, um sich die [[REST-Schnittstellen>>doc:||anchor="HErweiterungderREST-Schnittstelle"]] generieren  zu lassen. Diese Erzeugung erfolgt mit dem OpenAPI Generator [[https:~~/~~/github.com/OpenAPITools/openapi-generator>>https://github.com/OpenAPITools/openapi-generator]]
mgrawunder 37.4 148
mgrawunder 38.2 149 Man kann dabei jetzt einen Kommandozeilenaufruf verwenden oder man setzt auf das im Basisprojekt vorhandene MVN ()
mgrawunder 37.4 150
mgrawunder 38.2 151 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:
mgrawunder 37.4 152
mgrawunder 38.2 153 == Client ==
mgrawunder 38.1 154
mgrawunder 38.2 155 Im Client werden die Apache Http Bibliothek verwendet.
156
157
158 [[image:1756887005209-855.png]]
159
160 == Server ==
161
162 Im Server wird Spring (Boot) verwendet
163
164 [[image:1756887037619-847.png]]
165
166 TODO: Weitere Informationen zu
167
168 - Lombok
169
170 - Dependency Injection
171
172 - Spring (Boot), siehe auch [[https:~~/~~/www.marcobehler.com/guides/spring-framework>>https://www.marcobehler.com/guides/spring-framework]]
173
174
175
mgrawunder 38.1 176 = Erweiterung der REST-Schnittstelle =
177
mgrawunder 38.2 178 In diesem Beispiel wird einmal gezeigt, wie die REST-Schnittstelle des Basisprojektes einfach erweitert werden kann.
179
180 In diesem Beispiel soll die aktuelle Schnittstelle um die Möglichkeit erweitert werden, alle Lobbies vom Server zu bekommen.
181
182 == Schritt 1: Erweitere das OpenAPI-Dokument ==
183
184 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.
185
186 Die Funktion soll sehr einfach sein und keine Parameter verlangen. Dafür bietet sich die GET-Funktion an.
187
188 Im folgenden Bild sind alle Anpassungen zu sehen:
189
190 [[image:1756887436525-790.png||height="355" width="974"]]
191
192
193 Nach dem Speichern, sollte das OpenAPI-Dokument wie folgt aussehen
194
195 [[image:1756887488020-376.png||height="642" width="904"]]
196
197
mgrawunder 38.3 198 Jetzt kann man entweder in IntelliJ
mgrawunder 38.2 199
mgrawunder 38.3 200 [[image:1756888245896-845.png||height="347" width="620"]]
201
202 oder im Terminal (z.B. auch in IntelliJ)
203
204 [[image:1756888279902-777.png||height="637" width="1053"]]
205
206 Wobei hier auch clean compile reichen würde.
207
208 **ACHTUNG! Falls maven Problem macht, kann das auch an einer falschen Java-Version im System liegen (siehe auch [[FAQ>>doc:.Basisprojekt FAQ.WebHome]])**
209
210 Es werden durch den Aufruf neue Inhalte generiert (bzw. die alten überschrieben).
211
212 [[image:1756888428042-802.png||height="538" width="1077"]]
213
214 Hinweis: Niemals Änderungen unterhalb des target-Ordners machen. Das wird von Maven bei clean gelöscht.
215
216 === Wie bekommt man dann aber nun die Funktionalität rein? ===
217
218 Für jeden Endpunkt (also aktuell lobbies und users) werden drei Interfaces/Klassen erzeugt:
219
220 * *Api (z.B, LobbiesApi): Beschreibung der REST-Methoden, vor allem auch das Mapping von z.B. /lobbies/join auf die Methode lobbyJoin(String)
221 * (((
222 *ApiController implements *Api (Für Spring) (z.B. LobbiesApiController)
223 )))
224 * (((
mgrawunder 40.2 225 *ApiDelegate (z.B. LobbiesApiDelegate): Macht die eigentliche Arbeit und muss** im eigenen Code-Bereich** erweitert werden!
mgrawunder 38.3 226 )))
227
228
229
230 == Schritt 2: Erweiterung auf Server-Seite ==
231
232 Da es schon Funktionen für die Lobbies gibt, gibt es auch bereits eine Implementierung, die LobbiesApiDelegate überschreibt
233
234 [[image:1756888762381-912.png||height="48" width="789"]]
235
236 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).
237
238 In der Klasse muss man dann die neue Methode lobbyList aus der API überschreiben.
239
240 [[image:1756888929507-312.png||height="156" width="1161"]]
241
242 Dabei wird folgendes gemacht:
243
244 1. Es wird ein Rückgabeobjekt vom Typ Liste erzeugt
245 1. Es wird über alles Lobbies auf dem Server gegangen (lobbyManagement.getLobbies())
246 1. Da der Client u.U. nicht die vollständigen Informationen über die Lobbies bekommen soll, gibt es zwei unterschiedliche Klassen: ServerLobby und LobbyDTO.
247 1. Die Foreach-Schleife sorgt dafür, dass in das Rückgabeobjekt nur die LobbyDTOs eingefügt werden.
248 1. Dafür wird eine Funktion mit dem Namen lobbyMapping verwendet
249 1. Schließlich wird am Ende gesagt, dass alles ok ist und eine Antwort ResponseEntity.ok mit dem Rückgabeobjekt (lobbies) gesendet.
250
mgrawunder 38.4 251 **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
mgrawunder 38.5 252
253 Auf Server-Seite fehlt jetzt noch die Methode getLobbies im LobbyManagement
254
255 [[image:1756889590500-656.png||height="81" width="518"]]
256
257
258 === LobbyMapping ===
259
260 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!!)
261
262 Also z.B.
263
264 [[image:1756889440395-856.png]]
265
266 und definiert ein Interface mit einer Annotation
267
268 [[image:1756889472103-847.png]]
269
270 und damit kann man die Funktion aufrufen. Hinweis: Der Mapper ist im LobbyService über die Spring Dependency Injection gebunden.
mgrawunder 39.1 271
272 == Schritt 3: Erweiterung auf Client-Seite (Java) ==
273
274 Hinweis: Das Beispiel bezieht sich hier auf eine Client mit Java. Für andere Clients wie Angular ist das Vorgehen anders.
275
276 Auf der Client-Seite wird die komplette Kommunikation mit dem Server in der generierten Klasse DefaultApi gekapselt.
277
278 [[image:1756889795622-530.png]]
279
280 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
281
282 Im Client gibt es auch eine Klasse LobbyService. Dort ist die DefaultApi Klasse über Dependency Injection gebunden.
283
284 [[image:1756889917681-650.png]]
285
286 Dort kann man nun eine neue Methode getLobbies() integrieren:
287
288 [[image:1756889979252-910.png]]
289
290 Und das Ganze dann z.B. im MainMenuPresenter verwenden:
291
292 [[image:1756890010118-149.png||height="116" width="972"]]
293
294 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.
295
mgrawunder 40.2 296
297 = Kommunikation: Server ~-~-> Client (WebSockets) =
298
299 [[image:1756890800817-370.png||height="604" width="1121"]]
300
mgrawunder 51.2 301 Da man mit REST nicht Nachrichten vom Server an den Client schicken kann, werden im Basisprojekt dafür WebSockets verwendet.
302
303 Spring bietet eine native Unterstützung von WebSockets. Für eigene Funktionen kann man sich in die Kommunikation über die Serverklasse WebSocketHandler einklinken
304
305
306 [[image:1756890924024-346.png||height="377" width="1092"]]
307
308
309 Sobald sich jemand beim Server für WebSockets angemeldet hat wird von Spring ein org.springframework.web.socket.messaging.SessionConnectedEvent
310 geworfen, welches in der folgenden Methode (im WebSocketHandler) aufgefangen wird
311
312 [[image:1756890958794-603.png||height="373" width="1087"]]
313
314 Die Methode ist Observer für das Event SessionConnectedEvent
315
316 Der WebSocketServer kennt die Nutzer und erlaubt das Einloggen nur, wenn Login und Passwort stimmen (durch Spring Security)
317
318 [[image:1756891019715-621.png]]
319
320 1)Aus dem Event kann der Nutzer gelesen werden (der sollte nie leer sein)
321
322 2) Dann wird sich aus dem Repository (später mehr) der Nutzer geholt, der durch den Namen identifiziert ist (z.B. „test1“)
323
324 3) Schließlich werden allen anderen darüber informiert, dass ein neuer Nutzer da ist
325
326 == STOMP ==
327
328 WebSockets haben kein Protokoll (wie z.B. http)
329
330 Es können entweder binäre oder textuelle Daten verarbeitet werden (die jeweiligen Gegenstellen müssen das wissen!)
331
332 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)
333
334 STOMP: Streaming Text Oriented Messaging Protocol
335
336 Definiert ein einfaches Protokoll, welches es erlaubt, sinnvoll über WebSockets zu kommunizieren
337
338 Ist ein Teil von Spring
339
340 Methoden sind z.B. CONNECT, SEND oder SUBSCRIBE
341
342 STOMP arbeitet mit Topics
343
344 Ein Client registriert (SUBSCRIBE) sich für bestimmte Ereignistypen
345
346
347 §Wenn auf der Server-Seite dieser Typ veröffentlich wird dann wird dies an die jeweils interessierten Clients geschickt
348
349 Publish/Subscribe-Pattern
350
351 [[https:~~/~~/docs.spring.io/spring-framework/reference/web/websocket/stomp.html>>url:https://docs.spring.io/spring-framework/reference/web/websocket/stomp.html]]
352
353
354 §Der Server definiert unterschiedliche Topics (je nach Modul)
355
356 §Beim Nutzermanagement aktuell:
357
358 §/topic/users/loggedIn: Es hat sich ein neuer Nutzer angemeldet
359
360 §/topic/users/loggedOut: Ein Nutzer hat sich ausgeloggt
361
362
363 [[image:1756891125969-748.png||height="317" width="726"]]
364
365 §Topic-Namen sind Strings, sollte aber Aufbau von oben entsprechen
366
367 §In der Lobby würde es stattdessen /topic/lobbies/* heißen
368
369 == WebSockets: Versenden von Nachrichten ==
370
371 [[image:1756891180516-843.png||height="426" width="801"]]
372
373 [[image:1756891216134-578.png||height="428" width="699"]]
374
375 == Nachrichteninhalt ==
376
377 [[image:1756891254830-647.png||height="101" width="777"]]
378
379 * message kann grundsätzlich alles sein, was serialisiert werden kann
380 * Man könnte nun einfach die Java-Serialisierung verwenden (im alten Basisprojekt ist das auch so)
381 * Das hat aber eine Reihe von Nachteilen
382 ** 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
383 ** Es gibt eine Reihe von Sicherheitsproblemen
384 * Besser: Definiere ein gemeinsames Austauschformat, was viele verstehen ~-~-> Im Basisprojekt (und in vielen anderen Projekten auch) JSON verwenden
385 * Insbesondere Web-Clients (JavaScript) bieten hervorragende Möglichkeiten, an JSON zu verarbeiten
386 * Client und Server haben sich damit auf Format für den Austausch geeinigt
387 ** Topic: Strings
388 ** Message: JSON
389
390 An den Clilent werden auch bei WebSockets nur DTOs verschickt! (userMapping)
391
392 [[image:1756891375095-158.png||height="266" width="775"]]
393
394 == Wie verbindet sich ein Client mit dem Server? ==
395
396 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.
397
398 [[image:1756891512330-186.png||height="170" width="820"]]
399
400 === Auf Server Seite (WebSocketConnectionManager): ===
401
402 [[image:1756891551794-161.png||height="387" width="1019"]]
403
404 1) Variablen definieren
405
406 2) WebSocketClient erzeugen
407
408 3) Daraus WebSocketStompClient machen
409
410 4) Jackson als Mapper definieren (DTO-Object <-> JSON)
411
412 === [[image:1756891617399-232.png||height="289" width="1006"]] ===
413
414
415 1) Asynchron die Verbindung zum Server aufbauen
416
417 2) Wenn erfolgreich in das Hauptmenü wechseln (showScene à später mehr)
418
419 3) Über den Kontext ein Event pushen LoggedInEvent
420
421 4) Jede Serververbindung hat eine Session