Sauberer Code in C#: von .NET, CLR und BCL zur Clean Architecture

Warum kann eine kleine Änderung an einer Anwendung einen ganzen Tag kosten? Wir beginnen bei der Funktionsweise von .NET und gehen dann zu lesbarem C#, klarer Verantwortung und Clean Architecture über. Mit Beispielen aus dem Arbeitsalltag und einer Methode, bestehenden Code aufzuräumen, ohne alles neu zu schreiben.

Der vollständige Artikel steht unten. Aktivieren Sie JavaScript für Quiz und Simulationen.

Spezialbeitrag · Entwicklerwerkstatt

Code, zu dem man mit klarem Kopf zurückkehren kann.

Montag, 9:12. Der Kaffee ist noch heiß. Im Chat erscheint: „Wir ändern die Stornierungsregeln für Reservierungen. Das ist doch eine kleine Korrektur?“ Du öffnest das Projekt. Dieselbe Regel steckt im Controller, in einem Hintergrundjob und im Adminbereich. Überall sieht sie etwas anders aus.

Genau dann zeigt sich Codequalität: wenn wir eine Regel finden, ihren Grund verstehen und das Verhalten ändern müssen, ohne versehentlich den Rest des Systems zu beschädigen. Diesen Weg gehen wir gemeinsam: von dem, was .NET tatsächlich tut, über lesbares C# bis zu Grenzen, die eine Anwendung weiterentwickelbar machen.

01 / Plattform verstehen02 / Regeln ordnen03 / Änderung prüfen

Autor: ArcCloud Studio · Stand der Versionsinformationen: 8. Oktober 2026. Die Fahrradwerkstatt und Teamszenen sind Lernbeispiele, keine Beschreibung eines Kundenprojekts.

01 / Fundament

Was ist .NET – und wo gehört C# dazu?

Beim ersten Projekt landen C#, .NET, Visual Studio und ASP.NET Core leicht im selben Topf. Im Team wird die Unterscheidung schnell nützlich. Startet die Anwendung nicht auf dem Server, müssen wir wissen, ob die Runtime fehlt, das Betriebssystem falsch gewählt wurde oder die falsche Datei veröffentlicht wurde.

C# ist eine Sprache, .NET eine Plattform zum Entwickeln und Ausführen von Anwendungen. In der Sprache formulieren wir Regeln. Die Plattform liefert Laufzeitumgebung, Bibliotheken und Werkzeuge. ASP.NET Core unterstützt unter anderem HTTP-Anwendungen und APIs. Visual Studio, Rider und VS Code sind Arbeitswerkzeuge; die Editorwahl verändert keine Geschäftsregeln.

Sprache

C#

Du beschreibst Verhalten: reservieren, einen Termin absagen, einen Preis berechnen. Der Compiler prüft Syntax und Typen.

Ausführung

Runtime / CLR

Die Umgebung führt verwalteten Code aus und liefert Dienste für das laufende Programm.

Werkzeuge

SDK

Du baust, startest und veröffentlichst die Anwendung. Das SDK enthält unter anderem CLI, Compiler und Runtime.

Auf einem Entwicklerrechner installieren wir gewöhnlich das SDK. Der Serverstart hängt vom Veröffentlichungsmodell ab: Die Anwendung kann eine installierte Runtime nutzen oder diese mitbringen. „Ich habe .NET“ ist daher oft zu ungenau. Besser klären wir SDK-Version, Zielframework und Deployment-Modell.

Terminal · drei Fragen an die Umgebung

dotnet --list-sdks
dotnet --list-runtimes
dotnet --info

Der erste Befehl zeigt, womit du bauen kannst. Der zweite zeigt installierte Runtimes. Der dritte sammelt Diagnoseinformationen. Das ist ein sinnvoller Anfang, wenn das Projekt bei einer Person läuft und bei einer anderen schon vor der ersten eigenen Codezeile scheitert.

Dokumentation: Bestandteile der .NET-Plattform und Aufgabe des SDK.

CLR und BCL: Motor und Werkzeuge deines Alltags

CLR, Common Language Runtime, ist die Laufzeitumgebung für verwalteten Code. Im modernen .NET läuft auf einem typischen Server die Implementierung CoreCLR. Die Runtime kümmert sich unter anderem um Codeausführung, Ausnahmebehandlung, Typsystem und automatische Speicherverwaltung. Details unterscheiden sich je nach Plattform und Veröffentlichungsmodell.

BCL, Base Class Library, liefert grundlegende Typen und Operationen. Wenn du string, List<T>, DateTimeOffset oder Dateioperationen über System.IOverwendest, nutzt du dieses Fundament. Deine eigene Klasse Booking beschreibt dein Geschäft. Die BCL liefert Bausteine, damit du Textverarbeitung, Collections und Datumsfunktionen nicht selbst schreiben musst.

Der praktische Unterschied: „Passende Runtime nicht gefunden“ ist eine andere Problemklasse als ein falscher Datumsvergleich bei einer Reservierung. Das Erste betrifft den Programmstart, das Zweite eine von uns geschriebene Regel. Eine gute Diagnose beginnt mit der Ebene, auf der der Fehler liegt.

