You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 10 Current »

Inhaltsverzeichnis

Einleitung

In diesem Dokument wird anhand der Dokumentation des Basisprojekts ein Beispiel für die Dokumentation eines Softwareprojekts gegeben. Das Ziel des Dokuments ist es, das Basisprojekt zum Stand des Jahres 2022 zu dokumentieren, und damit eine Orientierung bei der Erstellung der Dokumentation für zukünftige Projektgruppe zu bieten.

Die Dokumentation unterteilt sich in mehrere Abschnitte. Im Kapitel Analyse wird die Vision für das Basisprojekt dargelegt und die Anforderungen an das Basisprojekt herausgearbeitet. Dem folgt das Kapitel Umsetzung, in dem die tatsächliche Implementierung beschrieben wird. Im nächsten Kapitel werden die Tests dokumentiert. 

Analyse

In diesem Kapitel wird zunächst die Vision des Basisprojekts beschrieben und danach die Anforderungsanalyse mit Userstories und Akzeptanzkriterien dargestellt.

Vision

Im "echten Leben" findet Softwareentwicklung praktisch nie mehr auf der grünen Wiese statt, sondern es gibt vorherige Projekte und Prototypen auf die aufgebaut werden kann, und Frameworks die mit eingebunden werden können. Deshalb gibt es auch ein Basisprojekt für das SWP auf dem Aufgebaut werden kann. Das Basissystem soll den Studierenden den Einstieg in das SWP erleichtern, indem es Vorgaben zur Softwarearchitektur macht und Beispiele liefert, wie Dinge anforderungskonform gemacht werden können. Gleichzeitig ist und soll das Basisprojekt nicht perfekt sein, sondern es enthält auch Fehler und Sachen die verbessert werden können, sodass die Studierenden lernen auch fremden Code zu verstehen und zu verbessern und ihren Bedürfnissen anzupassen. Für den Anfang soll das Basisprojekt allerdings ohne Anpassungen funktionieren, um einen sauberen Einstieg in das SWP zu ermöglichen.

Damit das System als Basis dienen kann, müssen einige Systeme implementiert werden und Entscheidungen über die Softwarearchitektur getroffen werden. Es soll eine klare Trennung zwischen Client mit Nutzerinteraktion und Server mit Spiellogik geben. Die Kommunikation mit zwischen den Komponenten erfolgt mit geteilten Klassen geben (Common). Auf dem Client sollen Login, Registrierung und ein rudimentäres Hauptmenü als Orientierung dienen. Auf dem Server soll eine dementsprechende Nutzerverwaltung vorhanden sein. Damit die beiden Komponenten zusammenarbeiten können, muss auch schon die Netzwerkkommunikation implementiert sein.

Das System soll möglichst einfach zu erweitern sein, da es nur als Fundament für die Studierendenprojekte dient und von ihnen erweitert werden muss. Dies soll unter anderem durch eine möglichst geringe Abhängigkeit zwischen einzelnen Komponenten erreicht werden. 

Daher soll die Architektur des Basissystems Ereignisgetrieben sein, sodass der grundlegende Ablauf der Anwendung durch Ereignisse gesteuert ist. Dabei soll der Informationsfluss und das Interface in Nachrichten eingekapselt werden. Alle Komponenten, für die die Information der Nachricht relevant ist, fangen sie ab und reagieren entsprechend. Damit wird das Observerpattern umgesetzt und es muss kein aktives warten stattfinden.

Der Client soll nach dem Model-View-Presenter-Pattern aufgebaut werden, um möglichst erweiter- und testbar zu sein. Dabei wird zwischen den Komponenten Modell, View und Presenter unterscheiden. Das Modell ist die eigentliche Anwendung. Der View ist das, was der Betrachter sieht, und sehr "dumm". Der Presenter ist der Mittler zwischen beiden Komponenten, und übersetzt beispielsweise Klicks auf dem View in Nachrichten an das Modell, und Nachrichten im Modell in Dinge die der View anzeigen soll. Dadurch soll der View austauschbar sein, da er nichts vom Presenter weiß.

Weiterhin soll eine klare und saubere Klassenhierarchie eine gute Erweiterbarkeit ermöglichen.

Anforderungsanalyse

In diesem Abschnitt werden aus der Vision konkrete Ziele und Anforderungen abgeleitet. Da in der Entwicklung des Basissystems das Scrumverfahren genutzt wurde, sind die Anforderungen als Userstories formuliert.

Userstories

Login:

