1 Commits

Author SHA1 Message Date
4559b758a7 Fix: Broken commit marker
All checks were successful
Word Count / count-words (pull_request) Successful in 31s
2026-03-18 22:17:17 +01:00

View File

@@ -49,12 +49,7 @@ Auch steht die Wahl der Programmiersprache und des CMS an. Nachdem wir im Unterr
- DNN / Dot Net Nuke
> Dieses CMS ist der Platzhirsch. Es wird von der DNN Foundation gewartet, arbeitet mit dem Dotnet Framework, welches nicht unter Linux läuft. Und ein Windows Server ist im Betrieb teurer und in der Absicherung aufwändiger.
- Oqtane
<<<<<<< HEAD
> # Oqtane wirkt auf sehr modular und flexibel, auch innerhalb von Modulen kann man auf alle funktionen des ASP.NET Core Frameworks verwenden. Die Dokumentation wirkt nicht besonders gut, aber ausreichend. Die enthaltenen Fehlinformationen und mangelnden Anleitungen für den Betrieb mit Linux sind erst im Nachhinein aufgefallen.
>
> Oqtane wirkt auf sehr modular und flexibel, auch innerhalb von Modulen kann man auf alle Funktionen des ASP.NET Core Frameworks verwenden. Die Dokumentation wirkt nicht besonders gut, aber ausreichend. Die enthaltenen Fehlinformationen und mangelnden Anleitungen für den Betrieb mit Linux sind erst im Nachhinein aufgefallen.
>
> > > > > > > afd1f3b (Fix: Typos and move Sources into the end)
Insbesondere aufgrund seiner sehr hohen Flexibilität, haben wir uns am Ende für Oqtane entschieden.
@@ -368,24 +363,24 @@ Integration von Brevo
Für den tatsächlichen Versand der E-Mails nutzen wir den Cloud-Dienst Brevo. Dieser bietet eine zuverlässige Zustellung (hohe Reputation der Mailserver), stellt uns jedoch in der kostenlosen Variante vor eine Herausforderung: nur 300 E-Mails pro Tag.
`Batch-Processing`: Mails werden nicht sofort ("Fire and Forget") versendet, sondern in eine Versandwarteschlange geschrieben. Nachdem schon die Notifications Infrastruktur, welche sich auch um den Mail versand kümmert, ins Framework eingebaut worden ist, wird diese gleich zum `schedulen` unserer E-Mails genutzt. Immer 100 Mails alle 24 Stunden bis alle Ziele die Mails erhalten haben. Das Limit von 100 / Tag ist konservativ sehr niedrig angesetzt, damit funktionen wie Passwort Reset Mails nicht (leicht) dadurch beeinflusst werden können.
`Batch-Processing`: Mails werden nicht sofort ("Fire and Forget") versendet, sondern in eine Versandwarteschlange geschrieben. Nachdem schon die Notifications Infrastruktur, welche sich auch um den Mail versand kümmert, ins Framework eingebaut worden ist, wird diese gleich zum `schedulen` unserer E-Mails genutzt. Immer 100 Mails alle 24 Stunden bis alle Ziele die Mails erhalten haben. Das Limit von 100 / Tag ist konservativ sehr niedrig angesetzt, damit Funktionen wie Passwort Reset Mails nicht (leicht) dadurch beeinflusst werden können.
#### 5.1.2 Token Lifetime
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 gultig 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 mache 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.
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.
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überhinaus ist der gleichzeitigen Zugriff auf appsettings.json und dem IServiceProvider in dem die Konfiguration gesetzt muss. => Nachdem in der Konfigurationsphase auch noch keinen Datenbankzugriff haben, können die Werte nicht aus der DB geladen werden, sondern müssen auf eine Text Datei zurückgreifen.
- `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` 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.
#### 5.1.3 Reporting System
Eine weitere Anforderung der Diplomarbeit war es Einträge in Modulen wie der `Hall of Fame`, dem `Schwarzen Brett` und dem Premium Bereich (`Engineer Applications`) melden zu können. Am Anfang war es wichtig, dass jeder schnell vorrankommt, allerdings haben wir die Kommunikation Teamintern ein wenig verschlafen und dadurch ein paar Funktionen doppelt geschrieben. Dadurch kam es zu inkonsistenzen in der Verwendung der unterschiedlichen Reporting Systeme. Deswegen haben wir uns am Ende für eine globales Reporting System entschieden.
Eine weitere Anforderung der Diplomarbeit war es Einträge in Modulen wie der `Hall of Fame`, dem `Schwarzen Brett` und dem Premium Bereich (`Engineer Applications`) melden zu können. Am Anfang war es wichtig, dass jeder schnell vorankommt, allerdings haben wir die Kommunikation Teamintern ein wenig verschlafen und dadurch ein paar Funktionen doppelt geschrieben. Dadurch kam es zu Inkonsistenzen in der Verwendung der unterschiedlichen Reporting Systeme. Deswegen haben wir uns am Ende für eine globales Reporting System entschieden.
Angestrebt wurde folgender Ablauf für das Melden eines Eintrags:
@@ -409,7 +404,7 @@ sequenceDiagram
Im oben dargestellten Ablaufdiagram werden das ReportingComponent und der ReportingHandler vom Reporting System über Dependency Injection bereitgestellt, nicht vom Modul selbst. Dadurch erreichen wir eine bessere Trennung der Zuständigkeiten und halten das S in SOLID ein: `Single responsibility`. Die Module sind jetzt nicht mehr für das Reporting selber verantwortlich, sondern müssen nur das Reportings System einbinden.
Damit DI funktioniert muss für den DI Consumer (`also das Modul, welches das Reporting System einbinden möchte`) das Interface zur Kompilierzeit zur Verfügung stehen. Um das zu erreichen habe ich eine neue Klassenbibliothek erstellt: Sie heißt Interfaces wird per Gitea Actions automatisch in ein Nuget Paket gebaut und in der `Gitea Actions Nuget Registry` veröffentlicht. Dieses Nuget Paket wird dann in jedem notwendigen Modul als dependency hinzugefügt und damit kann man Modulübergreifend auf die Services und das IReporting Component zugreifen.
Damit DI funktioniert muss für den DI Consumer (`also das Modul, welches das Reporting System einbinden möchte`) das Interface zur Kompilierzeit zur Verfügung stehen. Um das zu erreichen habe ich eine neue Klassenbibliothek erstellt: Sie heißt Interfaces wird per Gitea Actions automatisch in ein Nuget Paket gebaut und in der `Gitea Actions Nuget Registry` veröffentlicht. Dieses Nuget Paket wird dann in jedem notwendigen Modul als Dependency hinzugefügt und damit kann man Modulübergreifend auf die Services und das IReporting Component zugreifen.
Die Implementierung des IReportingComponents stellt nur eine Property (`ReportType`, welche den TypeName der Razor Komponente zurückliefert, damit Dynamic Component sie laden kann) und eine Methode (`ConstructParameterList`, welche das Parameter Dictionary erstellt. Nur zwecks Typensicherheit eingefügt) bereit. Mit dem Dynamic Component von Razor ist es möglich, per C# Code unterschiedliche Komponenten zu rendern und damit auch die per DI injizierte Klasse.
@@ -423,7 +418,7 @@ Die Implementierung des IReportingComponents stellt nur eine Property (`ReportTy
}
```
Die Bereitstellund des Moduls geschieht im `AdminModules` Modul.
Die Bereitstellung des Moduls geschieht im `AdminModules` Modul.
### 5.2 Event Registration
@@ -466,7 +461,7 @@ Um die Mitglieder regelmäßig über neue Inhalte zu informieren, wurde ein auto
- Filterung: Der Job identifiziert alle Einträge, die seit dem letzten Versand erstellt wurden.
- Zielgruppenselektion: Es werden alle Benutzer identifiziert, die der Rolle "Absolventen" angehören.
- Zusammenstellung: Für jeden dieser Benutzer wird eine personalsierte Email-Notification generiert, welche eine Zusammenfassung der neuen Einträge enthält.
- Zusammenstellung: Für jeden dieser Benutzer wird eine personalisierte Email-Notification generiert, welche eine Zusammenfassung der neuen Einträge enthält.
- Versand: Die generierten Notifications werden in die Warteschlange der Notification-Infrastruktur eingereiht und sukzessive versendet.
Integration des Reporting-Systems
@@ -476,7 +471,7 @@ Ein wichtiges Merkmal des Schwarzen Bretts zur Sicherstellung der Inhaltsqualit
#### 5.3.4 Technischer Hintergrund
Auf der Serverseite folgt das Modul dem etablierten Muster mit einem `BlackBoardRepository` für den effizienten Datenbankzugriff und einem `BlackBoardController` für die API-Bereitstellung. Die Implementierung des Scheduled Jobs als HostedServiceBase ermöglicht eine tiefe Integration in die Oqtane-Infrastruktur bei gleichzeitig geringem Ressourcenverbrauch. erbarkeit des Moduls gewährleistet.
Auf der Serverseite folgt das Modul dem etablierten Muster mit einem `BlackBoardRepository` für den effizienten Datenbankzugriff und einem `BlackBoardController` für die API-Bereitstellung. Die Implementierung des Scheduled Jobs als HostedServiceBase ermöglicht eine tiefe Integration in die Oqtane-Infrastruktur bei gleichzeitig geringem Ressourcenverbrauch.
## 6 Learnings
@@ -519,7 +514,7 @@ 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.