Was passiert beim Klick auf „Starten“?

Im üblichen JIT-Modell erzeugt der C#-Compiler Zwischencode in IL und Metadaten. Während der Ausführung übersetzt der JIT-Compiler benötigten Code in Prozessorinstruktionen. Das ist der vereinfachte Weg; Runtime-Optimierungen sind vielfältiger als dieses eine Schaubild.

Du formulierstC#Regeln und Anwendungsfälle
→
Der Compiler erzeugtIL + MetadatenZwischencode und Typbeschreibung
→
Runtime + JITMaschinencodeInstruktionen für den Prozessor
Und Native AOT?

Native AOT erzeugt nativen Code beim Veröffentlichen und verwendet beim Start keinen JIT. Das kann Startzeit und Speicherverbrauch verbessern. Dafür sind Bibliothekskompatibilität, Reflection und Einschränkungen beim dynamischen Laden zu prüfen. Das Veröffentlichungsergebnis ist an Betriebssystem und Architektur gebunden. AOT verwendet weiterhin Runtime-Mechanismen einschließlich GC. Entscheide anhand von Messungen deiner konkreten Anwendung.

Der Garbage Collector räumt Speicher auf. Für Ressourcen bleiben wir verantwortlich.

Der GC kann Speicher von Objekten zurückgewinnen, die von den Wurzeln der Anwendung aus nicht mehr erreichbar sind. Hält ein großes Dictionary oder ein Event-Handler weiterhin eine Referenz, kann ein Objekt deutlich länger im Speicher bleiben als geplant. Automatische Speicherverwaltung ersetzt das Nachdenken über Datenlebenszyklen nicht.

Handles und externe Ressourcen sind ein anderes Thema. Eine Datei sollte nach Gebrauch geschlossen werden; ein freizugebendes Objekt ist gemäß seinem Vertrag zu verwenden. Die Konstrukte using und await using machen diese Absicht im Code sichtbar. Auch das ist Lesbarkeit: Wir sehen, wo eine Ressource gebraucht wird und wo ihre Lebensdauer endet.

Mehr dazu: .NET-Glossar, Ausführung verwalteten Codes, Native AOT und GC-Grundlagen.

02 / Versionen ohne Rätselraten

.NET Framework, .NET Core und .NET: Warum so viele Namen?

Du übernimmst eine Anwendung und findest im Repository net48, in einem anderen Projekt net8.0, während die gemeinsame Bibliothek auf netstandard2.0zielt. Diese Angaben bezeichnen unterschiedliche Kompilierungsziele. Ähnliche Namen bedeuten nicht, dass alle Projekte ohne Vorbereitung auf denselben Server umziehen können.

Die Tabelle lässt sich seitlich scrollen →

FamilieBedeutungIm Projekt prüfen
.NET FrameworkAn Windows gebundene Familie. Die neueste Version ist 4.8.1; der Support hängt vom unterstützten Betriebssystem ab.Windows-Abhängigkeiten, älteres ASP.NET, Drittbibliotheken und Deployment-Modell.
.NET CoreHistorische Reihe 1.0–3.1, entwickelt als plattformübergreifende Plattform.Upgrade-Plan. Der Support dieser Versionen ist beendet.
.NET 5 und neuerFortsetzung von .NET Core unter kürzerem Namen. Moderne Plattform für verschiedene Betriebssysteme und Anwendungstypen.Zielversion, Support, Pakete und Umgebungsanforderungen.
.NET StandardSpezifikation eines gemeinsamen API-Satzes für Bibliotheken. Keine Laufzeitumgebung für Anwendungen.Nutzer der Bibliothek. .NET Framework unterstützt .NET Standard 2.1 nicht.

Es gab kein „.NET Core 4“. Auf .NET Core 3.1 folgte .NET 5. ASP.NET Core und Entity Framework Core behielten „Core“ im Namen, daher begegnet es dir weiterhin in neuen Projekten. Die C#-Sprachversion ist nochmals etwas anderes: Ein Projekt kann eine bestimmte Sprachversion verwenden und zugleich auf ein bestimmtes Framework zielen.

Was heute wählen?

Stand 8. Oktober 2026 ist .NET 10 ein LTS-Release mit Support bis zum 14. November 2028. .NET 8 LTS und .NET 9 STS werden bis zum 10. November 2026 unterstützt. Für eine neue Anwendung lohnt sich die Prüfung von .NET 10 LTS einschließlich Bibliotheks- und Umgebungskompatibilität. In bestehenden Projekten auf 8 oder 9 ist jetzt ein konkreter Upgrade- und Verhaltenstestplan besonders wichtig.

.NET 11 ist zu diesem Zeitpunkt für November 2026 angekündigt. Dieser Beitrag behandelt Vorabversionen nicht als Standard für Produktion. Prüfe bei späterer Lektüre die aktuelle Microsoft-Tabelle; die Daten hier beschreiben den Stand bei Erstellung.

