Suchen

Mit dem Hoverboard über das Minenfeld .NET Core 3 und moderne Softwareentwicklung

| Autor / Redakteur: Lars Jacobi * / Stephan Augsten

Die Software-Entwicklung hat in ihrem Entwicklungsweg viele Phasen durchlaufen, die durchgehend von Lessons Learned geprägt sind. Welche Maßnahmen bündelt Microsoft aus ihrem Erfahrungsschatz in die neue Entwicklungsplattform .NET Core?

Firmen zum Thema

Das Modell der Zwiebel-Architektur lässt sich in .NET-Core-Projekten unkompliziert umsetzen.
Das Modell der Zwiebel-Architektur lässt sich in .NET-Core-Projekten unkompliziert umsetzen.
(Bild: k8_iv / Unsplash)

Bei der Entwicklung moderner, Mehrwert bringender IT-Produkte müssen im Vorfeld Entscheidungen gefällt werden, die für den weiteren Projektverlauf und seinen Erfolg von großer Bedeutung sind. Diese Entscheidungen werden meist von Managementpositionen aus getroffen, die – frech gesagt – nicht über das nötige Detailwissen verfügen.

Das müssen sie auch gar nicht! Sie können sich auf die Fachkompetenz der Kollegen verlassen, die im Lösungsbereich der Fragestellung agieren. Wie transportiere ich den Wunsch des Kunden am besten in unsere Entwicklungsabteilungen? Die Requirement Engineers können dazu mehr sagen. Welche Entwicklungsplattform sollten wir als Basis für unser Produkt nutzen? Die Experten aus der Software-Entwicklung können hier helfen.

Doch häufig sind die Empfehlungen der Kollegen für Führungsebenen schwer verständlich oder basieren nicht auf Fakten, sondern auf Meinungen. Darum soll am Fallbeispiel der Open-Source-Plattform .NET Core 3 von Microsoft veranschaulicht werden, mit welchen Herausforderungen die moderne Software-Entwicklung konfrontiert ist und welche Konzepte sich .NET Core zu Nutze macht, um sich diesen Anforderungen zu stellen.

Herausforderungen der modernen IT

Die Herausforderungen bei der Planung und Entwicklung eines Software-Projektes sind immer an Qualitätskriterien gebunden, die es zu erfüllen gilt. Erweiterbarkeit, Sicherheit, Wartbarkeit, Zuverlässigkeit, Performanz u.v.m. sind in einem heutigen Software-Produkt von immenser Bedeutung. Um solche Aspekte zu realisieren, haben sich verschiedene Konzepte und architektonische Modelle etabliert, deren Kerngedanken dieser Artikel erläutert.

Layering

3-Schichten-Architektur
3-Schichten-Architektur
(Bild: Method Park)

Vor einigen Jahrzehnten bestanden Programme nur aus einer einzigen Komponente mit sehr einfachen Aufgaben. Mit der steigenden Anzahl und Komplexität der Anforderungen an das Programm wuchs die Komponente zu einem Monolithen. Darum teilt man Software in verschiedene Komponenten mit eigenen Verantwortungsbereichen ein. Man spricht auch von Layering, da das Programm in verschiedene Schichten basierend auf ihrer Kernfunktion aufgebaut wird.

Ein gängiges Modell ist die 3-Schichten-Architektur. Wie der Name schon sagt, wird das Programm in drei Schichten, nämlich Präsentations-, Logik- sowie Datenhaltungsschicht oder auch Persistenzschicht unterteilt.

  • In der Präsentationsschicht befinden sich Module, die für die direkte Interaktion zwischen Benutzer und Applikation verantwortlich sind. Ihre Kernaufgaben bestehen in der Veranschaulichung von Daten und der Entgegennahme und Validierung von Nutzereingaben.
  • In der Logikschicht werden Module implementiert, die für die Anwendung der branchenspezifischen Geschäftslogik verantwortlich sind. Die Logikschicht bildet den Kern der Applikation.
  • In der Datenhaltungsschicht liegt der Fokus der implementierten Technologien auf dem Persistieren von Daten sowie deren Verfügbarkeit. Hier finden sich Module zur Anbindung von Datenbanken und ähnlichen Formen der Persistenz.

Das große Problem der Schichtenmodelle liegt in den Beziehungen der Schichten zueinander. Häufig wachsen diesen Applikationen mit der Zeit neue Schichten hinzu, beispielsweise eine Domänenschicht zum Anbinden interner oder externer Services. Die zentrale Problemfrage lautet: Wie stehen die Schichten miteinander in Verbindung und wie hängen sie inhärent voneinander ab?

Die Abhängigkeiten der Module und Schichten untereinander sind der Schlüssel zu einer flexiblen und erweiterbaren Software, denn ein unabhängiges Software-Modul ist austauschbar, somit einfacher zu warten und zu ersetzen.

