5.9 KiB
Table of Contents
- Technologie
- Entwicklung mit Asp.Net (Was ist Blazor? / Was ist Razor? / Kestrel)
- Was ist Oqtane? Architektur von Oqtane?
- Systemarchitektur (Postgres / Oqtane / Nginx )
- Websockets und HTTP 1.1
- Dependency injection
- Projektmanagement
- Learnings
- Probleme mit Oqtane
- Arbeitszeiteinschätzung (Zeitverzug)
- Teamleitung (Motivation / Downsizing)
- Produktion != Staging
- Sprints und Meetings (in Zukunft ja Asyncron
- Modules
Technologie
Entwicklung mit Asp.Net (Was ist Blazor? / Was ist Razor? / Kestrel)
Was ist Oqtane? Architektur von Oqtane?
Systemarchitektur (Postgres / Oqtane / Nginx )
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. 2
- Die Abstraktionen sollen nicht von Implementierungsdetails abhängig sein, sondern die low-level Implementierung sollen gemäß der Abstraktionsschickt implemetiert werden. 2
Ausgangslage ist eine Softwarearchitektur im Direct-Dependency-Graph Model.
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.
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 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
Dependency Injektion ist in .Net genau so wie Konfiguration, Protokollierung und das Optionsmuster ins Framework integriert. 3
Alle Dependencies werden in einem service container zur verwaltung registriert. .Net hat einen eingebauten service container (eine Implementierung des IServiceProvider). 3
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. 3
Hier ein Beispiel aus der Dokumentation von Microsoft: 3
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 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.
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