Ein guter Migrationsplan beginnt mit einer Bestandsaufnahme. Erfasse Pakete, Integrationen, Windows-Abhängigkeiten, Build-Verfahren und geschäftskritische Szenarien. Starte dann die Tests und plane eine Rückkehr zur vorherigen Version. Nur net48 gegen net10.0 in der Projektdatei auszutauschen beantwortet diese Fragen nicht.

Wenn du eine gemeinsam mit einer .NET-Framework-Anwendung genutzte Bibliothek pflegst, kann netstandard2.0 weiterhin sinnvoll sein. Dient sie ausschließlich modernem .NET, erschließt ein modernes Ziel dessen APIs. Entscheidend sind die Nutzer der Bibliothek, nicht allein das Alter eines Namens.

Quellen zu Versionen: offizielle .NET-Supportrichtlinie, .NET-Framework-Support und .NET Standard.

Vom Entwicklerschreibtisch / Montag, 10:05

„Ich habe drei Stellen gefunden. Jetzt muss ich klären, welche davon stimmt.“

Das Team kehrt zur Stornierungsregel zurück. Eine Methode vergleicht Stunden, eine andere Kalendertage, die dritte nur den Reservierungsstatus. Das aktuelle Verhalten zu verstehen dauert länger, als eine neue Bedingung zu schreiben. Hier beginnt das Gespräch über sauberen Code.

03 / Lesbarkeit, die Änderungen erleichtert

Was bedeutet „sauberer Code“ eigentlich?

Betrachte den Code mit den Augen der Person, die in sechs Monaten einen Fehler behebt. Vielleicht ist es ein neues Teammitglied. Vielleicht du selbst nach anderen Projekten und einer Woche Urlaub. Findet sie die Regel schnell? Versteht sie, woher die Zahl 24 kommt? Kann sie die Korrektur prüfen, ohne sich manuell durch die ganze Anwendung zu klicken?

Lesbarkeit entsteht durch kleine Entscheidungen. Der Name CanCancel sagt mehr als Check. Der Parameter minimumNotice erklärt den Sinn eines zuvor nackten Zahlenwerts. Eine kurze Methode hilft, wenn sie eine zusammenhängende Aufgabe erfüllt. Ein paar einfache Zeilen auf ein Dutzend Methoden zu verteilen kann das Lesen erschweren.

In unserem Beispiel legt der Inhaber einer Fahrradwerkstatt fest: Kunden dürfen einen Termin spätestens 24 Stunden vor Beginn stornieren. Die Grenzen müssen klar sein: Genau 24 Stunden sind erlaubt, 24 Stunden minus ein Zeittakt nicht. Eine erneute Stornierung einer bereits stornierten Reservierung soll ein eindeutiges Ergebnis ohne erneute Nebenwirkungen liefern.

Vorher · du errätst die Bedeutung

if (b.S == 0 &&
    (b.T - DateTime.Now).TotalHours > 24)
{
    b.S = 1;
}
Zuerst die Fragen

Formuliere die Regel als Satz

„Eine bestätigte Reservierung darf mindestens 24 Stunden vor dem Termin storniert werden.“

Lege Statuswerte, Zeitgrenze und Wiederholungsverhalten fest. Wähle erst danach Namen und Typen.

In diesem kurzen Ausschnitt stecken mehrere nicht sofort sichtbare Entscheidungen. Was bedeutet Status null? Warum ist der Operator strenger als die vereinbarte Regel? Ist die Zeit lokal? Woher kommt „jetzt“ im Test? Bessere Einrückung beantwortet keine dieser Fragen.

Ein Kommentar sollte den Entscheidungsgrund bewahren

Halte fest, warum die Werkstatt 24 Stunden braucht: etwa um den Arbeitsplatz freizugeben und den Mechaniker zu informieren. Ein solcher Kommentar hilft bei späteren Regeländerungen. „Ändert den Status auf storniert“ über der Methode Cancel wiederholt meist nur den Code.

Ähnlich verhält es sich mit Duplikation. Dieselbe Geschäftsregel sollte eine erkennbare Quelle haben. Zwei ähnliche Ausschnitte für unterschiedliche Abläufe sollten jedoch nicht immer zusammengelegt werden. Die Abrechnung eines privaten Termins und die eines Flottenvertrags können heute ähnlich aussehen, sich aber nach anderen Regeln entwickeln. Ein abstrakter gemeinsamer „Prozess für alles“ verdeckt oft genau diesen Unterschied.

Hilft SOLID noch?

Ja, wenn du die Prinzipien als Fragen nutzt. SRP zeigt, dass Stornierungsregeln und Mailanbieter unterschiedliche Verantwortliche haben. ISP fördert kleine Verträge für konkrete Nutzer. DIP trennt Anwendungsfälle von Speicherungsdetails. LSP erinnert daran, dass Implementierungen ihre Vertragsversprechen einhalten müssen. OCP lenkt den Blick auf Erweiterungen an tatsächlich häufig wechselnden Stellen. Jede neue Schicht sollte jedoch ein bestehendes Problem lösen; ihre Kosten müssen für das Team verständlich sein.

Zuerst die Regel. Dann ein bequemer Weg, sie zu nutzen.