Durch das Layering konnten größere und komplexere Software-Produkte realisiert werden, allerdings verknoteten sich die einzelnen Software-Komponenten schnell zu einer unflexiblen und untestbaren „Lösung“. Als Gegenmaßnahme stellt Robert C. Martin („Uncle Bob“) in „Clean Architecture“ (ISBN: 9783958457249) ein architektonisches Konzept vor, auf dem u.a. das Modell der Zwiebel-Architektur basiert.

Zwiebel-Architektur

Onion Architecture
Onion Architecture
(Bild: Method Park)

In der Zwiebel-Architektur sind die Abhängigkeiten der verschiedenen Schichten eines Software-Produkts nach innen gerichtet.

  • Im Kern der Software, der Domain, befinden sich die Geschäftsobjekte, mit denen die Software arbeitet.
  • In der Applikationsschicht ist die Geschäftslogik des Programms eingebettet. Sie arbeitet mit Geschäftsobjekten und ist somit direkt von der Domain abhängig. Diese beiden Schichten verkörpern den Kern des Programms.
  • Um die Applikationsschicht herum sind Schichten für Kernaspekte der Anwendung, wie bspw. das Persistieren von Daten oder die Präsentation von Daten über Schnittstellen, angebunden. Auch hier sind die Abhängigkeiten der Schichten nach innen zur Applikationsschicht gerichtet. Resultierend kann die Technologie zum Anzeigen der Daten ausgetauscht werden; gleichzeitig sind mehrere Arten der Präsentation möglich (Web-Applikation, Mobile App, Desktop Applikation). Gleiches gilt für die Datenhaltungsschicht (Datenbank, Filesystem etc.).

Diese Umkehrung der Abhängigkeiten trägt den Namen Dependency Inversion Principle und gehört zu den Clean-Code Prinzipien aus Uncle Bobs „Clean Code“ (ISBN-13: 9783826655487). Ein Konzept ist schön und gut, wie allerdings setzt man es konkret um? Die Antwort lautet: mit Dependency Injection.

Dependency Injection

Dependency Injection beschreibt ein Entwurfsmuster, das die Abhängigkeiten eines Objekts/Moduls zur Laufzeit reglementiert. Dieses Entwurfsmuster sorgt dafür, dass die Abhängigkeiten eines Objektes bei dessen Erzeugung (Initialisierung), also zur Laufzeit des Programms, in das Objekt hineingereicht werden. Somit ist es nicht mehr fest mit anderen Modulen verdrahtet, sondern kann flexibel umkonfiguriert werden.

Dabei stößt man allerdings auf ein Hindernis: Es muss eine Komponente geben, die weiß, wie die Abhängigkeitsstruktur der Applikation aussieht, um die Objekte miteinander zu verdrahten. Diese Komponente wird als Dependency Injection Container bezeichnet. Es handelt sich dabei um eine Art Register, in dem die Objekte der Applikation erfasst werden. Sollte ein Objekt zur Laufzeit während dessen Initialisierung ein anderes Objekt benötigen, wird dieses über den Dependency Injection Container identifiziert, erzeugt und in das Objekt hineingereicht (injiziert).

Die Applikation erreicht dadurch ein neues Level an Flexibilität, da nun jedes Objekt nach Belieben konfiguriert werden kann. Dependency Injection ermöglicht es, konkrete Schnittstellen für beliebige Typen von Strahlern, Lasern, Sensoren oder anderen Messgeräten, Modulen, Frameworks oder auch gemockte Testobjekte, die Ihr Algorithmus nutzen soll, von außen zu injizieren. Ihre eigentliche Geschäftslogik bleibt somit unabhängig von den genutzten Technologien.

Frameworks: Fluch oder Segen?

Dependency Injection gehört genauso wie das Logging und die Fehlerbehandlung zu den sogenannten Cross Cutting Concerns in der Software-Entwicklung. Cross Cutting Concerns sind Aspekte, die sich nicht sauber von einzelnen Komponenten einer Applikation trennen lassen, sondern ihre Ebenen durchstechen.

Da Features wie das Logging oder fundamentale Konzepte wie Dependency Injection zu einem Großteil der heute programmierten Software-Produkte gehören, haben sich hierzu eine Vielzahl an Frameworks, also externe, einbindbare Pakete etabliert. Auch für verschiedene Arten der Datenhaltung, User Interfaces und Datenkonversionen finden sich viele Frameworks, die sich problemlos in das eigene Software-Projekt einbinden und nutzen lassen.

Das hat den großen Vorteil, dass diese Funktionalitäten nicht erneut aus eigenen Anstrengungen heraus programmiert werden müssen. Das Einbinden von Frameworks birgt aber auch Nachteile: Häufig sind diese Technologien so tief in der eigenen Software verdrahtet, dass das Projekt von ihnen abhängt. Wird ein Framework nicht mehr weiterentwickelt, ist der Umstieg auf ein anderes meist sehr teuer, im schlimmsten Fall sogar unmöglich.

