In dieser Artikelserie möchten wir euch einen Einblick geben, wie typische Projekte aus technologischer Sicht bei uns aussehen. Wir betrachten dazu in einzelnen kurzen Artikeln unterschiedliche Tech-Stacks. Wir berücksichtigen dabei auf keinen Fall all unsere Projekte, möchten aber zeigen, wie viele oder gar die meisten jüngeren Projekte in diesem Bereich aufgebaut sind. Es geht uns hier also weniger um die Bandbreite aller Projekte, sondern eher häufig auftretende Gemeinsamkeiten. Auch interessieren uns hier mehr die jüngeren Projekte als die älteren. In diesem Artikel soll es dabei um den .NET Tech-Stack gehen. Und da wir von jüngeren Projekten sprechen, geht es natürlich hauptsächlich um .NET Core 🙂.

.NET Core

Seit .NET Core und besonders ab Version 3.0 erfreuen sich .NET Projekte erhöhter Nachfrage durch Kunden:innen und größerer Beliebtheit unter unseren Entwickler:innen. Einer der Gründe hierfür ist definitiv die Cross-Plattform Verfügbarkeit. Wir betreuen durchaus klassische .NET Projekte, die wir in der Vergangenheit aufgebaut haben. Da es sich dabei in der Regel um größere Anwendungen handelt, werden diese auch in dieser Form weiter gepflegt. Neue Projekte werden allerdings nur noch in .NET Core umgesetzt.

.NET Core wird inzwischen als Open-Source-Projekt auf Github gepflegt und auch weitgehend mit der entsprechenden Philosophie von seinen Entwicklern behandelt. Das gilt erfreulicherweise auch für die ausführliche Dokumentation, die direkt verlinkt eventuelle Github Issues beinhaltet, die auf mögliche Errata hinweisen können.

Basistechnologie

Das Release der .NET Core Version 3.0 im September 2019 brachte einige erhebliche Neuerungen mit sich. Hierzu zählt unter anderem der Support für .NET Standard 2.1 mit den speichereffizienten Span<T> Typen, Trimming von ungenutzten Codeteilen für self-contained Apps und ReadyToRun Images. Wem das alles nichts sagt, kann sich auch die vollständigen Versionshinweise durchlesen.

Einige Projekte konnten wir bereits mit dem Release Candidate der Version 3.0 starten, weil wir wussten, dass die Laufzeit des Projekts definitiv über den Release der 3.0 hinausgehen würde. Seitdem starten wir neue Projekte mit der aktuellen Version 3.1 (aktuell 3.1.3). Die Version 3.1 ist eine LTS Version und bekommt damit für mindestens drei Jahre Support. Der neue Release-Plan von Microsoft sieht vor jedes Jahr im November eine neue Hauptversion zu veröffentlichen.

Wenn wir von .NET-Core-Anwendungen sprechen, handelt es sich in unserem Umfeld vor allem um ASP.NET Core Anwendungen, die als API-Backend eingesetzt werden.

Third-Party-Erweiterungen

Wie in vielen anderen Sprachen und Frameworks setzen wir auch in .NET diverse Erweiterungen ein:

ORM

Ein Projekt ohne ORM wäre im .NET-Umfeld bei uns eine Rarität. Wir schätzen den Komfort und mitunter die Sicherheit, den diese Tools uns Entwicklern liefern.

  • EntityFramework (EF) Core wird in Projekten am häufigsten eingesetzt. Hier fehlen noch einige Funktionen, die das EntityFramework (ohne Core) unterstützt, es reift aber mit jedem neuen Release. Eine der wohl beliebtesten Funktionen ist hierbei das Erzeugen von Datenbank-Migrationen aus Code-Änderungen in Model-Klassen.
  • Dapper ist ein sehr minimalistischer Ansatz im Vergleich zu EF Core und hat ein deutlich kleineres Feature-Set. Das Framework ist bei uns nur selten in Verwendung. Unter anderem, weil die Abstraktion der SQL-Queries in anderen Tools gerne gesehen wird.

