Update: Quellenverzeichnis

This commit is contained in:
2026-03-19 14:36:12 +01:00
parent e4cf3cb00d
commit aad23ca449
6 changed files with 336 additions and 176 deletions

View File

@@ -1,7 +1,4 @@
---
include_toc: true
gitea: none
---
# Konstantin Hintermayer
## 1. Einleitung des individuellen Teils
@@ -67,11 +64,11 @@ Es war von Anfang an klar, dass es ein SQL-basiertes System wird, da wir im Team
### 3.2 Beschreibung und 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.
Oqtane ist ein Framework und CMS zur Entwicklung von Webseiten mithilfe von ASP.NET und Blazor. [@oqtane_about] 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]
In dieser Diplomarbeit fokussieren wir uns hauptsächlich auf `Themes` und `Modules`, aber es gibt auch `Language Packs` und `Pure Extensions`. [@oqtane_docs_extensions]
Ein `Module` (Modul) soll neue Funktionalitäten in das CMS hinzufügen und ein `Theme` soll die ganze Gestaltung der Webseite (die Shell) festlegen. [^6]
Ein `Module` (Modul) soll neue Funktionalitäten in das CMS hinzufügen und ein `Theme` soll die ganze Gestaltung der Webseite (die Shell) festlegen. [@oqtane_docs_extensions]
#### 3.2.1 Architektur eines Moduls
@@ -118,7 +115,7 @@ Die VPN basierten Zugänge sind tendenziell schwieriger zu finden und auszunutze
### 3.4 Entwicklung mit ASP.NET
#### 3.4.1 Blazor [^7]
#### 3.4.1 Blazor [@wikipedia_blazor]
Blazor ist ein kostenloses und quelloffenes Web-Framework, welches es möglich macht Benutzeroberflächen für Web-Browser, basierend auf C# und HTML, zu erstellen. Es wird von Microsoft als teil des ASP.NET Core Frameworks entwickelt.
@@ -169,18 +166,18 @@ Wie Front- und Backend miteinander interagieren hängt hauptsächlich vom Render
Dependency Injection ist ein Entwurfsmuster, bei dem die Abhängigkeiten eines Objekts nicht von diesem selbst erzeugt, sondern von außen „injiziert“ werden.
Wie der Software-Architekt Martin Fowler, der den Begriff im Jahr 2004 maßgeblich prägte, beschreibt, geht es im Kern darum, die Erzeugung von Objekten von deren Nutzung zu trennen [^9]. Anstatt dass eine Klasse ihre Hilfsobjekte mittels des new-Operators selbst instanziiert, werden ihr diese meist über den Konstruktor zur Verfügung gestellt.
Wie der Software-Architekt Martin Fowler, der den Begriff im Jahr 2004 maßgeblich prägte, beschreibt, geht es im Kern darum, die Erzeugung von Objekten von deren Nutzung zu trennen [@fowler_dependency_injection]. Anstatt dass eine Klasse ihre Hilfsobjekte mittels des new-Operators selbst instanziiert, werden ihr diese meist über den Konstruktor zur Verfügung gestellt.
In den folgenden beiden Kapiteln wird das Dependency Inversion Principle und das Microsoft Dependency Injection Framework genauer vorgestellt.
#### 3.5.1 Dependency Inversion Principle [^1]
#### 3.5.1 Dependency Inversion Principle [@ms_dependency_inversion][@logrocket_dependency_inversion]
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]
- Die High-Level-Module beschreiben die Applikations- / Businesslogik, ohne direkt mit den Low-Level-Modulen zu interagieren, sondern lediglich auf Abstraktionen. [@oodesign_dependency_inversion]
- Die Abstraktionen sollen nicht von Implementierungsdetails abhängig sein, sondern die Low-Level-Implementierung sollen gemäß der Abstraktionsschicht implementiert werden. [@oodesign_dependency_inversion]
Ausgangslage ist eine Softwarearchitektur im Direct-Dependency-Graph-Modell.
@@ -209,13 +206,13 @@ Das High-Level-Modul ruft lediglich eine Abstraktion eines Low-Level-Moduls auf,
#### 3.5.2 Microsoft Dependency Injection Framework
Dependency Injection ist in .NET genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. [^4]
Dependency Injection ist in .NET genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. [@ms_di_overview]
Alle Dependencies werden in einem `Service-Container` zur Verwaltung registriert. .NET hat einen eingebauten `Service-Container` (eine Implementierung des `IServiceProvider`). [^4]
Alle Dependencies werden in einem `Service-Container` zur Verwaltung registriert. .NET hat einen eingebauten `Service-Container` (eine Implementierung des `IServiceProvider`). [@ms_di_overview]
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 danach bereitgestellt. [^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 danach bereitgestellt. [@ms_di_overview]
Hier ein Beispiel aus der Dokumentation von Microsoft: [^4]
Hier ein Beispiel aus der Dokumentation von Microsoft: [@ms_di_overview]
```c#
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
@@ -268,7 +265,9 @@ architecture-beta
service ib(mdi:car-clutch)[IMessageWriter]
a:R --> L:ib
ib:R <-- L:b
```
### 3.6 Continuous Integration
@@ -321,31 +320,31 @@ Für die Sicherung und Zusammenarbeit wurde im Rahmen dieser Arbeit Remote-Repos
### 4.4 Gitea
Als schlanke und selbst gehostete Open-Source-Alternative zu Plattformen wie GitHub oder GitLab wurde für die Verwaltung der Repositories Gitea eingesetzt. Hauptgrund für die Benutzung von Gitea war meine bereits aufgesetzte Instanz, welche schon mehrere Jahre in Verwendung war und mit der es im Team auch schon Erfahrungen gab. Gitea bietet neben der reinen Git-Verwaltung essenzielle Werkzeuge für den Software-Lebenszyklus, wie ein integriertes Issue-Tracking, Code-Reviews über Pull-Requests sowie eine Benutzerverwaltung, und unterstützt somit die strukturierte Umsetzung der Diplomarbeit im Team.[^12]
Als schlanke und selbst gehostete Open-Source-Alternative zu Plattformen wie GitHub oder GitLab wurde für die Verwaltung der Repositories Gitea eingesetzt. Hauptgrund für die Benutzung von Gitea war meine bereits aufgesetzte Instanz, welche schon mehrere Jahre in Verwendung war und mit der es im Team auch schon Erfahrungen gab. Gitea bietet neben der reinen Git-Verwaltung essenzielle Werkzeuge für den Software-Lebenszyklus, wie ein integriertes Issue-Tracking, Code-Reviews über Pull-Requests sowie eine Benutzerverwaltung, und unterstützt somit die strukturierte Umsetzung der Diplomarbeit im Team.[@gitea_docs]
#### 4.4.1 Repositories
Ein Repository bildet den zentralen Speicherort für einen Projektteil. In Gitea wurden separate Repositories für die einzelnen Module und Themes, das Oqtane Framework, Skripte, die Dokumentation angelegt. Ein zusätzliches Repository bindet alle übrigen Quellcode-Repositories als Submodule ein, das macht die Einrichtung einer neuen Entwicklungsumgebung sehr kompfortabel. Dies ermöglichte eine saubere Trennung der verschiedenen Projektkomponenten. [^12]
Ein Repository bildet den zentralen Speicherort für einen Projektteil. In Gitea wurden separate Repositories für die einzelnen Module und Themes, das Oqtane Framework, Skripte, die Dokumentation angelegt. Ein zusätzliches Repository bindet alle übrigen Quellcode-Repositories als Submodule ein, das macht die Einrichtung einer neuen Entwicklungsumgebung sehr kompfortabel. Dies ermöglichte eine saubere Trennung der verschiedenen Projektkomponenten. [@gitea_docs]
#### 4.4.2 Issues
Zur Aufgabenplanung und Fehlerverfolgung wurde das integrierte Issue-System genutzt. Jede anstehende Aufgabe oder entdeckte Schwachstelle wurde als „Issue“ erfasst, einem Verantwortlichen zugewiesen und mit Labels (z. B. „Bug“, „Feature“ oder „Dokumentation“) versehen. Dies half dabei, den Überblick über den Projektfortschritt zu behalten und die Anforderungen aus dem Lastenheft strukturiert abzuarbeiten. [^12][^13]
Zur Aufgabenplanung und Fehlerverfolgung wurde das integrierte Issue-System genutzt. Jede anstehende Aufgabe oder entdeckte Schwachstelle wurde als „Issue“ erfasst, einem Verantwortlichen zugewiesen und mit Labels (z. B. „Bug“, „Feature“ oder „Dokumentation“) versehen. Dies half dabei, den Überblick über den Projektfortschritt zu behalten und die Anforderungen aus dem Lastenheft strukturiert abzuarbeiten. [@gitea_docs][@gitea_issue_tracker]
#### 4.4.3 Pull Requests
Um die Qualität des Codes zu sichern, wurden Änderungen nicht direkt in den Hauptzweig eingespielt, sondern über Pull Requests eingereicht. Ein Teammitglied konnte so die Änderungen eines anderen sichten, kommentieren und bei Bedarf Korrekturen anfordern. Erst nach einer erfolgreichen Überprüfung wurde der Code in den main-Branch gemergt. [^12][^14]
Um die Qualität des Codes zu sichern, wurden Änderungen nicht direkt in den Hauptzweig eingespielt, sondern über Pull Requests eingereicht. Ein Teammitglied konnte so die Änderungen eines anderen sichten, kommentieren und bei Bedarf Korrekturen anfordern. Erst nach einer erfolgreichen Überprüfung wurde der Code in den main-Branch gemergt. [@gitea_docs][@gitea_pull_requests]
#### 4.4.4 Actions
Gitea Actions wurden eingesetzt, um CI/CD-Pipelines (Continuous Integration / Continuous Deployment) zu realisieren. Bei jedem Push oder Pull Request wurden automatisierte Skripte ausgeführt, die das Projekt bauten. Dies reduzierte manuelle Fehlerquellen erheblich. Außerdem konnten wir mithilfe von CI/CD den Release Prozess einmalig festlegen und automatisieren, ohne bei jedem Update manuell den selben Prozess wiederholt durchgehen zu müssen. Das APT-Package Projekt enthält die CI/CD Konfiguration für das bauen von Oqtane, der Module und Themes, sowie das verpacken in ein APT Paket und dem veröffentlichen aller Pakete als eingenes Gitea Release. [^12][^15]
Gitea Actions wurden eingesetzt, um CI/CD-Pipelines (Continuous Integration / Continuous Deployment) zu realisieren. Bei jedem Push oder Pull Request wurden automatisierte Skripte ausgeführt, die das Projekt bauten. Dies reduzierte manuelle Fehlerquellen erheblich. Außerdem konnten wir mithilfe von CI/CD den Release Prozess einmalig festlegen und automatisieren, ohne bei jedem Update manuell den selben Prozess wiederholt durchgehen zu müssen. Das APT-Package Projekt enthält die CI/CD Konfiguration für das bauen von Oqtane, der Module und Themes, sowie das verpacken in ein APT Paket und dem veröffentlichen aller Pakete als eingenes Gitea Release. [@gitea_docs][@gitea_actions]
#### 4.4.5 Releases
Über die Release-Funktion wurden wichtige Meilensteine der Diplomarbeit festgeschrieben. Hierbei wird ein spezifischer Git-Tag mit einer Versionsnummer versehen und die dazugehörigen Binärdateien, Pakete und Dokumente archiviert. So lässt sich jederzeit auf einen stabilen, abgabebereiten Stand des Projekts zugreifen. [^12]
Über die Release-Funktion wurden wichtige Meilensteine der Diplomarbeit festgeschrieben. Hierbei wird ein spezifischer Git-Tag mit einer Versionsnummer versehen und die dazugehörigen Binärdateien, Pakete und Dokumente archiviert. So lässt sich jederzeit auf einen stabilen, abgabebereiten Stand des Projekts zugreifen. [@gitea_docs]
#### 4.4.6 Package Repositories
Gitea fungierte zusätzlich als Register für Pakete und Container-Images. Selbst erstellte Artefakte, wie das Debian Paket für die Bereitstellung der Anwendung, wurden direkt in der Gitea-Instanz versioniert gespeichert. Dadurch waren alle notwendigen Komponenten für das Deployment an einem zentralen Ort verfügbar und abrufbar. Gitea selbst unterstützt verschiedenste Pakettypen. Darunter fallen unteranderem NuGet- und Debianpakete. Für beide haben wir in dieser Arbeit verwendung gefunden. [^12][^16]
Gitea fungierte zusätzlich als Register für Pakete und Container-Images. Selbst erstellte Artefakte, wie das Debian Paket für die Bereitstellung der Anwendung, wurden direkt in der Gitea-Instanz versioniert gespeichert. Dadurch waren alle notwendigen Komponenten für das Deployment an einem zentralen Ort verfügbar und abrufbar. Gitea selbst unterstützt verschiedenste Pakettypen. Darunter fallen unteranderem NuGet- und Debianpakete. Für beide haben wir in dieser Arbeit verwendung gefunden. [@gitea_docs][@gitea_packages]
### 4.5 Kommunikation
@@ -369,12 +368,12 @@ Für den tatsächlichen Versand der E-Mails nutzen wir den Cloud-Dienst Brevo. D
Das Token Lifetime Modul wurde geschrieben, um die Token-Lebenszeit konfigurierbar zu machen. Notwendig war das, um die Passwort Reset Links im initialen Mail versand länger gültig sein zu lassen. Durch das `Batch Processing` war es möglich, dass eine Mail erst Tage nach erstellen des Links hinaus geschickt wird und bei einer Standard Ablaufdauer von 2 Tagen sind manche Links schon ungültig, bis sie den Mail Server erreichen. Ziel war es, die Änderung der Lebenszeit für Administratoren im User Interface im Admin Bereich möglich zu machen.
Technisch bedeutet das, dass die standardmäßig vorkonfigurierten `DataProtectionTokenProviderOptions` explizit konfiguriert werden müssen. [^10] Der ASP.NET Core `UserManager`, welcher das generieren der Tokens übernimmt, verwendet einen `DataProtectorTokenProvider` und dieser wiederum kann mithilfe der `DataProtectionTokenProviderOptions` konfiguriert werden.
Technisch bedeutet das, dass die standardmäßig vorkonfigurierten `DataProtectionTokenProviderOptions` explizit konfiguriert werden müssen. [@andrewlock_token_lifetime] Der ASP.NET Core `UserManager`, welcher das generieren der Tokens übernimmt, verwendet einen `DataProtectorTokenProvider` und dieser wiederum kann mithilfe der `DataProtectionTokenProviderOptions` konfiguriert werden.
Es gibt 2 Möglichkeiten, wie man dieses Problem Lösen kann:
- `der Workaround`: Ein eigenes Modul, welches in seiner `Server/Startup.cs` die benötigten Werte korrekt setzt. Das hat den Vorteil, dass wir keine Änderungen im CMS selbst haben, sondern nur unsere eigene Erweiterung dafür schreiben. Andererseits könnte durch die undeterministische Ladereihenfolge eine `race-condition` auftreten. [^11] Darüber hinaus besteht kein gleichzeitiger Zugriff auf die appsettings.json und den IServiceProvider, in dem die Konfiguration gesetzt werden muss. => Nachdem wir in der Konfigurationsphase auch noch keinen Datenbankzugriff haben, können die Werte nicht aus der Datenbank geladen werden, sondern wir müssen auf eine Textdatei zurückgreifen.
- `die saubere Lösung`: Eine Änderung im Kern von Oqtane. Also wird in unserem Fork von Oqtane die Konfigurationslogik für die Tokenlifetime implementiert. In diesem Fall könnte die Konfigurationslogik direkt in `OqtaneServiceCollectionExtensions.cs` hinzugefügt werden, da hier auch alle anderen Oqtane Spezifischen Konfigurationen gesetzt werden. Das hat den Vorteil, dass der Code aufgeräumter und sauberer ist und die `race-condition` verhindert werden kann. [^11] Der initiale Grund dagegen ist, dass wir ein weiteres Git Repository zum warten haben (den Fork vom Oqtane.Framework), welches jetzt nicht mehr mit Upstream Commit-gleich ist.
- `der Workaround`: Ein eigenes Modul, welches in seiner `Server/Startup.cs` die benötigten Werte korrekt setzt. Das hat den Vorteil, dass wir keine Änderungen im CMS selbst haben, sondern nur unsere eigene Erweiterung dafür schreiben. Andererseits könnte durch die undeterministische Ladereihenfolge eine `race-condition` auftreten. [@race_conditions_pdf] Darüber hinaus besteht kein gleichzeitiger Zugriff auf die appsettings.json und den IServiceProvider, in dem die Konfiguration gesetzt werden muss. => Nachdem wir in der Konfigurationsphase auch noch keinen Datenbankzugriff haben, können die Werte nicht aus der Datenbank geladen werden, sondern wir müssen auf eine Textdatei zurückgreifen.
- `die saubere Lösung`: Eine Änderung im Kern von Oqtane. Also wird in unserem Fork von Oqtane die Konfigurationslogik für die Tokenlifetime implementiert. In diesem Fall könnte die Konfigurationslogik direkt in `OqtaneServiceCollectionExtensions.cs` hinzugefügt werden, da hier auch alle anderen Oqtane Spezifischen Konfigurationen gesetzt werden. Das hat den Vorteil, dass der Code aufgeräumter und sauberer ist und die `race-condition` verhindert werden kann. [@race_conditions_pdf] Der initiale Grund dagegen ist, dass wir ein weiteres Git Repository zum warten haben (den Fork vom Oqtane.Framework), welches jetzt nicht mehr mit Upstream Commit-gleich ist.
`Der Workaround` ist die Möglichkeit für die wir uns entschieden haben, allerdings ist das nicht die schönste Lösung. Eine eventuell nachfolgende Diplomarbeit kann an dieser Stelle ansetzen und `die saubere Lösung` implementieren.
@@ -477,7 +476,7 @@ Auf der Serverseite folgt das Modul dem etablierten Muster mit einem `BlackBoard
### 6.1 Produktion != Staging
Ein Learning, welches doch relativ schnell aufkam ist im Bereich der IT eigentlich kein unbekanntes. Wir hatten dieses Learning relativ bald, im Frühling 2025, als die ersten Probleme mit dem Deployment von Oqtane aufkamen. Oqtane war zwar in unserer Entwicklungsumgebung sehr einfach zum einrichten gewesen, das Deployment in der Cloud vom Hetzner war jedoch geplagt von Problemen. Im Zeitraum von Mai bis Okober hatten wir keine laufende Produktivumgebung. Dadurch sind wir mit dieser Diplomarbeit auch in [Zeitverzug](#Arbeitszeiteinschätzung-und-verzug) gekommen. Hätte ich mich vor dem Start der Diplomarbeit mit dem Deployment von Oqtane auseinander gesetzt, dann wäre das in [Probleme mit Oqtane](#Probleme mit Oqtane) beschriebene Problem früher aufgekommen und der Zeitverzug wäre nicht so groß, oder noch ganz vermeidbar gewesen.
Ein Learning, welches doch relativ schnell aufkam ist im Bereich der IT eigentlich kein unbekanntes. Wir hatten dieses Learning relativ bald, im Frühling 2025, als die ersten Probleme mit dem Deployment von Oqtane aufkamen. Oqtane war zwar in unserer Entwicklungsumgebung sehr einfach zum einrichten gewesen, das Deployment in der Cloud vom Hetzner war jedoch geplagt von Problemen. Im Zeitraum von Mai bis Okober hatten wir keine laufende Produktivumgebung. Dadurch sind wir mit dieser Diplomarbeit auch in [Zeitverzug](#arbeitszeiteinschätzung-zeitverzug) gekommen. Hätte ich mich vor dem Start der Diplomarbeit mit dem Deployment von Oqtane auseinander gesetzt, dann wäre das in [Probleme mit Oqtane](#fehlende-oder-nur-schlechte-dokumentation-von-oqtane) beschriebene Problem früher aufgekommen und der Zeitverzug wäre nicht so groß, oder noch ganz vermeidbar gewesen.
### 6.2 Teamleitung (Motivation / Downsizing)
@@ -501,7 +500,7 @@ Um das Projektziel dennoch zu erreichen, wurde der Zeitplan im Herbst 2025 massi
`90% fertig, oder fertig?`: Es gibt einige "Regeln", wie: das `Paretoprinzip`, `Hofstadters Law` und die `90-90 Regel`. Letztere wurde im Jahr 1985 von Jon Bentley in einer Kolumne "Programming pearls" veröffentlicht. Ausgeschrieben lautet sie:
> [Rule of Credibility] The first 90 percent of the code accounts for the first 90 percent of the development time. The remaining 10 percent of the code accounts for the other 90 percent of the development time.
> (Jon Bentley. 1985. Programmimg pearls. Commun. ACM 28, 9 (Sept. 1985), 896901. https://doi.org/10.1145/4284.315122) [^8]
> (Jon Bentley. 1985. Programmimg pearls. Commun. ACM 28, 9 (Sept. 1985), 896901. https://doi.org/10.1145/4284.315122) [@bentley1985programming]
> Diese Diplomarbeit liefert weitere Evidenz, dass diese Faustregel stimmt.
### 6.4 Sprints und Meetings (in Zukunft ja asynchron)
@@ -514,40 +513,6 @@ Ein zentrales Problem in unserer ursprünglichen Arbeitsweise war die Kopplung v
Lösungsansatz: Meetings und Besprechungen asynchron zueinander setzen.
- Asynchrone Daily-Updates: Statusberichte erfolgen schriftlich (z. B. in Gitea Issues oder YouTrack), nicht mehr in stundenlangen Call-Marathons. Das nimmt den zeitlichen Druck vom einzelnen Entwickler. Oder zumindest in kurzen Commitnachrichten, welche am Ende des Tages automatisch an alle Teammitglieder zum Überblick gesendet werden (eventuell mit @username tagging, um eine Person nochmal genau anzusprechen)
- Asynchrone Daily-Updates: Statusberichte erfolgen schriftlich (z. B. in Gitea Issues oder YouTrack), nicht mehr in stundenlangen Call-Marathons. Das nimmt den zeitlichen Druck vom einzelnen Entwickler. Oder zumindest in kurzen Commitnachrichten, welche am Ende des Tages automatisch an alle Teammitglieder zum Überblick gesendet werden (eventuell mit \@username tagging, um eine Person nochmal genau anzusprechen)
- Review-First-Policy: Ein Feature gilt erst dann als „fertig“, wenn es einen asynchronen Code-Review-Prozess durchlaufen hat. Das Meeting dient nur noch der Klärung von Blockern, nicht der Präsentation von Code. Das war eigentlich schon von Anfang an in unserer `Definition of Done` festgelegt worden.
- Entkoppelung von Meeting und Deadline: Meetings sollten der Synchronisation dienen, während die Abgabe von Arbeitspaketen kontinuierlich (Continuous Integration) erfolgen muss, um Lastspitzen (in der [Gitea Actions](#Continuous Integration) Pipeline) am Tag der Besprechung zu vermeiden.
# Quellen
[^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
[^5]: https://www.oqtane.org/#about
[^6]: https://docs.oqtane.org/dev/extensions/index.html
[^7]: https://en.wikipedia.org/wiki/Blazor
[^8]: https://dl.acm.org/doi/10.1145/4284.315122
[^9]: https://martinfowler.com/articles/injection.html
[^10]: https://andrewlock.net/implementing-custom-token-providers-for-passwordless-authentication-in-asp-net-core-identity/#changing-the-default-token-lifetime
[^11]: https://www.cs.umd.edu/projects/syschat/raceConditions.pdf
[^12]: https://docs.gitea.com/
[^13]: https://docs.gitea.com/installation/comparison#issue-tracker
[^14]: https://docs.gitea.com/usage/pull-request
[^15]: https://docs.gitea.com/usage/actions/overview
[^16]: https://docs.gitea.com/usage/packages/overview