Bevor wir Datenbank, Controller und E-Mail ergänzen, können wir die Entscheidung selbst prüfen. Der folgende Ausschnitt ist ein eigenständiges Beispiel einer Zeitregel. Das vollständige Downloadprojekt erweitert sie später um Reservierungszustand und Anwendungsfall.

C# · ausdrückliche Regel und ausdrückliche Zeit

public static bool HasEnoughNotice(
    DateTimeOffset startsAt,
    DateTimeOffset now,
    TimeSpan minimumNotice)
{
    if (minimumNotice < TimeSpan.Zero)
        throw new ArgumentOutOfRangeException(
            nameof(minimumNotice));

    return startsAt - now >= minimumNotice;
}

var allowed = HasEnoughNotice(
    startsAt,
    now,
    TimeSpan.FromHours(24));

Wir übergeben Zeit als Wert. Der Test muss weder bis morgen warten noch die Rechneruhr verstellen. DateTimeOffset beschreibt einen Zeitpunkt einschließlich UTC-Offset; der Vergleich berücksichtigt diesen. Es ist jedoch kein vollständiges Zeitzonenmodell. Reserviert jemand wiederkehrende Termine „um 9:00 in Warschau“, brauchen wir auch die Regeln dieser Zone einschließlich Zeitumstellung.

Die Geschäftsregel wurde auf 24 Stunden tatsächlich verbleibender Zeit präzisiert. Hieße sie „bis Ende des vorherigen Arbeitstags“, sähe das Modell anders aus. Feiertagskalender, Zeitzone und Öffnungszeiten wären Teil des Problems. Gute Implementierung beginnt mit der richtigen fachlichen Frage.

„Jetzt“ darf nicht aus dem Kundenformular kommen. Die Anwendung wählt eine vertrauenswürdige Zeitquelle. In .NET lässt sich TimeProviderverwenden und im Test kontrollieren. Der Nutzer liefert die Reservierungs-ID; der Server bestimmt die Zeit und prüft Berechtigungen.

Braucht eine solche Methode sofort ein eigenes Interface? Nicht unbedingt. Eine reine Funktion auf Werten ist leicht auszuführen und zu verstehen. Ein Interface hilft vor allem an Grenzen zu austauschbaren oder zu verbergenden Abhängigkeiten: Datenspeicher, Uhr, externer Dienst. Zu viele Verträge verursachen eigene Lesekosten.

Zeit kontrollieren: TimeProvider in der Microsoft-Dokumentation.

04 / Verantwortungsgrenzen

C# Clean Architecture in der Praxis

Die Anwendung wächst. Supportbereich, Kalender, Benachrichtigungen und Zahlungsintegration kommen hinzu. Wir möchten die Stornierungsregel weiterhin verstehen, ohne die Mailkonfiguration zu lesen. Clean Architecture ordnet Abhängigkeiten so, dass technische Details nicht die Geschäftslogik beherrschen.

Die wichtigste Regel betrifft die Richtung der Codeabhängigkeiten: Sie zeigen nach innen, zu Regeln und Anwendungsfällen. Die Domäne muss weder HTTP-Controller noch Entity Framework kennen. Ein Anwendungsfall kann den benötigten Speichervertrag definieren; die Infrastruktur implementiert ihn.

An der Grenze zur Umgebung

API / Oberfläche und Infrastructure

HTTP, Datenbankzugriff, E-Mail, externe Dienste. Adapter übersetzen die Außenwelt in die Bedürfnisse der Anwendung.

Anwendungsfall

Application

„Reservierung stornieren“: laden, Domäne fragen, Speichern versuchen, Ergebnis zurückgeben.

Geschäftsregel

Domain

Reservierungsstatus, Termin und erforderlicher Vorlauf. Ohne Abhängigkeit von HTTP oder einer konkreten Datenbank.

Das Diagramm zeigt Codeabhängigkeiten. Der Aufruffluss kann zur Laufzeit von Application über ein Interface zum Infrastructure-Adapter führen.

Dieser letzte Punkt sorgt oft für Verwirrung. „Die Anwendung muss doch die Datenbank aufrufen!“ Ja, sie muss aber die konkrete Datenbankklasse nicht kennen. Das Interface ist der Vertrag, der Adapter erfüllt ihn. Der Startcode, manchmal Composition Root genannt, verbindet die Teile durch Dependency Injection.

In einem kleinen Projekt können Module und Ordner zunächst Grenzen ausdrücken. Separate Projekte helfen später, Referenzregeln beim Kompilieren zu erzwingen. Entscheidend ist, dass der Ordner Domain nicht heimlich den Datenbankkontext importiert und der Name Application nicht zur neuen Schublade für alles wird.

Beispielhafte Aufteilung · Projektabhängigkeiten

Domain          → .NET-Basisbibliotheken
Application     → Domain
Infrastructure  → Application, Domain
Demo / API      → Application, Infrastructure
                  + Registrierung der Abhängigkeiten

