Langlebige, robuste und evolvierbare Software – Anregungen für Entwicklungsleiter:innen

Das Erstellen von langlebigen, robusten und evolvierbaren Anwendungen ist keine Trivialität und gewinnt im Kontext von sich schnell verändernden Anforderungen immer mehr an Bedeutung.

Dieser kleine Leitfaden versteht sich als Anregung und spiegelt unsere Erfahrung bei bluehands mit der Entwicklung von hochspezialisierten komplexen Fachanwendungen. Es ist eine Sammlung von Praktiken, die wir bei der täglichen Arbeit in den Projekten mit unseren Kunden anwenden.

· 7 Min Lesezeit

Klares fachliches Model

Die klare Benennung und Beschreibung der Fachlichkeit ist ein Schlüsselfaktor für evolvierbare Software. Die Erfahrung zeigt, dass sich fachliche Anforderungen und technische Rahmenbedingungen in unterschiedlichen Geschwindigkeiten entwickeln. Sind Fachlichkeit und technische Umsetzung nicht klar getrennt, ist die Anwendung wie zementiert.

Jede Anwendung kann in drei voneinander getrennte logische Bereiche aufgeteilt werden. Der Zugangspunkt zur Anwendung als UI oder API, das fachliche Modell, welches dazu dient, den Nutzen der Anwendung zu generieren und das technische Modell zur Umsetzung des fachlichen Modells.

Das fachliche Modell zeichnet sich dadurch aus, dass es nur fachliche Operationen und kontextbezogene Darstellungen des Zustandes enthält. Konkret: Alle Methoden sind Verben, die im Kontext der Domäne existieren, z. B. „Geld überweisen“, „Umziehen“, „Abflussventil öffnen“. Technische Verben wie „Speichern“ kommen nicht vor.

Ebenso bezieht sich der angezeigte bzw. abfragbare Zustand auf den fachlichen Kontext. Konkret: „Kontoauszug mit aktuellem und letztem Kontostand“, „Liste der Kontobewegungen im letzten Monat“, „Kunde mit der Anzahl der Bestellungen“ oder „Kunde mit einer ABC-Bewertung“. Im Allgemeinen sind hier Aggregate im Spiel, d. h. die angezeigte Information ist das Ergebnis einer Berechnung aus vielen anderen Daten.

Für die konkrete Identifizierung und Benennung der Fachlichkeit empfehlen wir Ansätze angelehnt an das Domain Driven Design. Ein oder mehrere Event Storming Workshops sind angemessen. Es gilt die Merkregel: In Operationen und Repräsentationen denken.

Bei der Implementierung des fachlichen Modells gilt es, dieses Modell dem Konsumenten zur Verfügung zu stellen. Hier ist es wichtig, das Duplizieren der Fachlichkeit zu vermeiden (z. B. ähnliche Logik im JavaScript UI und Backend – Validierungsregeln oder die Verfügbarkeit von Operationen). Wir empfehlen eine Architektur basierend auf HATEOAS, also REST mit Hypermedia. Für das ASP.NET Core Programmiermodel stellen wir eine Bibliothek als NuGet-Package zur Verfügung.

Die Operationen und die Repräsentation asynchron modellieren

Die temporale Kopplung zwischen zwei Teilen der Anwendung erschwert die Skalierung des Systems.
Diese Kopplung muss aufgelöst werden. Wir empfehlen die Modellierung über Nachrichten. Eine strikte Trennung von Operationen, die den Zustand verändern, und Operationen, die den Zustand erfragen, unterstützt diese Entkopplung.

Wir empfehlen den Einsatz von CQRS im Backend der Anwendung. Für Systeme mit hohen Anforderungen an Nachvollziehbarkeit oder Skalierbarkeit wird Event Sourcing eingesetzt. Alternativ und in Spezialfällen kann auch das Actor-Modell verwendet werden.

Insgesamt ergibt sich folgende Situation: Das Frontend als Zugangspunkt kommuniziert mit dem Backend über eine REST-Schnittstelle via HTTP. Das ist eine Backend-for-Frontend-Architektur. Das Abarbeiten der Operation erfolgt gegebenenfalls über eine Microservice-Architektur. Diese Services sind entweder zustandsverändernd (Command) oder stellen eine Sicht (Query) zur Verfügung.

Den Anwendungskern von Technologien, Frameworks, Tools, Verbindungen und Zugriffstechniken isolieren

Kopplung ist Gift für die Evolvierbarkeit. Andererseits: Anwendungen ohne Kopplung tun nichts.

Es gilt daher, die Kopplung von Komponenten und Anwendungsteilen aktiv zu gestalten.

Wir empfehlen das Ports and Adapters Pattern (aka Hexagonale Architektur) bzw. die Erweiterungen Onion Architecture und Clean Architecture. Die Domäne und der Use Case (Application) Layer sind vollkommen unabhängig von externen Tools und Frameworks. Abhängigkeiten bestehen nur zur Laufzeitumgebung sowie Compiler und Programmiersprache.

Erst die Ports und Adapter haben externe Abhängigkeiten. Diese Abhängigkeiten können dann wieder evolvieren, um z. B. die Datenbank-Technologie zu wechseln oder auf ein anderes Anwendungsmodell umzusteigen (ASP.NET Core WebApi zu gRPC oder Azure Function). Wir empfehlen auch innerhalb des Ports oder des Adapters so weit wie möglich die technologische Abhängigkeit zu isolieren. Die hier gewählten Strategien sind spielentscheidend für die Langlebigkeit der Anwendung.

Sichere Umgebung so klein wie möglich halten