Als registrierte:r Nutze:r möchte ich mich beim Spiel einloggen können, damit ich mit anderen Nutzer:innen chatten kann.
Akzeptanzkriterien:
Die Nutzer:in wird bei einem Fehlschlag informiert.
Die Nutzerin wird bei Erfolg in das Hauptmenü geleitet.
Alle anderen Nutzer:innen sollen sehen können, dass sich jemand neues angemeldet hat.
Im Loginformular werden Nickname und Passwort abgefragt.

Registrierung

Als Nutzer:in möchte ich mich vor dem Einloggen registrieren, um einen eindeutigen und persistenten Account zu erhalten.
Akzeptanzkriterien:
Die Nutzer:in wird bei einem Fehlschlag informiert.
Im Formular werden Nickname und Passwort abgefragt.
Das Passwort muss doppelt angegeben werden, um Fehler zu vermeiden.
Der Account wird bei Erfolg der Registrierung gespeichert.
Die Nutzerin kann sich nach der Registrierung einloggen.

UI

Als Nutzer:in möchte ich, dass die Menügestaltung möglichst einheitlich ist, damit ich mich immer einfach zurechtfinden kann.

Umsetzung

Zunächst werden die drei Module des Projekts dargestellt, anschließend werden Ereignisse und die Registrierung sowie der Login erläutert.    
    
Das Basissystem gliedert sich in drei Module: Server, Client und Common. Client ist der Bestandteil, der das Userinterface enthält und auf den Systemen der Spieler läuft. Der Server ist der Bestandteil, der die Nutzer- und Spielverwaltung enthält. Common enthält zwischen Server und Client geteilte Klassen. Da es sich bei Client und Server um zwei Anwendungen handelt und um Abhängigkeiten zu reduzieren, dürfen in Client und Server nur Abhängigkeiten zu Common und nicht zur anderen Anwendung bestehen. Alle geteilten Klassen müssen sich in Common befinden.

Client

Der Client beinhaltet alle ausschließlich clientseitige Klassen, Interfaces und Ressourcen. Die Aufgabe des Clients ist die Präsentation des Spiels und der umgebenden Serverlogik.

Der Client wurde nach dem Model-View-Presenter-Pattern implementiert. In diesem Pattern wird zwischen drei Komponenten unterschieden: das Modell mit der dahinterliegenden Logik; der View, der die zugrundeliegende Logik dem/der Nutzer:in graphisch darstellt; und dem Presenter, der zwischen den beiden vermittelt. Dabei soll der View möglichst "dumm" gehalten werden, sodass die Logik möglichst vollständig vom Model gekapselt werden kann. Ziel dieses Patterns ist es, dass der View ausgetauscht werden kann, ohne dass das Model ausgetauscht werden muss.

Server 

Der Server enthält die Nutzer- und Spielverwaltung und später die Spiellogik.

Common

Das Modul Common enthält Klassen die sich Client und Service teilen. Hauptsächlich sind dies Messages und DataTransferObjects (DTO).       

Bei den Messages gibt es drei Arten: RequestMessages, ResponseMessages, ServerMessages. RequestMessages werden von einem Client zum Server geschickt. ResponseMessages sind die Antworten des Servers auf eine spezifische RequestMessage. ServerMessages werden vom Server an einen oder mehrere Clients geschickt. Sie sind nicht notwendigerweise eine Antwort auf eine Request.
        
Die DTO dienen zur Übertragung von größeren Datenobjekten. Ein Beispiel hierfür ist das UserDTO, mit dem der Inhalt von User-Objekten zwischen Client und Server transportiert werden kann, ohne dass das client- oder serverseitige Verhalten der Klasse mitgeliefert werden.

Ereignisgetrieben