Zusätzlich kommt je nach eingesetzter Datenbank eventuell ein zusätzlicher Provider zum Einsatz. EF Core unterstützt beispielsweise nativ nur die Anbindung an MSSQL Datenbanken. Da wir häufig auch andere Datenbanken wie PostgreSQL einsetzen, kommt beispielsweise npgsql ebenso oft zum Einsatz.

Logging

Das standardmäßig gelieferte Logging-System von .NET Core ist ausreichend für simple Einsatzzwecke. Gerade, wenn einfach in ein lokales Dateisystem geloggt werden soll, erfüllt es seinen Zweck. Trotzdem kommen mitunter auch hier Erweiterungen zum Einsatz:

  • NLog ermöglicht das Logging in unterschiedlichen Formaten an diverse Ziele. Es kann beispielsweise in Format A auf eine virtuelle Konsole und in Format B direkt an einen Elastic Server geloggt werden.

HTTP-Client

Wenn unser .NET Backend mal selbst HTTP Requests ausführen muss …

  • bringt Polly einfache Möglichkeiten mit, die .NET-nativen HTTPClients um Funktionen wie Retry-Mechanismen und Circuit Breaker zu erweitern

Object-Mapping

  • Automapper wird vor allem verwendet, um Entity auf DTO-Objekte zu mappen und umgekehrt. Sind Felder identisch in Typ und Name, passiert dies mit sehr wenig Aufwand. Viele andere Sonderfälle können konfiguriert werden und Automapper lässt sich auch für andere, wahrscheinlich nicht intendierte Zwecke verwenden, wie etwa zwei Teil-Objekte zu einem neuen vollständigen zu kombinieren.

JSON Parsing

Da wir vor allem Web-APIs bauen, reden wir in der Regel von HTTP-basiertem Traffic und damit fast immer von JSON-Payloads, die verarbeitet werden müssen.

  • Newtonsoft Json.NET war lange Zeit der Platzhirsch und ist immer noch ein mächtiger Parser mit vielen Konfigurationsmöglichkeiten.
  • System.Text.Json ist jetzt bei uns oft erste Wahl. Nicht zuletzt, weil es der neue standardmäßig integrierte Parser ist. Der ist aktuell noch nicht in jeder Hinsicht ausgereift, lässt sich aber ebenfalls in vielen Aspekten erweitern.
  • Utf8Json kann eingesetzt werden, wenn Performance die oberste Priorität ist.

Testing

Für die simpleren Tests verwenden wir gerne und häufig die folgenden Helfer:

  • xUnit hat bei uns mit seinen Facts und Theories den Vorzug über nUnit gefunden. Die Theories ermöglichen es uns auf einfache Weise Testfunktionen mit variablen Inputs zu füttern.
  • Shouldly macht Assertions einfacher zu lesen und zu schreiben
  • FakeItEasy hilft, sehr einfach Fake-Objekte zu erstellen, die als Mock-Input für Funktionen und ähnliches verwendet werden können.

Die genannten Libraries werden vor allem für Unit Tests verwendet. In Kombination mit der WebApplicationFactory können sie auch dazu verwendet werden, Integrationstests innerhalb eines Service-Scopes auszuführen. Die offizielle Dokumentation von Microsoft gibt hierzu auch einige Hinweise.

API Dokumentation

Da geschriebene API-Endpunkte letztendlich ja auch von jemandem verwendet werden sollen, sollten sie möglichst gut dokumentiert sein. Das erreichen wir meist mit Swashbuckle. Die Library liefert uns einen Swagger-Endpunkt mit einer OpenAPI-konformen Dokumentation der API im JSON-Format. Das funktioniert besonders gut, wenn man hierzu noch die XML-Docs aktiviert und die Endpunkte entsprechend kommentiert. Auf Wunsch lässt sich hierzu auch eine Swagger-UI aktivieren, die Endpunkte anzeigt. Mittels der genannten UI lassen sich auch Requests gegen die Endpunkte mit anpassbaren Daten fahren und bestimmte Fälle testen.