Darüber hinaus müssen Mitarbeiter ggf. auf die Handhabung der genutzten Frameworks geschult werden. Sind die Frameworks keine Open-Source-Lösungen, stehen in der Regel zusätzlich Anschaffungskosten an. Es ist also eine Herausforderung, die richtige Mischung zwischen eigenem und externem Code auszuloten, um das eigene Projekt sicher voranzubringen.

.NET Core 3

.NET Core 3 ist eine universelle Entwicklungsplattform, die Community-getrieben als Open-Source-Projekt entwickelt wird. .NET Core zeichnet sich durch seine Modularität aus. Man muss nicht länger ungenutzte Dienste und Module einbinden, sondern nur das, was man wirklich benötigt.

Microsoft bietet darüber hinaus hauseigene Technologien wie Microsoft Identity, eine Authentifizierungstechnologie, Entity Framework Core als Persistence Provider, aber auch Technologien zur Behandlung von Cross Cutting Concerns wie das Logging oder einen Dependency Injection Container. Dabei ist man nicht gezwungen, diese Technologien zu nutzen, sondern kann nach Belieben externe Frameworks an die Applikation anbinden.

.NET Core ermöglicht dadurch modernen Software-Studios, sich auf das Kerngeschäft der jeweiligen Branche zu fokussieren. Bei der Wahl der Technologie und Programmiersprache bietet .NET Core ebenfalls viel Freiheit: von mobilen Apps über Web-Anwendungen bis hin zu klassischen Desktop-Anwendungen in wahlweise C# (C-Sharp) in Full-Stack oder in Kombination mit anderen Programmiersprachen.

Mit .NET Core hat Microsoft hierfür eine Entwicklungsplattform auf den Markt gebracht, die mit der Plattformunabhängigkeit eine ernstzunehmende Kampfansage an konkurrierende Technologien bspw. aus dem Java-Umfeld darstellt. Egal ob Mac, Linux oder Windows – .NET Core Applikationen sind, bis auf Microsoft GUI-Technologien wie Windows Presentation Foundation (WPF), nicht länger an ein Windows-Betriebssystem gebunden.

.NET Core und die Zwiebel-Architektur

.NET Core Projektstruktur
.NET Core Projektstruktur
(Bild: Method Park)

Wie sieht ein .NET Core Projekt nun konkret aus und wie wird die Zwiebel-Architektur umgesetzt? .NET Core gibt eine Projektstruktur vor, die vorab jeder Applikation eine Schicht hinzufügt, die hier als Host-Schicht bezeichnet wird. Sie liegt schützend um die Kernkomponenten, also Domain-Schicht- und Application Layer, des Programms. Die Host-Schicht ermöglicht als Abstraktions- und Konfigurationsschicht die besagte Modularität.

Host-Einstellungen, wie z.B. Routen für die Verfügbarkeit von API-Endpoints oder die Initialisierung des Dependency Injection Containers, die Registrierung interner und externer Technologien, wie zur Datenhaltung und des Loggings, sowie die Konfiguration von Layering über einschlägige Konventionen (ASP.NET Core MVC) werden programmatisch konfiguriert.

Somit sind Sie an kein Framework abseits von .NET Core gebunden, denn Ihr eigener Programmcode bleibt stets über die Host-Schicht von externem Code getrennt und somit wiederverwendbar. Die Abhängigkeiten Ihres Software-Produktes, wie in der Zwiebel-Architektur dargestellt, sind nach innen gerichtet. Der Fokus liegt auf Ihren eigenen Geschäftsobjekten und Geschäftsregeln.

Die Applikation ist nicht auf eine bestimmte Technologie zur Datenhaltung (SQL, XML, NoSql etc.) oder zur Präsentation (Angular, Blazor, Razor, Wpf) angewiesen oder davon abhängig. Durch den gegebenen Schutz der Geschäftslogik und der Geschäftsobjekte sowie dem hohen Maß an Flexibilität durch die Host-Schicht hält eine in .NET Core geschriebene Anwendung mit dem heutigen Tempo der Entwicklungen in der IT-Branche Schritt.

Fazit

Die Disziplin des Software Engineerings hat einen langen Entwicklungsweg hinter sich. Durch eine Fülle von Erfolgen weiß man heutzutage, was gut funktioniert. Doch vor allem die gemachten Fehler und Fallgruben prägen, was sich bewährt hat.

Lars Jacobi
Lars Jacobi
(Bild: Method Park)

Microsoft hat als Pionierunternehmen in der Software-Entwicklung mit .NET Core 3 eine Entwicklungsplattform zur Verfügung gestellt, die nicht nur die eigene jahrzehntelange Erfahrung bündelt, sondern durch den Open-Source-Ansatz auch die der Community. Daher ist .NET Core eine ernstzunehmende Alternative, wenn es um die Wahl einer geeigneten Entwicklungsplattform geht.

* Lars Jacobi arbeitet bei Method Park als Software Engineer und Spezialist für Software-Architektur und Code-Qualität.

(ID:46847258)