Die Kommunikation zwischen Client und Server wie auch die interne Kommunikation erfolgt ereignisgetrieben. Dabei handelt es sich um eine Umsetzung des Observerpatterns. Verschiedene Bestandteile, die miteinander kommunizieren, enthalten einen Eventbus von Google Guava (https://github.com/google/guava/wiki/EventBusExplained), über den die Kommunikation läuft. Dabei muss es sich in allen Bestandteilen, die miteinander kommunizieren sollen, um das gleiche Eventbus-Objekt handeln. Bestandteile, die das Eventbus-Objekt enthalten, können mit der Methode eventBus.post(Object msg) eine Nachrichten-Objekt msg auf den Eventbus legen. Damit andere Bestandteile auf diese Nachricht reagieren können, müssen sie bei dem Eventbus als Listener registriert sein.
Dies geschieht mit der Methode eventBus.register(Object listener). Die Reaktion auf eine Nachricht msg der Klasse Type erfolgt mit einer Methode mit einem Parameter der Klasse Type oder einer Oberklasse davon. Der Name so einer Methode ist in der Regel onType(Type msg) - also on + Klassenname. Diese Methode muss mit der Annotation @Subscribe versehen sein.

Die ereignisgetriebene Architektur ermöglicht es, die Netzwerkkommunikation und die Verarbeitung von, von Nutzer:innen erzeugten Ereignissen, asynchron und ohne Blockaden oder Busy Waiting zu gestalten. Außerdem müssen nicht ein Ereignisse auslösenden Komponenten, nicht alle Komponenten, die das Ereignis weiterverarbeiten, kennen und aufrufen. Dadurch werden Abhängigkeiten reduziert, allerdings lässt sich durch die Betrachtung der Codestelle, die ein Ereignis auslöst, nicht sagen, welche Komponenten darauf reagieren.  

Komponenten 

Server

Der Server lässt sich momentan in drei Hauptkomponenten aufteilen, die den Unterpackages des Servers entsprechenden.

In den meisten Komponenten gibt es mindesten eine spezialisierte Klasse, die Service heißt. Diese Klasse übernimmt das Anfangen von Requests und absenden von ResponseMessages.

Kommunikation übernimmt das Senden und Empfangen von Nachrichten zu den Clients.

Das Usermanagement übernimmt die Nutzerverwaltung. Im Usermanagement übernimmt der UserStore, die Speicherung der User:innen. Im Moment ist mit dem MainMemoryUserStore nur ein UserStore implementiert, der die Nutzer im RAM vorhält und nicht persistent speichern kann. Das Usermanagement stellt Methoden, zum Ein- und Ausloggen, zum Erstellen und Löschen, zum Updaten und zum Abfragen der gerade eingeloggten User:innen bereit.

Das Lobbymanagement übernimmt die Verwaltung der Lobbies. Es stellt Methoden zum Erzeugen und Löschen sowie zur Suche von Lobbies nach Namen bereit.

Client

Die Aufteilung im Client ist etwas anders, hier gibt es die Login-, Registration- und MainMenuPresenter und zugehörige Events. Daneben gibt es einen Client und Lobbyservice, um Request an den Server zu senden, und den SceneManager, der die Views und Presenter initialisiert. Daneben fängt der SceneManager verschieden ShowScene-Events ab, woraufhin er dann die spezifizierte Szene mit dem entsprechenden View anzeigt.

Login: 

In diesem Abschnitt wird die Funktionsweise des Logins beschrieben, und die oben beschriebenen Konzepte noch einmal in ihrer Anwendung im Loginprozess aufgezeigt. Die Beschreibung ist deshalb ausführlicher als bei anderen Abschnitten.

Bevor registrierte User:innen am Spiel teilnehmen können, müssen sie sich zunächst einloggen. Das Loginformular ist das erste was ein:e User:in sieht, wenn der Client gestartet wird. Es besteht wie im Bild zu sehen aus zwei Textfeldern, eines für den Nickname und eines für das Passwort. Daneben gibt es zwei Buttons einen, um den Login zu bestätigen, und ein anderen der zum Regsitrierungsdialog führt.

Im Regelfall gibt der/die Nutzer:in die entsprechenden Daten in die Testfelder ein und klickt den Login-Button. Nach dem Login Model-View-Presenter-Pattern löst diese Aktion im View, der durch die LoginView.fxml beschrieben ist, die Methode onLoginButtonPressed im LoginPresenter, die Username und Passwort an das Model im UserService vermittelt. Durch den UserService wird eine LoginRequest mit Username und Passwort erstellt und auf den Eventbus gepostet. Da die LoginRequest der Kommunikation zwischen Client und Server dient und von beiden Modulen benutzt wird, ist sie Teil des Commmon-Moduls. In der Client-Anwendung subscribet die Methode onRequestMessage in der ClientConnection-Klasse auf alle RequestMessages.
Diese Methode fängt die gepostete LoginRequest ab und sendet sie, wenn es eine Verbindung gibt, an den Server. Durch die Nutzung des Eventbuses gibt es in diesem Fall keine Abhängigkeit von UserService und ClientConnection wo der UserService die ClientConnection kennt und sich darum kümmern muss, dass die RequestMessage an die korrekte Methode übergeben wird. Am Server wird die RequestMessage von der Methode process im ServerHandler empfangen, und auf den Eventbus des Servers gepostet. Hier fängt der AuthenticationService die LoginRequest ab, und versucht den/die User:in beim UserManagement einzuloggen. Das Einloggen im UserManagement ist erfolgreich, wenn es eine:n User:in  mit entsprechendem Namen und Passwort gibt. Wenn das Einloggen funktioniert, wird eine ClientAuthorizedMessage und ansonsten eine ServerExceptionMessage erstellt und auf den Eventbus gepostet. Diese Messages werden wiederum im ServerHandler abgefangen. Hier wird eine aufgrund der ClientAuthorizedMessage eine LoginSuccessfulResponse und den Client, der sich eingeloggt hat geschickt, und eine UserLoggedInMessage an alle Clients gesendet.

Die Nachrichten werden in den Clients abgefangen und auf den Eventbus gepostet. Die LoginSuccesfulResponse löst in der ClientApp aus, dass durch den SceneManager das Hauptmenü gezeigt wird, und im MainMenuPresenter, dass alle zur Zeit angemeldeten User:innen angezeigt werden. Die UserLoggedInMessage sorgt dafür, dass in anderen Clients, der/die gerade neu eingeloggte User:in zu dieser Liste hinzugefügt wird.

Registrierung:

Bevor User:innen sich einloggen können, müssen sie sich registrieren. Der Registrierungsdialog besteht aus drei Textfeldern, eines für den Nickname und zwei für das Passwort. Daneben gibt es zwei Buttons, "Register", um die Registrierung zu bestätigen, und "Cancel", um sie abzubrechen und den Dialog zu verlassen.

Beim Registrieren wird überprüft, dass tatsächlich ein Nickname und ein Passwort angegeben wurden, und dass das Passwort korrekt wiederholt wurde. Außerdem darf nicht schon ein User mit diesem Username existieren. 

Hauptmenü

Das Hauptmenü besteht aus eine Liste mit den zur Zeit angemeldeten User:innen und zwei Buttons: "Create Lobby" und "Join Lobby".

Testdokumentation

Im Basisprojekt wurde der geschriebene Code mithilfe von automatisierten Unittests getestet. In diesem Kapitel wird das Vorgehen bei den Tests, welche Tests es gibt und was sie abdecken, beschrieben.

Vorgehen

Zum Testen wurde das Framework "JUnit" genutzt.  Dabei orientieren sich die Tests in der Regel am Konzept "Arrange, Act, Assert". Hierbei werden zunächst alle benötigten Objekte initialisiert und in den richtigen Zustand gebracht. Dazu kann es Hilfsmethoden geben, insbesondere wenn dieselbe Konfiguration öfter benötigt wird. Nach dem "Arrange"-Schritt wird die zu testende Aktion ausgeführt. Mittels Asserts wird daraufhin überprüft, ob die getestete Aktion tatsächlich die erwarteten Auswirkungen hatte.

Die GUI konnte auf diese Weise allerdings nicht getestet werden. Hierfür wurde die Funktionsweise der GUI immer wieder manuell überprüft.  

Was wurde getestet?

Client

Im Client gibt es eine Testabdeckung von 7 % der Codezeilen und 10 % der Methoden. Es wurden Tests für das RegistrationErrorEvent und den UserService geschrieben.

Common

Die Tests im Common decken 36 % der Methoden und 50 % der Codezeilen ab. Es wurden Tests für die DTOs geschrieben und welche, die die (de)serialisierbarkeit der Messages überprüfen.

Server

Im Server decken die Tests 31 % der Methoden und 32 % der Codezeilen ab. Die Tests beschränken sich auf die Klassen des UserManagements.

Ausblick

Die grundlegenden Funktionen eine Client-Server-Architektur und einer Nutzerverwaltung wurden im Basisprojekt umgesetzt. Diese Basis kann jetzt für die Implementierung beliebiger (Brett-)spiele genutzt werden. Allerdings fehlen auch noch einige Funktionen der Nutzerverwaltung wie das Logout von Nutzer:innen. Daneben gibt es schon ein einfaches Lobbysystem, dass aber momentan noch nicht im Client aufrufbar ist. Vor einer Brettspielimplementierung müssten diese beiden Probleme noch behoben werden.

Neben der Brettspielimplementierung ist auch es auch möglich auf Grundlage des Basissystems eine Chatanwendung zu entwickeln. Dies ließe sich auch mit der Brettspielimplementierung kombinieren.

Anhang

Klassendiagramme der Module

Client

Common

Server




  • No labels