Tooling

Auch wenn Windows das intuitiv passende Betriebssystem für .NET-Entwicklung zu sein scheint, ist die Entwicklung unter macOS oder Linux inzwischen genauso gut möglich. Man kann jetzt (dank der Core-Variante) frei nach persönlichen Vorlieben entscheiden und muss keine Kompromisse eingehen.

Die IDE, die im .NET-Kosmos wohl schon am längsten beheimatet ist und damit der Klassiker ist Visual Studio, das immer noch gerne verwendet wird. Durch Visual Studio for Mac haben inzwischen auch Mac-Nutzer:innen die Möglichkeit, das Tool (mit leichten Änderungen) einzusetzen. Bei uns wird das Programm inzwischen immer seltener eingesetzt – vor allem durch die sehr guten Alternativen bedingt. Wo es im Einsatz ist, meist mit ReSharper von Jetbrains.

Auch wenn der Name Visual Studio Code an das zuvor genannte klassische Visual Studio erinnert, ist es eine eigenständige Neuentwicklung und die beiden Tools sind nur bedingt miteinander verwandt. Die IDE ist auf allen drei genannten Plattformen verfügbar und danke vieler verfügbaren Extensions sehr mächtig und vielseitig einsetzbar. Beispielhaft sind im genannten Kontext die Azure Extensions zu nennen.

Im .NET-Umfeld ist bei uns sehr weit verbreitet Jetbrains Rider im Einsatz, egal welches Betriebssystem darunter läuft. Visual Studio wird, wenn überhaupt ausschließlich mit Resharper verwendet

Von Jetbrains ist bei uns sehr häufig Rider im Einsatz, egal welches Betriebssystem darunter läuft. Vor allem, wenn man nicht nur .NET-Entwicklung betreibt, liefern die Jetbrains IDEs eine einheitliche Umgebung für unterschiedliche Sprachen und Frameworks.

Neben den jeweiligen IDEs sind vor allem diverse CLIs in Verwendung. In der Regel vor allem die .NET CLI und die dazu gehörige EF-Core-Erweiterung. Zusätzlich je nach Hosting-Umgebung meist CLIs für Azure, Docker und/oder Kubernetes.

Hosting und Operations

Public Cloud

Obwohl die meisten Public-Cloud-Anbieter das Hosting von .NET Anwendungen ermöglichen, gehen bei uns oft .NET und Azure Hand in Hand. Das empfohlene Modell zum Hosten von .NET Core ist Docker, wodurch eine Wechsel auf einen anderen Cloud-Anbieter vergleichsweise einfach bleibt. Selbstgehostete Lösungen wurden mit der Zeit immer seltener und sind nur noch selten anzutreffen.

Development

Wenn wir das Projekt ohnehin in Azure aufbauen, setzen wir zudem meist auf Azure DevOps, insbesondere wegen der sehr guten Integration der beiden Plattformen. Nicht alle Funktionen oder ihre Bedienbarkeit sind hundertprozentig ausgereift, vor allem an Stellen, an denen eine Konfiguration lange nur über das Web-Portal und nicht durch Code konfigurierbar war. DevOps bietet aber inzwischen sehr gute Werkzeuge, um CI/CD Pipelines (auch in Code-Form) aufzubauen.

App Hosting