Je kleiner diese Umgebung, umso leichter wird der Wechsel der Hosting-Umgebung.

Eine sichere Umgebung zeichnet sich dadurch aus, dass Prozesse innerhalb dieser Umgebung vollständigen Zugriff auf die Informationen und Daten haben. Diese Prozesse entscheiden dann über die Zugriffsmöglichkeiten (z. B. ein Backend-Dienst mit Vollzugriff auf Datenbank – im Backend werden basierend auf der Rolle des Anwenders die Daten gefiltert).

Unsere Empfehlung ist, diesen Bereich so klein wie möglich zu halten. Im Idealfall kann man die Autorisierung des Zugriffs in die Ressource verlagern, d. h. alle Dienste zwischen Zugangspunkt und Informationsressource arbeiten im Kontext der Zugriffsrolle. Falls dies technisch nicht möglich ist, gibt es einen kleinen Zugriffsdienst auf die Ressource als Gateway. Dieser Zugriffsdienst ist dann innerhalb der sicheren Umgebung und muss abgeschottet werden (In der Regel über die Netzwerk-Topologie).

Software Engineering at its best

Eine Binsenweisheit – gutes Engineering mit strikter und radikaler Einhaltung der SOLID-Prinzipien und anderer Praktiken helfen. Wiederholungen (Missachtung von DRY), auch strukturell, sind schädlich.

Clean Code als eine Sammlung von Praktiken zum Erreichen einer evolvierbaren Codebasis ist ständig anzuwenden. Eine Auflistung dieser Praktiken würde diesen Beitrag sprengen. Wir verweisen auf die entsprechenden Quellen.

Unsere Erfahrung zeigt, dass kleinste Nachlässigkeiten sich teuer bemerkbar machen. Wir erfahren in unserer täglichen Praxis, dass Clean Code zu einer kostengünstigen und schnellen Produktentwicklung führt. Dem Zitat von Uncle Bob ist nichts hinzuzufügen: „The only way to go fast, is to go well”

PaaS, FaaS und SaaS soweit wie möglich

Für das Hosting von verteilten Applikationen haben wir – von einfachen Szenarien abgesehen – zwei Möglichkeiten: Die Orchestrierung von Containern mit z. B. Kubernetes oder die Verwendung von PaaS Angeboten von den großen Cloud-Lösungen wie Microsoft Azure oder Amazon AWS.

Durch das geschickte Kombinieren von Infrastruktur-Elementen wie Laufzeitumgebung (Hosting), Data-Storage, Messaging, Networking durch Platform as a Service, Function as a Service und Software as a Service können sehr effiziente, skalierbare und kostengünstige Lösungen entstehen.

Ein Hosting von verteilten Anwendungen in VM-Umgebungen ist von Spezialfällen (starke Verwendung von Windows-Features) nicht sinnvoll, da ein Continuous Deployment und Infrastructure as Code nur schwer realisierbar sind.

Durch die Verwendung der Hexagonalen Architektur kann ein Vendor- und Technologie-Lock-in vermieden werden. Somit erhält man die Vorteile von PaaS-Angeboten ohne deren Nachteile.

Identität der Benutzer (Anmeldeverfahren, Geheimnisse, Namen) von der Benutzer-Entität (Benutzer-Id) trennen

Das Vermengen von Identität und Entität führt zu einem Vendor- und Technologie-Lock-in und erschwert die spätere Migration.

Die meisten Anwendungen besitzen ein Konzept zur Zugriffssteuerung mittels Benutzer-Konten. Es ist wichtig, drei wesentliche Aspekte zu unterscheiden: Das Benutzer-Konto (Im Wesentlichen eine Id und evtl. weitere anwendungsspezifische Merkmale), die Identität des Benutzer-Kontos (wie wird nachgewiesen, dass man Zugriff auf das Konto hat – typischerweise über Benutzername und Kennwort oder kryptographische Verfahren) sowie die Rollen. Das Benutzer-Konto und die Identität sind globale Systemeigenschaften, die Rollen sind bei den (Teil-)Anwendungen verankert.

Wir empfehlen OpenId Connect mit Federation und Claim Based Identity. Die Claims werden über JSON Web Token (JWT) transportiert und abgebildet.

Moderne Technologien verwenden

Es gehört zur Natur von IT und Softwaretechnologien, dass diese veralten und von den Herstellern nicht mehr unterstützt werden. Die Verwendung von nicht unterstützten Softwarekomponenten stellt ein erhebliches Risiko dar. Nicht nur aufgrund von Sicherheitslücken, sondern auch durch erschwertes Bauen und oft schwindendes Wissen. Moderne Technologien haben den Vorteil, dass sie bestehende Probleme adressieren und diese lösen. Natürlich müssen die beteiligten Personen diese Technologien dann auch beherrschen. Das (initiale) Nichtbeherrschen dieser Technologien sollte aber nicht daran hindern, diese einzuführen. So ist z. B. der Einsatz von funktionalen Paradigmen in objektorientierten Sprachen für viele ungewohnt. Sie verbessern aber deutlich die Qualität.

Telemetrie und Fehlerdiagnostik einbauen

Wir empfehlen Health-Monitoring, Usage-Monitoring, Error-Reporting und Logging mit Ablaufverfolgung
Wir empfehlen den Einsatz moderner Technologien und das Erstellen eines Maintenance-Plans zur kleinteiligen Aktualisierung der beteiligten Komponenten.

Sie haben Fragen? Lassen Sie uns reden ...

Aydin Mir Mohammadi

Aydin
Mir Mohamadi


Lars Kaufmann

Lars
Kaufmann