Die Anwendung kann weiterhin ein einziger deploybarer Prozess bleiben. Ein modularer Monolith bietet oft genug Kontrolle über Grenzen, ohne zusätzliche Netzwerkkommunikation zwischen Diensten. Einen Microservice auszugliedern lohnt sich erst bei konkreten Gründen: Teamverantwortung, unabhängigen Deployments oder unterschiedlichen Betriebsanforderungen.

Abhängigkeitsrichtung und Aufteilungsbeispiele: Webanwendungsarchitekturen in der Microsoft-Dokumentation.

05 / Ein Anwendungsfall

Verfolgen wir eine Stornierung von Anfang bis Ende

Der Kunde wählt eine Reservierung und klickt auf „Stornieren“. In einer vollständigen Anwendung prüft die Eingangsschicht zunächst seine Identität und sein Recht auf genau diese Reservierung. Eine gültige ID allein ist keine Berechtigung. Anschließend übergibt sie die Anfrage an den Anwendungsfall, der den Vorgang steuert.

  1. Eingang.Die API liest die ID, validiert die Anfrage und prüft die Ressourcenautorisierung.
  2. Lesen.Application lädt die Reservierung über den Repository-Vertrag. Ein fehlender Datensatz ist ein ausdrückliches Ergebnis.
  3. Entscheidung.Die Domäne prüft Zustand und Zeit. Hier liegt die 24-Stunden-Regel.
  4. Speichern.Der Adapter versucht, den neuen Zustand mit Versionskontrolle zu sichern. Ein Konflikt ist kein stiller Erfolg.
  5. Antwort.Die API übersetzt das Ergebnis in den vereinbarten HTTP-Vertrag. Die Oberfläche zeigt, was passiert ist.

Jetzt lohnt es sich, die Vorgangsergebnisse zu benennen. Cancelled, AlreadyCancelled, TooLate, NotFound und Conflict zeigen, womit wir es zu tun haben. Eine erwartete fachliche Ablehnung muss keine Exception sein. Ein unerwarteter Datenbankverbindungsfehler braucht eine eigene Behandlung und darf nicht so tun, als sei die Reservierung einfach nicht gefunden worden.

Sieh dir den Domänenausschnitt aus dem Downloadprojekt an

Domain/Booking.cs · Methode der Klasse Booking

public BookingCancellation Cancel(
    DateTimeOffset now,
    CancellationPolicy policy)
{
    if (Status == BookingStatus.Cancelled)
        return new(this,
            CancellationDecision.AlreadyCancelled);

    if (StartsAt <= now)
        return new(this,
            CancellationDecision.VisitStarted);

    if (StartsAt - now < policy.MinimumNotice)
        return new(this,
            CancellationDecision.TooLate);

    return new(
        this with { Status = BookingStatus.Cancelled },
        CancellationDecision.Cancelled);
}

Im Beispiel ist Booking ein Record. Eine erfolgreiche Entscheidung erzeugt einen neuen Wert mit storniertem Status. Andere Entscheidungen behalten das bestehende Objekt. Vollständige Typen, Konstruktoren und Verträge findest du im Archiv.

Was bringt der Repository-Vertrag?

Unser Anwendungsfall braucht zwei Fähigkeiten: eine Reservierung finden und den neuen Zustand mit einer erwarteten Version speichern. Ein solcher kleiner Vertrag ist lesbar. Wir bauen kein universelles Repository für jeden Datentyp mit Dutzenden hier ungenutzten Methoden.

In einer echten Anwendung kann ein anderer Kompromiss sinnvoll sein, etwa EF Core direkt in einem bestimmten Modul. Dann müssen Abhängigkeit und Teststrategie bewusst gewählt werden. Clean Architecture verlangt weder eine bestimmte Bibliothek noch einen Mediator oder ein Interface für jede Klasse. Sie soll dem Team helfen, verständliche Grenzen zu erhalten.

Was, wenn zwei Personen gleichzeitig klicken?

Ein Leseergebnis kann nur kurz aktuell sein. Zwischen Laden und Speichern ändert jemand anderes den Zustand. Deshalb übergeben wir die gelesene Datensatzversion beim Speicherversuch. Der Adapter muss erkennen, dass diese Version nicht mehr gilt. Der Anwendungscode muss entscheiden können, ob er erneut liest oder einen Konflikt zurückgibt.

Im Produktionsadapter muss der Schreibmechanismus die Versionsbedingung atomar prüfen. Eine separate Frage „Stimmt die Version?“ schützt das spätere Update nicht. Das In-Memory-Beispiel zeigt den Vertrag, ersetzt aber keine Transaktions- und Nebenläufigkeitstests mit der echten Datenbank-Engine.

Siehe: Nebenläufigkeitskonflikte in EF Core behandeln.

Vom Entwicklerschreibtisch / Freitag, 16:40

Reservierung storniert. E-Mail nicht angekommen. Was soll der Kunde sehen?

Der Mailanbieter antwortet nach dem Datenbank-Commit nicht mehr. Der Button wartet weiter. Der Kunde aktualisiert die Seite und versucht es erneut. Jetzt zählen Transaktionsgrenzen und Wiederholungsverhalten mehr als ein elegant gezeichnetes Diagramm.

Schöner Code muss auf die wirkliche Welt treffen