Eine der einfachsten Hosting-Methoden für (ASP).NET Core Services sind die App Services von Azure. Diese können entweder mit Docker Images oder direkt mit den Build-Artefakten der Anwendungen bespielt werden und sind sehr gut integriert. SSl-Zertifikate bekommt man zum Hosting der Anwendung dazu und die Verwendung eigener Domains ist schmerzfrei. Die App Services können direkt aus den DevOps Pipelines heraus befüllt werden; im Falle von Images kann entsprechend die Version erhöht werden. Werden Docker Images verwendet, so können diese aus der Pipeline zunächst in eine Azure Container Registry geladen werden, von wo sich der App Service selbst das Image in der gewünschten Version laden kann.

Alternativ zu den genannten häufigsten Vertretern kommen auch Azure Functions als Alternative zum Hosting auf Serverless-Basis zum Einsatz. Diese können entweder stateless oder durable eingesetzt werden. Außerdem setzen wir in größeren Projekten auf den Azure Kubernetes Service. Hier kommen auch wieder Deployments mit Images zum Einsatz, die über die Azure Container Registry als Zwischenstation laufen.

Database

Azure Database for PostgreSQL ist die managed Alternative zur selbstgehosteten Postgres-Datenbank. Postgres ist günstiger als vergleichbare MS-SQL-Varianten und kann für die Entwicklung einfach containerisiert auf den Entwicklungsrechnern hochgefahren werden. Im Umfeld der Public Cloud sind die managed Varianten der meisten Services wie Datenbanken fast immer die erste Wahl.

Secret Management

Azure Key Vault bietet die Möglichkeit, im Umfeld von Azure und Azure DevOps einfach und gut integriert Secrets und Zertifikate zu verwalten. Wir benutzen Key Vault gerne für diverse Teile unserer Infrastruktur: sowohl in Pipelines aus Dev Ops als auch in Services und Anwendungen. Durch das Options Pattern in ASP.NET Core können beispielsweise Secrets automatisch aus dem KeyVault in den AppService gezogen und in der Anwendung geladen werden. Der Einrichtungsaufwand hierfür ist minimal.

Security

Die Anforderungen in diesem Bereich sind offensichtlich besonders kritisch und sehr stark projektabhängig. Einige wenige erkennbare Überschneidungen gibt es aber.

Einer der Gründe für die Nutzung von Azure liegt oft darin, dass unsere Kunden bereits im Universum von Microsoft unterwegs sind und potenziell einige Ressourcen bereits in Azure liegen. Das bringt oft ein bestehendes Azure AD mit sich, das eingesetzt und angebunden werden kann oder soll.

Für kleinere Projekte eignet sich auch die in ASP.NET Core eingebaut Identity-Variante. Wenn es größer wird, dann kommen Anwendungen, wie Identity Server oder Keycloak zum Einsatz.

Logging und Monitoring

Logging lösen wir in unterschiedlichen Projekten sehr individuell. In kleineren Projekten kommt vor allem Application Insights als Teil des Azure-Monitor-Angebots zum Einsatz. Dieser bietet wohl die einfachste Integration in App Services und gleichzeitig out of the box schöne Features wie automatische WebServer Logs und Metriken über Response Codes. Daneben setzen wir auch häufig Lösungen auf Basis des Elastic Stacks und Prometheus ein. Ein beliebtes Monitoring-System ist zudem das allseits bekannte Grafana.

Zusammenfassung

Mit einer Mischung aus .NET Core und der Public Cloud in Form von Azure-Diensten fühlen wir uns sehr wohl. Auch wenn es einige andere Kombinationsmöglichkeiten gibt, die auch .NET-Entwicklung und -Hosting unterstützen, schließt hier interessanterweise an erster Stelle Microsoft selbst den Kreis, und das, obwohl ein großer Faktor für seine Beliebtheit bei uns die Cross-Platform-Fähigkeit von .NET Core ist.

Wir haben hier versucht euch einen Überblick über die technischen Komponenten eines typischen Projektes bei uns im Haus zu geben. Dabei haben wir bewusst nicht jeden Aspekt beleuchtet – auch weil es vereinzelt keine typische Wahl gibt. Wir hoffen, wir konnten euch einen guten Einblick geben!