--- include_toc: true gitea: none --- # Technologie ## Entwicklung mit Asp.Net (Was ist Blazor? / Was ist Razor? / Kestrel) ## Was ist Oqtane? Architektur von Oqtane? ## 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 5 `SOLID` Prinzipien in der Softwareentwicklung. Das DIP unterscheidet zwischen high-level und low-level Modulen. - Die high-level Module beschreiben die Applikations- / Buisnesslogik, 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 Abstraktionsschickt implemetiert werden. [^3] Ausgangslage ist eine Softwarearchitektur im Direct-Dependency-Graph Model. ```mermaid architecture-beta service a(mdi:package-variant-closed)[Klasse A] service b(mdi:package-variant-closed)[Klasse B] service c(mdi:package-variant-closed)[Klasse C] a:R --> L:b b:R --> L:c ``` 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] service c(mdi:package-variant-closed)[Klasse C] service ic(mdi:car-clutch)[Interface C] a:B --> T:ib ib:R <-- L:b b:B --> T:ic ic:R <-- L:c ``` Das high-level Modul ruft lediglich eine Abstraktion eines low-level Moduls auf, welche ein von einem, oder mehreren low-level Modulen implementiert worden ist. 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, testbar. Genau diese Modularität macht dependency injection möglich. ### Microsoft Dependency Injection Framework .Net hat ein eingebautes Framework für Dependency Injection. DI 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 instanzieren einer Klasse werden alle im Konstruktor erwarteten Dependencies bereitgestellt, bzw. selbst instanziert und dannach bereitgestellt. [^4] Hier ein Beispiel aus der Dokumentation von Microsoft: [^4] ```c# HostApplicationBuilder builder = Host.CreateApplicationBuilder(args); builder.Services.AddHostedService(); builder.Services.AddSingleton(); 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 IMessage Writer). Bei Programstart 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 instanzieren und zu starten. Nachdem Worker ein Dependency auf IMessageWriter hat wird über den zuvor erstelltem Dependency Graph die implementierung von IMessageWriter gesucht. Jetzt wird MessageWriter instanziert und dem Konstruktor von Worker übergeben, damit seine Dependencies befriedigt werden. So sehen 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 # 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 Asyncron # Modules ## Mass Mailer ## Event Registration ## Schwarzes Brett