Eine Reservierung zu speichern und über einen externen Anbieter eine Nachricht zu senden bildet gewöhnlich keine gemeinsame Transaktion. Behandeln wir beides ohne Ausfallplan als untrennbaren Vorgang, zeigen wir möglicherweise einen Fehler trotz erfolgreich gespeicherter Änderung. Der Nutzer weiß dann nicht, ob er die Anfrage wiederholen soll.

Eine Lösung ist die Outbox: Zusammen mit der Zustandsänderung speichern wir in derselben Transaktion den Auftrag zur Benachrichtigung. Ein separater Prozess versucht die Zustellung und dokumentiert den Fortschritt. Fragen zu Wiederholungen, Duplikaten, Vorgangs-IDs, Versuchslimits und festhängenden Aufgaben bleiben jedoch. Eine Tabelle „Outbox“ zu nennen garantiert keine einmalige Zustellung.

Im Workshop endet das Beispiel bewusst vor der Mailintegration. Wir wollen eine Grenze gut verstehen und den Umfang danach ausdrücklich erweitern. Der Downloadcode enthält keine produktive Anmeldung, Eigentümerprüfung für Reservierungen, SQL-Datenbank oder Zahlungen. Diese Teile müssen für den konkreten Dienst entworfen und geprüft werden.

Auch Async, Exceptions und Logs beeinflussen die Lesbarkeit

Bei Ein- und Ausgabeoperationen solltest du konsequent CancellationToken weiterreichen und asynchrone APIs verwenden. Das Wort async beschleunigt Berechnungen nicht von selbst und startet nicht automatisch einen neuen Thread. Es beschreibt, wie Code während des Wartens auf einen Vorgang zusammenarbeitet.

Fehlerbehandlung braucht dieselbe Disziplin. Ein leerer catch kann einen Alarm verstummen lassen, lässt den Nutzer aber in einem unklaren Zustand. Ein Log sollte den Ablauf rekonstruierbar machen: Vorgangs-ID, Schritt und Ergebnis. Dafür braucht es weder das vollständige Passwort noch Token oder den gesamten Formularinhalt. Plane Diagnostizierbarkeit zusammen mit dem Anwendungsfall.

Technischer Hintergrund: Teilausfälle und Anwendungsresilienz.

06 / Prüfen, bevor eine Änderung als fertig gilt

Tests, die Fragen des Teams beantworten

Am leichtesten schreibt man einen Test für den glücklichen Fall. In unserer Werkstatt lässt sich eine Reservierung für nächste Woche stornieren. Das ist ein guter Anfang, doch die meisten Missverständnisse stecken an Regelgrenzen und in wiederholten Vorgängen.

Die Tabelle lässt sich seitlich scrollen →

SituationErwartetes VerhaltenWas wir prüfen
Genau 24 Stunden bis zum TerminStornierung erlaubtDie vertraglich festgelegte Grenze
24 Stunden minus ein TickAblehnung: zu spätExakter Vergleich ohne Rundung
Derselbe Zeitpunkt, anderer UTC-OffsetDasselbe ErgebnisZeitpunkte statt Uhranzeigen vergleichen
Reservierung bereits storniertAusdrückliches WiederholungsergebnisKeine erneute Zustandsänderung
Datensatz nach dem Lesen geändertErkannter KonfliktVertrag der Versionskontrolle
Erfolgreiche Stornierung und erneutes LesenStornierter Zustand sichtbarTatsächliches Speichern im verwendeten Adapter

Ein Domänentest läuft ohne Server. Ein Anwendungsfalltest prüft das Zusammenspiel von Regel, Uhr und Speichervertrag. Ein Integrationstest des Adapters beantwortet, ob Abfragen, Transaktionen und Constraints auf der gewählten Datenbank-Engine funktionieren. Ein Nutzerpfadtest prüft schließlich, ob die Oberfläche Erfolg, Ablehnung und Fehler anzeigen kann.

Verwechsle diese Nachweise nicht. Ein grüner Test mit einer In-Memory-Collection bestätigt keine SQL-Abfrage. Eine andere Testdatenbank-Engine kann Vergleiche, Typen und Transaktionen anders behandeln. Auch hundert Prozent Zeilenabdeckung beantworten allein nicht, ob die wichtigsten Risiken geprüft wurden.

Workshop zum Herunterladen · C#

Fahrradwerkstatt: kleines Projekt, vollständiger Weg einer Entscheidung

Das Archiv enthält Domain, Application, einen In-Memory-Adapter, ein Demoprogramm und Prüfszenarien. Ohne zusätzliche NuGet-Pakete. Du kannst Abhängigkeiten verfolgen, Regeln ändern und sehen, welche Erwartungen danach nicht mehr gelten.

C#-Projekt und Anleitung als ZIP herunterladen ↓

Zielframework: .NET 10. Die README beschreibt Voraussetzungen, Start, durchgeführte Prüfung und Grenzen der Demonstration.

Eine Übung für eine ruhige halbe Stunde

