Subscription Manager ist eine Webanwendung, die ich entwickelt habe, um wiederkehrende Abonnements für digitale Dienste und ihre Kosten zu verwalten.
Am Anfang war sie eine recht einfache .NET-Anwendung.
Mit der Zeit kamen eine Blazor-Webanwendung, eine API, Persistenz, Authentifizierung, Tests, Cloud-Infrastruktur und einige externe Integrationen hinzu.
Als ich über eine weitere Anwendung nachdachte, schienen einige dieser Bausteine nützlich genug, um sie erneut zu verwenden.
Am einfachsten wäre es gewesen, das gesamte Projekt zu kopieren. Damit hätte ich aber auch vieles übernommen, das speziell zu Subscription Manager gehörte.
Das war einer der Gründe, warum ich mit der Arbeit an Launchpad begonnen habe.
Mit etwas beginnen, das bereits im Einsatz war
Zu diesem Zeitpunkt hatte Subscription Manager bereits mehrere Iterationen durchlaufen.
Ich hatte eine funktionierende Grenze zwischen Web und API, Authentifizierung, Kontoverwaltung, Tests und eine Blazor-Anwendung, die über HTTP mit der API kommunizierte.
Das bedeutete nicht, dass diese Struktur für jede .NET-Anwendung richtig war. Es bedeutete lediglich, dass ich sie bereits in einem echten Projekt eingesetzt hatte und wusste, wie sie sich in der Praxis verhielt.
Damit war sie ein nützlicher Ausgangspunkt.
Launchpad begann damit, einige dieser Bausteine wiederzuverwenden und die Teile wegzulassen, die nur für Subscription Manager sinnvoll waren.
Identity war ein gutes Beispiel.
In Subscription Manager hatte dieser Bereich nach und nach produktspezifische Aufgaben übernommen: Benutzereinstellungen, E-Mail-Bestätigung, Zurücksetzen des Passworts, Abonnementpläne und weitere Kontoeinstellungen.
Die erste Version von Launchpad brauchte deutlich weniger.
Ich behielt den grundsätzlichen Ansatz bei, aber nicht die gesamte Funktionalität, die sich im Laufe der Zeit darum angesammelt hatte.
Ähnlich war es an anderen Stellen.
| Als Ausgangspunkt übernommen | In Subscription Manager belassen |
|---|---|
| Kommunikation zwischen Web und API | Abonnement-Domäne |
| ASP.NET Core Identity | Katalog digitaler Dienste |
| Behandlung des aktuellen Benutzers | Stripe-Integration |
| Lebenszyklus des Kontos | Wechselkurs-Integration |
| Testkonventionen | Sparplan-Funktionalität |
| Blazor-Interaktionsmodell | Produktspezifische Ansichten |
Das Ziel war nicht, alles zu finden, was beide Projekte gemeinsam nutzen könnten.
Es ging vor allem darum, Dinge nicht nur deshalb mitzunehmen, weil sie bereits vorhanden waren.
Nicht jede Schicht musste mitkommen
Ein kleines Beispiel ist das Domain-Projekt.
Subscription Manager hat eines, weil dort tatsächliches Domänenverhalten rund um Abonnements und digitale Dienste liegt.
Das wiederverwendbare Launchpad Template hat derzeit keines.
Ich hätte ein leeres Domain-Projekt hinzufügen können, damit die Struktur vertrauter aussieht. Es gab dort aber nichts Sinnvolles unterzubringen.
Also habe ich darauf verzichtet.
Eine generierte Anwendung kann ein solches Projekt hinzufügen, wenn ihre Domäne es braucht. Auch Launchpad kann später gemeinsamen Domänencode bekommen, wenn dafür ein konkreter Anwendungsfall entsteht.
Im Moment hatte ich keinen konkreten Bedarf für diese Schicht.
Der Generator kam später
Ich wollte den Generator ebenfalls nicht zu früh bauen.
Am Anfang wusste ich noch nicht, welche Teile der Grundlage stabil bleiben würden. Zu diesem Zeitpunkt hätte ich Konfiguration für Fälle hinzugefügt, die mir noch gar nicht begegnet waren.
Der frühere Plan war deshalb einfacher: weiter mit der Grundlage arbeiten, sie in konkreten Projekten einsetzen und beobachten, was sich tatsächlich wiederholt.
Dieser Plan änderte sich, als Launchpad konkreter wurde.
Im Repository entstand schließlich eine klare Trennung:
Launchpad
├── Template
│ └── wiederverwendbare Grundlage für ein Produkt
│
└── Generator
└── Werkzeuge zum Erstellen eines Produkts
Damit ließ sich die Grenze leichter nachvollziehen.
Das Template ist der Teil, aus dem eine neue Anwendung werden kann.
Der Generator umfasst die Werkzeuge für diesen Prozess und bleibt in Launchpad.
Als diese Trennung klar war, musste die Generierung kein sehr allgemeines Problem mehr lösen. Sie konnte mit einer Struktur arbeiten, die Launchpad bereits kannte.
Vorhersehbare Arbeit vorhersehbar halten
Launchpad ist auch der Ort, an dem ich mit KI-gestützter Softwareentwicklung experimentiere.
Dadurch wurde die Repository-Generierung zu einem interessanten Fall.
Einige Schritte beim Erstellen eines neuen Produkts brauchen keine Interpretation. Projekte und Namespaces umzubenennen, bekannte Bezeichner zu aktualisieren, die Solution neu zu bauen und Tests auszuführen, hat jeweils ein recht klar erwartbares Ergebnis.
Diese Schritte wollte ich deterministisch halten.
Der aktuelle Ablauf sieht ungefähr so aus:
Produktdefinition
↓
Template kopieren
↓
Transformieren
↓
Validieren
↓
Paket erstellen
↓
Veröffentlichen
Diese Schritte sind gewöhnlicher .NET-Code.
KI kann später weiterhin nützlich sein, wenn die Arbeit weniger vorhersehbar wird: bei produktspezifischem Verhalten, UI-Entscheidungen oder Änderungen, bei denen etwas interpretiert und nicht nur transformiert werden muss.
Diese Trennung finde ich in diesem Projekt hilfreich. Sie macht auch Fehler im Generierungsablauf deutlich weniger rätselhaft.
Weiterhin ein Experiment
Launchpad ist kein fertiges Framework für jede .NET-Anwendung.
Das Template enthält Dinge, die sich bisher als nützlich erwiesen haben. Der Generator kann ein darauf basierendes Produkt vorbereiten, transformieren, validieren, paketieren und veröffentlichen.
Der nächste sinnvolle Test besteht darin, die Grundlage erneut einzusetzen und zu sehen, was davon Bestand hat.
Einige Teile könnten sich als wirklich wiederverwendbar erweisen. Andere könnten wieder in einzelne Produkte zurückwandern. Etwas, das heute nützlich aussieht, kann später ganz verschwinden.
Subscription Manager gab mir eine konkrete Anwendung als Ausgangspunkt. Launchpad gibt mir einen Ort, an dem ich prüfen kann, welche Teile dieser Erfahrung es tatsächlich wert sind, weitergetragen zu werden.
Für mich war das hilfreicher, als eine wiederverwendbare Grundlage zu entwerfen, bevor es überhaupt etwas Reales gab, das ich wiederverwenden konnte.