Files
pm/Diplomarbeitsbuch-individueller-teil-Konstantin-Hintermayer.md
2026-03-08 15:42:16 +00:00

234 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
include_toc: true
gitea: none
---
## 1. Einleitung des individuellen Teils
In diesem Abschnitt wird meine persönliche Aufgabenstellung im Rahmen des Projektes (`Alumnihub`) beschrieben.
### Auftrag / persönliche Aufgabenstellungen
Meine Zuständigkeiten und Verantwortlichkeiten:
- Product Owner
- Infrastruktur
- Entwicklung
- Auswertungen
- Schwarzes Brett
### Motivation
Lernen von ASP.NET und der Entwicklung mit Blazor und Oqtane. Ich habe Interesse an dem Thema Webentwicklung. Privat entwickle ich schon seit Jahren viel mit React.JS.
## 2. Anforderungen an das entwickelte Modul bzw. die Funktionalität
- funktionale / nichtfunktionale Anforderungen
- Use Cases
## 3. Technische Grundlagen
Mein Aufgabenbereich umfasst einerseits die Entwicklung eigener Module, sowie das Bereitstellen des Services. Als Betriebssystem habe ich mich für Linux entschieden, einfach, da ich mit Linux im Serverumfeld die meisten und besten Erfahrungen gemacht habe.
(Diese Entscheidung wurde gemeinsam getroffen:)
Auch steht die Wahl der Programmiersprache und des CMS an. Nachdem wir im Unterricht fast ausschließlich mit C# entwickelt haben und nicht in eine komplett unbekannte Entwicklungsumgebung abdriften wollten, haben wir uns für Webentwicklung mit ASP.NET Core 9 und (Upgrade im Lauf der Diplomarbeit auf .NET Core 10) dem CMS Oqtane entschieden. Auch hier gab es einige Kandidaten:
- Piranha CMS
>Piranha erscheint auf den ersten Blick nicht so flexibel wie Oqtane, basiert auf .NET 8.0 und wird nicht so aktiv gewartet.
- Umbraco
>Viel Arbeit mit Partials, welche in der Admin Oberfläche geschieht, aber sehr gut dokumentiert. Im großen und ganzen wirkt Umbraco nicht so flexibel.
- DNN / Dot Net Nuke
>Platzhirsch. Kennt man, wird von der DNN Foundation gewartet. Arbeitet mit dem Dotnet Framework, welches nicht unter Linux läuft. Und ein Windows Server würde ich ich nicht einfach so ins Internet, abgesehen von den Lizenzkosten, die das kosten würde.
- Oqtane
>Schlecht dokumentiert, auf den ersten Blick sehr modular und flexibel.
Am Ende haben wir uns für das Oqtane Framework trotz seiner schlechten Dokumentation entschieden.
Im Bereich der Datenbanken musste ich mir ein paar Fragen stellen.
1. Auf welche Art Datenbank setzen wir? SQL, NoSQL, Graph, ...
2. Mit welcher speziellen implementiereung bekommen wir Support und haben Wissen im Team?
3. Ist das auserkorene System kompatibel mit dem CMS auf dem wir aufbauen?
Es war von Anfang an klar, dass es ein SQL basiertes System wird, da wir im Team nur mit SQL-basierten Systemen erfahrungen haben. Außerdem unterstützt unser CMS (Oqtane) nur SQL basierte Systeme. In der Linuxwelt kommen jetzt nur noch ein paar Datenbanken in die Auswahl: PostgreSQL, MySQL / MariaDB, SQLite. Da ist die Wahl auf PostgreSQL gefallen. Grund dafür war meine Vorerfahrung mit diesem DBMS, welche ich im Nebenjob errungen habe.
# Technologie
## Entwicklung mit ASP.NET (Was ist Blazor? / Was ist Razor? / Kestrel)
## Was ist Oqtane? Architektur von Oqtane?
Oqtane ist ein Framework und CMS zur Entwicklung von Webseiten mithilfe von ASP.NET und Blazor. [^5] Ein Oqtane-System besteht aus mehreren Komponenten.
In dieser Diplomarbeit fokussieren wir uns hauptsächlich auf `Themes` und `Modules`, aber es gibt auch `Language Packs` und `Pure Extensions`. [^6]
Ein `Module` (Modul) soll neue Funktionalitäten in das CMS hinzufügen und ein `Theme` soll die ganze Gestaltung der Website (die Shell) festlegen. [^6]
[^5]: https://www.oqtane.org/#about
[^6]: https://docs.oqtane.org/dev/extensions/index.html
### Architektur eines Moduls
Ein Modul in Oqtane besteht aus 4 Projekten. Server, Client, Shared und Package.
Im Server-Projekt liegt Sourcecode, welcher serverseitig ausgeführt werden soll. In der Praxis bedeutet das: alle Repositories, Controller, Manager, Migrationen und Server-Services (entwickelt nach einem Interface definiert im Client) und Server-Startuplogik.
Im Client-Projekt liegen Code und Razor-Komponenten für den Client. Also Client-Staruplogik, Client-Services (+ Inferfaces dafür, die Services hier sollen lediglich die Server-Services über HTTP aufrufen), Ressourcendateien (.resx), die Komponenten / das User Interface und die Moduldefinitionen für jedes Modul.
Im Shared-Projekt wird geteilter Sourcecode abgelegt, der server- und clientseitig verwendet wird. In der Praxis bleibt es hierbei bei den EntityFramework-Modellen zum Speichern der Daten im Arbeitsspeicher.
Im Package Projekt findet man Skripte zum Debuggen und Releasen eines Moduls. Und die NuGet-Spezifikation.
- Beim Debug werden die DLLs, PDBs und statischen Assets wie Skripte und Stylesheets der 3 anderen Projekte in den bereits gebauten Oqtane.Server `oqtane.framework/oqtane.server/bin/debug/net10.0/...` kopiert.
- Beim Release wird ein NuGet-Paket erstellt und unter oqtane.framework/oqtane.server/Packages abgelegt. Dort abgelegte NuGet-Pakete werden beim nächsten Start des Oqtane Servers installiert (DB Migrationen werden gemacht und die Pakete entpackt).
## Systemarchitektur (Postgres / Oqtane / Nginx )
```mermaid
architecture-beta
group server(server)[Server]
service db(database)[PostgreSQL] in server
service oqtane(server)[Oqtane] in server
service nginx(server)[NginX] in server
service internet(cloud)[Internet]
internet:R <--> L:nginx
nginx:R <--> L:oqtane
oqtane:R <--> L:db
```
## Websockets und HTTP 1.1
## Dependency injection
### Dependency Inversion Principle [^1]
Das Dependency-Inversion-Principle (DIP / auf Deutsch: Abhängigkeits-Umkehr-Prinzip) ist eines von den fünf `SOLID` Prinzipien in der Softwareentwicklung.
Das DIP unterscheidet zwischen high-level und low-level Modulen.
- Die High-Level-Module beschreiben die Applikations- / Businesslogik, ohne direkt mit den Low-Level-Modulen zu interagieren, sondern lediglich auf Abstraktionen. [^3]
- Die Abstraktionen sollen nicht von Implementierungsdetails abhängig sein, sondern die Low-Level-Implementierung sollen gemäß der Abstraktionsschicht implementiert werden. [^3]
Ausgangslage ist eine Softwarearchitektur im Direct-Dependency-Graph-Modell.
```mermaid
architecture-beta
service a(mdi:package-variant-closed)[Klasse A]
service b(mdi:package-variant-closed)[Klasse B]
a:R --> L:b
```
Bei diesem Beispiel ist die Klasse A ein high-level Modul, welches direkt auf die Klasse B referenziert, was das DI-Prinzip verbietet.
Das Problem dabei: Die einzelnen Klassen sind eng gekoppelt, was das Austauschen von B mit einer anderen Klasse unmöglich macht. Genau dieses Problem wird vom DIP gelöst.
```mermaid
architecture-beta
service a(mdi:package-variant-closed)[Klasse A]
service b(mdi:package-variant-closed)[Klasse B]
service ib(mdi:car-clutch)[Interface B]
a:B --> T:ib
ib:R <-- L:b
```
Das High-Level-Modul ruft lediglich eine Abstraktion eines Low-Level-Moduls auf, welche von einem, oder mehreren Low-Level-Modulen implementiert wurde. Für das High-Level-Modul ist es hier egal, welches Low-Level-Modul die Implementierung bereitstellt. Dadurch erhält man einen viel modulareren Aufbau in der Software. Die einzelnen Module sind auch leichter austauschbar und testbar. Genau diese Modularität macht Dependency Injection möglich.
### Microsoft Dependency Injection Framework
Dependency Injektion ist in .NET genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. [^4]
Alle Dependencies werden in einem `Service-Container` zur Verwaltung registriert. .NET hat einen eingebauten `Service-Container` (eine Implementierung des `IServiceProvider`). [^4]
Das Dependency Injection Framework verwaltet alle Instanzen. Nach Bedarf werden Instanzen erstellt, oder wieder entsorgt (sofern das Service nicht mehr gebraucht wird). Beim Instanziieren einer Klasse werden alle im Konstruktor erwarteten Dependencies bereitgestellt, bzw. selbst instanziiert und dannach bereitgestellt. [^4]
Hier ein Beispiel aus der Dokumentation von Microsoft: [^4]
```c#
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
builder.Services.AddSingleton<IMessageWriter, MessageWriter>();
using IHost host = builder.Build();
host.Run();
public class MessageWriter : IMessageWriter
{
public void Write(string message)
{
Console.WriteLine($"MessageWriter.Write(message: \"{message}\")");
}
}
public interface IMessageWriter
{
void Write(string message);
}
public sealed class Worker(IMessageWriter messageWriter) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
messageWriter.Write($"Worker running at: {DateTimeOffset.Now}");
await Task.Delay(1_000, stoppingToken);
}
}
}
```
Das ist ein simples Beispiel, welches Teile des DI Frameworks zeigt. Wir haben einen Service (Klasse Worker), ein Dependency (Klasse MessageWriter) und eine Abstraktionsebene, von dem Dependency (Interface IMessageWriter).
Bei Programmstart wird zuerst manuell der `Service-Container` erstellt, dannach alle Module registriert (entweder als HostedService, oder als Modul mit einer spezifischen Lifetime (Scoped, Transient, Singleton)).
Mit dem Aufruf von `builder.Build()` wird intern ein Dependency Graph erstellt und mit `host.Run()` wird versucht die Klasse Worker zu instanziieren und zu starten. Nachdem Worker ein Dependency auf IMessageWriter hat, wird über den zuvor erstellten Dependency-Graph die Implementierung von IMessageWriter gesucht. Jetzt wird MessageWriter instanziiert und dem Konstruktor von Worker übergeben, damit seine Dependencies befriedigt werden.
So sieht der Abhängigkeitsgraph bei diesem Beispiel aus.
```mermaid
architecture-beta
service a(mdi:package-variant-closed)[Worker]
service b(mdi:package-variant-closed)[MessageWriter]
service ib(mdi:car-clutch)[IMessageWriter]
a:R --> L:ib
ib:R <-- L:b
```
[^1]: https://learn.microsoft.com/en-us/dotnet/architecture/modern-web-apps-azure/architectural-principles#dependency-inversion
[^2]: https://blog.logrocket.com/dependency-inversion-principle/
[^3]: https://www.oodesign.com/dependency-inversion-principle
[^4]: https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection/overview
# Continuous Integration
## Automatisierter Build und Release Prozess mithilfe von Gitea Actions.
Gitea, das Versionskontrollsystem dieser Diplomarbeit, hat einen Continuous-Integration-System eingebaut. Im Kern ist es baugleich zu den GitHub-Pipelines. Man kann im `.gitea/workflow` Ordner `.yml` Dateien ablegen, welche dann das Verhalten der Workflows definieren.
Man kann definieren auf welcher Änderung im Git Repository die Pipeline losgetreten wird (Keyword: `on`) und entweder eigene Kommandos aufreihen, oder auf bestehende `actions` zurückgreifen, welche dann der Reihe nach ausgeführt werden (Keyword: `jobs`).
Die meisten Pipelines sind folgendermaßen Aufgebaut:
Clone -> Checkout -> Submodule Checkout (optional) -> Dependencies einrichten (zum Beispiel das dotnet SDK) -> Build ausführen. -> Release erstellen und Artefakte veröffentlichen (z.B. in Registries). Aber man kann auch andere Dinge tun, z.B. mithilfe von Künstlicher Intelligenz Code und Dokumentation überprüfen.
Anwendungen von Gitea Actions bei dieser Diplomarbeit:
- APT-Package Repository:
> Zum Bauen von Oqtane und allen Modulen, verpacken in ein .deb Paket und in die Registry pushen.
- Interfaces Projekt
> Zum Bauen vom Interfaces-Projekt, verpacken in ein NuGet Paket und in die Registry pushen.
- ursprünglich: oqtane.framework
> Zum bauen und Verpacken in einen Docker Container und in die Registry pushen.
- PM Repository:
> Zum automatischen Überprüfen der Dokumente, unter anderem, mithilfe von KI, wie zum Beispiel Gemini.
# Projektmanagement
## Scrum
## YouTrack
## Gitea (mit Issues)
## Git
## Kommunikation
# Learnings
## Probleme mit Oqtane
## Arbeitszeiteinschätzung (Zeitverzug)
## Teamleitung (Motivation / Downsizing)
## Produktion != Staging
## Sprints und Meetings (in Zukunft ja asynchron)
# Modules
## Mass Mailer
## Event Registration
## Schwarzes Brett