Der Inhaber möchte den Vorlauf von 24 auf zwölf Stunden verkürzen. Notiere vor der Codeänderung Beispiele für genau zwölf Stunden und knapp darunter. Ändere dann die Policy und starte die Szenarien. Achte darauf, ob Controller, Adapter und Modell gleichzeitig bearbeitet werden müssen. Falls ja, suche die Stelle, an der die Regel ihren Verantwortungsbereich verlassen hat.

Der zweite Teil ist anspruchsvoller: Die neuen Regeln sollen nur für Reservierungen ab einem bestimmten Erstellungsdatum gelten. Eine globale Wertänderung könnte nun zuvor akzeptierte Bedingungen verletzen. Vielleicht muss die Reservierung ihre Policy-Version speichern. Das zeigt, warum wir nach der Bedeutung einer Änderung fragen sollten, bevor wir ihre Einfachheit im Code feiern.

Passende Nachweise auswählen: Teststrategie für Anwendungen mit EF Core.

Zeit für Praxis

Drei Entscheidungen am echten Schreibtisch

Wähle eine Antwort und lies die Begründung. Du kannst es erneut versuchen. Die Übung läuft lokal im Browser; sie ist weder Kompetenzbewertung noch Zertifikat.

1. Wo platzierst du die 24-Stunden-Regel?

Ausschließlich im Angular-Button und -Formular.

In der Domäne, aufgerufen durch den Anwendungsfall.

Wähle eine Antwort, um die Begründung zu sehen.

2. Du willst einen Transaktionsfehler im SQL-Adapter entdecken.

Ich ergänze einen Integrationstest mit der richtigen Datenbank-Engine.

Ich ersetze die Datenbank durch List<Booking> und hake das Thema ab.

Wähle eine Antwort, um die Begründung zu sehen.

3. Eine kleine Anwendung hat zwei einfache Formulare und ein Team.

Ich teile sie sofort in mehrere Microservices auf.

Ich beginne mit einfachen Modulen und prüfe, wo Grenzen nötig sind.

Wähle eine Antwort, um die Begründung zu sehen.

Geübte Entscheidungen: 0/3

Neu beginnen

Beantworte drei Fragen. Zu jeder Entscheidung erhältst du eine Erklärung.

Antworten und Erklärungen als Text

1. Die Regel gehört in die durch den Anwendungsfall aufgerufene Domäne. Formularvalidierung erleichtert die Bedienung, schützt aber den Server nicht. 2. Transaktionsverhalten braucht einen Adaptertest auf der richtigen Engine in einer separaten Testdatenbank. 3. Einfache Module können genügen; Grenzen verstärken wir bei Bedarf. Diese Antworten sind auch ohne interaktive Buttons zugänglich.

Vom Entwicklerschreibtisch / erster Mittwoch im neuen Team

„Ich habe CancelBooking gefunden. Ich sehe die Regel und den Test ihrer Grenze.“

Die neue Kollegin kennt die Projekthistorie noch nicht. Trotzdem kann sie die Änderungsstelle nennen und die Folgen erklären. Das ist ein gutes Qualitätssignal: Systemwissen steht im Code, in Tests und kurzer Dokumentation, statt ausschließlich im Kopf einer Person zu bleiben.

07 / Ein Projekt, das bereits Geld verdient

Wie beginnt Refactoring, ohne alles neu zu schreiben?

In einer bestehenden Anwendung bringt das ruhige Aufräumen eines häufig geänderten Ablaufs oft den größten Nutzen. Wähle eine Stelle mit regelmäßigen Fehlern, schwierigen Tests oder vielen Moduländerungen für einfache Wünsche. Zeichne statt eines Gesamtumbaus den Weg eines Vorgangs: vom Eingang über die Regel bis zu Speicherung und Antwort.

Halte zuerst fest, was die Anwendung heute tut. Charakterisierungstests erfassen das aktuelle Verhalten auch bei unvollständiger Dokumentation. Nicht jedes vorhandene Detail ist beabsichtigt; Abweichungen von Erwartungen müssen besprochen werden. Beim Refactoring ordnen wir die Struktur und bewahren das vereinbarte Verhalten. Änderungen der Geschäftsregeln sollten getrennt sein, damit klar ist, was freigegeben wird.

  1. Prozess erfassen.Eingänge, Daten, Regeln, Integrationen und Nebenwirkungen. Ergänze die fachlich verantwortliche Person.
  2. Bestehendes Verhalten absichern.Ergänze Tests wichtiger Szenarien und einen Weg, das gemeldete Problem nachzustellen.
  3. Eine Verantwortung herauslösen.Verschiebe Regel oder Adapter in einem kleinen Schritt. Halte die Anwendung nach jeder Änderung funktionsfähig.
  4. Grenzen prüfen.Ablehnung, fehlende Daten, Konflikt, Wiederholung und Ausfall einer Abhängigkeit. Richte Tests nach Risiken aus.
  5. Deployen und beobachten.Erhalte die Rückkehrmöglichkeit und Informationen zur Bewertung nach dem Deployment.

Code-Review als Gespräch über die nächste Änderung

Frage im Review: „Was geschieht, wenn das Speichern scheitert?“, „Kann diese Methode ein zweites Mal aufgerufen werden?“ und „Welcher Test bestätigt diese Grenze?“ Solche Fragen helfen, die Lösung zu präzisieren. Eine reine Diskussion über Klammerreihenfolgen bringt weniger; mechanische Formatierungsregeln können Werkzeuge übernehmen.

Eine gute Änderungsbeschreibung nennt in wenigen Sätzen Problem, neues Verhalten und Prüfung. Bei schwierigen Entscheidungen bewahrt eine kurze Architekturnotiz den Kontext: Was wurde gewählt, warum und wann sollte das Thema erneut geprüft werden? So muss die nächste Person keine wochenlange Chatdiskussion rekonstruieren.

Kurze Liste für den nächsten Pull Request
  • Beschreiben die Namen fachlich verwendete Begriffe?
  • Hat die Regel eine erkennbare Quelle?
  • Lassen sich Zeit und Abhängigkeiten im Test kontrollieren?
  • Haben Ablehnung, technischer Fehler und Konflikt verständliche Ergebnisse?
  • Haben wir das Ressourcenrecht geprüft, nicht nur die Anmeldung?
  • Ist klar, wie die Änderung sicher ausgerollt und zurückgenommen wird?
  • Sind Testgrenzen und verbleibende Risiken beschrieben?

Wann reicht eine einfachere Lösung?

Nicht jede Anwendung braucht ein umfangreiches Domänenmodell. Ein Bereich mit wenigen einfachen CRUD-Vorgängen kann mit wenigen Schichten gut lesbar sein. Gut benannte Anwendungsfälle, konsequente Validierung und Tests kritischer Pfade reichen häufig als Ausgangspunkt.

Stärkere Grenzen werden wertvoll, wenn Regeln, Integrationen, Eingangskanäle oder Teams zunehmen. Dann können die Kosten dafür, herauszufinden, was geändert werden darf, höher sein als die zusätzlicher Verträge. Die Architektur sollte zur konkreten Situation passen.

Lass außerdem Raum für schrittweise Entwicklung. Du musst nicht jeden künftigen Zahlungsanbieter vorwegnehmen. Du kannst aber deinen Abrechnungsprozess von den Details des heutigen SDK trennen. Bei einem Anbieterwechsel weißt du dann, wo die Analyse beginnt. Unterschiede in Verträgen, Daten und Verhalten bleiben zu prüfen – ein Interface beseitigt sie nicht.

Eine Frage zum Mitnehmen

Wenn sich morgen eine Regel ändert: Weiß das Team, wo es sie anpassen und wie es bestätigen kann, dass der Rest weiter funktioniert?

Von der Lektüre zum eigenen Projekt

Hast du ein System, in dem jede Änderung weitere Korrekturen nach sich zieht?

Zeige uns einen konkreten Prozess: Anmeldung, Reservierung, Bestellung, Zahlung oder Integration. Bei ArcCloud Studio arbeiten wir mit C#, .NET, Angular sowie AWS und Azure. Wir können prüfen, wo Verantwortungen vermischt werden, welche Probleme Nutzer betreffen und wo Reparaturen sinnvoll beginnen.

01 / Diagnose

Wir klären, was geprüft wird

Geschäftsziel, Symptome, vorhandene Unterlagen und vereinbarter Umfang. Zugänge und Vertraulichkeit klären wir vor der Weitergabe sensibler Informationen.

02 / Plan

Wir beschreiben konkrete Maßnahmen

Probleme, Auswirkungen, Prioritäten, vorgeschlagene Korrekturen und Abnahmekriterien. Implementierungsumfang, Preis und Termin stimmen wir gesondert ab.

03 / Umsetzung

Wir prüfen die Wirkung der Änderung

Nach Freigabe können wir Korrekturen umsetzen, vereinbarte Tests durchführen und die für das Team nötige Dokumentation übergeben.

Architektur- und Code-Reviews erfordern passende Unterlagen und einen gemeinsam vereinbarten Umfang. Eine automatische Messung einer öffentlichen URL ersetzt diese Arbeit nicht.

Für die erste Nachricht reichen Problembeschreibung, eingesetzte Technologien und gewünschtes Ergebnis. Sende keine Passwörter oder Zugangsschlüssel über das öffentliche Formular.

Wenn du einen Schritt weitergehen möchtest

Kehre zum Diagramm zurück und verfolge einen Prozess in deinem Projekt. Lies danach unseren Beitrag über Architekturentscheidungen und ihre dokumentierten Gründe oder den Workshop zu Queues, Wiederholungen und resilienten Integrationen.

Microsoft-Quellen sind in den jeweiligen Kapiteln verlinkt. Geschichten, Workshop-Aufbau und Democode wurden für diesen Beitrag erstellt. Die Titelgrafik ist eine mit KI erstellte konzeptionelle Illustration; sie zeigt kein Kundensystem.

Wenn du nach der Lektüre Sprache, Runtime und Bibliotheken unterscheiden, die Verantwortung einer Regel nennen und einen risikogerechten Test wählen kannst, hast du einen guten Ausgangspunkt für die nächste Änderung. Beginne mit einem Prozess. Beobachte, was verständlicher wird.