[MELDEN] Von der Vision zum Code: Ein Leitfaden zur Ausrichtung der Geschäftsstrategie auf die Ziele der Softwareentwicklung ist veröffentlicht!
HOL ES DIR HIER

Nutzung von.NET für skalierbare und wartbare Lösungen

readtime
Last updated on
February 17, 2025

A QUICK SUMMARY – FOR THE BUSY ONES

TABLE OF CONTENTS

Nutzung von.NET für skalierbare und wartbare Lösungen

Ein neues Projekt in Angriff zu nehmen ist immer aufregend und vielversprechend, bringt aber auch zahlreiche Herausforderungen mit sich. Es erfordert eine sorgfältige Entscheidungsfindung, um das zukünftige Wachstum des Systems sicherzustellen. In diesem Artikel untersuchen wir die Strategien und Optionen, die mit einem Projekt verbunden sind, das darauf abzielt, ein großes bestehendes System schrittweise durch eine neue Plattform zu ersetzen. Obwohl die Projektspezifikationen einzigartig sind, können die erörterten Prinzipien universell angewendet werden. Diese Techniken und Muster bieten eine hohe Skalierbarkeit und Belastbarkeit, sodass sie an verschiedene Projekte angepasst werden können und signifikante Änderungen bewältigen können.

Umsetzung

Überblick über den Tech-Stack

Bei der Auswahl unserer Technologie liegt einer der Schlüssel zum Aufbau skalierbarer Weblösungen in der Einhaltung der Grundsätze von 12-Faktor-Anwendung und Industriestandards im Allgemeinen.

Von Konsolenanwendungen bis hin zu Web-APIs unterstützt moderner.NET weitgehend und fördert diese von Grund auf. Das .NET-Ökosystem bietet inhärente Unterstützung für Plattformunabhängigkeit, Beobachtbarkeit, Containerisierung und Abstraktionen und fördert so die Wartbarkeit und Konfigurierbarkeit von Software.

Apps, die innerhalb dieses Ökosystems entwickelt wurden, lassen sich mühelos an die Geschäftsanforderungen anpassen, bieten umgebungsübergreifende Unterstützung und sind unabhängig von der Bereitstellung. Microsofts Engagement für ein Open-Source-Ökosystem wird durch formelle Leitlinien, Standards und erprobte Verfahren ergänzt, die Stabilität, Zuverlässigkeit und Wachstum gewährleisten. Das Ökosystem bietet Bibliotheken auf Unternehmensebene wie MediaTR und Entity Framework Core, die verschiedene Praktiken der Softwareentwicklung unterstützen und Geschäftsmuster genau widerspiegeln.

Die Kombination aus Open-Source-Beiträgen und Herstelleraufsicht gewährleistet Stabilität und die Einhaltung bewährter Verfahren, und schnelle Lösungen sowohl für allgemeine als auch für Nischenentwicklungsherausforderungen, wodurch Kosten minimiert und die Produktionszeit verkürzt werden.

Beobachtbarkeit

Die Beobachtbarkeit ist für jede bereitgestellte Webanwendung von größter Bedeutung. Sie ermöglicht einen effizienten Support, die Identifizierung von Problemen und die Sicherstellung der Benutzerzufriedenheit. Angesichts der unterschiedlichen Nutzungsmuster und der inhärenten Komplexität von Web-Stacks ist eine robuste, zukunftssichere Lösung, die den Industriestandards entspricht, unerlässlich.

Nachdem wir nach verfügbaren .NET-Tools gesucht hatten, entschieden wir uns für Serilog. Serilog ist eine etablierte Logging-Lösung in.NET, die mehrere Integrationen (über mehrere Logging-Sink-Integrationen — wie Seq, Grafana Loki oder ElasticSearch) und Anreicherungen zum transparenten Hinzufügen zusätzlicher Daten bietet.

Serilog ist sowohl auf Code- als auch auf Konfigurationsebene integrierbar — letztere kann verwendet werden, um den Anwendungscode unabhängig von den angeschlossenen Logging-Sinks zu machen. Der Schweregrad der Protokollierung, spezifische Überschreibungen usw. können durch Konfiguration von der Umgebung abhängig gemacht werden. Weitere Informationen finden Sie unter https://github.com/serilog/serilog-settings-configuration

Seine Konfigurationsflexibilität ermöglicht eine nahtlose Integration ohne Codeänderungen, und die Einrichtung ist so einfach wie die Bereitstellung unter der generischen.NET-Framework-Protokollierungsabstraktion, ohne die Codesemantik zu ändern.

Darüber hinaus unterstützt das .NET-Ökosystem den OpenTelemetrie-Standard für Instrumentierung und Datenverbreitung, Gewährleistung einer Integration mit geringen Auswirkungen und der Einhaltung von Industriestandards, ohne dass in den Anwendungscode eingedrungen wird.

Dadurch hat die zugrundeliegende Lösung nur geringe Auswirkungen auf den Anwendungscode und stellt sicher, dass die meisten Entwickler mit den Codekonventionen vertraut sind und die Industriestandards in Bezug auf das Format der produzierten Daten und die Trennung von Bedenken einhalten.

Lokales Umfeld

Eine komfortable, effiziente, stabile und vertrauenswürdige Entwicklungsumgebung ist für den Entwicklungsprozess von entscheidender Bedeutung. Selbst mit dem Aufkommen von Cloud-Entwicklungsumgebungen erfolgt die Entwicklung mehrteiliger Lösungen häufig auf einem lokalen Computer, auf dem die Infrastruktur der verschiedenen Komponenten des Systems über Tools wie Docker Compose verkabelt ist.

Da unser System verschiedene Ereignisse aus externen Quellen verarbeiten muss, haben wir verschiedene Optionen geprüft, um ein praktisches Setup für das Testen verschiedener Flows sowohl lokal als auch in der CI-Pipeline zu erstellen. Unser Ziel war es, eine Lösung zu finden, die unsere lokalen Maschinen nicht übermäßig belastet und gleichzeitig alle notwendigen Funktionen bietet, die unser System benötigt.

Ein Ansatz, der in solchen Fällen häufig in Betracht gezogen wird, ist die Erstellung typischer Debugging-Routen, wobei darauf geachtet wird, dass dieser Teil der API-Oberfläche in einer Produktionsumgebung nicht sichtbar ist. Wir haben jedoch festgestellt, dass dies zu riskant ist und zu viel Overhead für den Produktionscode verursacht.

Nachdem wir die richtigen Abstraktionen auf Anwendungsebene definiert und die Anwendungslogik effektiv von der zugrunde liegenden Infrastruktur getrennt hatten, überlegten wir, die lokale Umgebung ganz anders als die Produktionsumgebung einzurichten und leichtere Lösungen zu verwenden, die uns dennoch alle Funktionen bieten, die wir benötigen.

Um Logik zu überprüfen, die durch externe Mittel aufgerufen wird, z. B. durch das Konsumieren von Nachrichten oder Ereignissen, Wir haben uns für Redis als Messaging-Lösung entschieden, die das Message-Bus-Setup der Produktion genau nachbilden kann.

Neben den typischen Nutzungsszenarien wie verteiltem Caching, Implementierungen verteilter Sperren und sicher inkrementierten Zählern über mehrere Prozesse hinweg kann Redis auch als sehr praktische, schemalose Messaging-Lösung mit implizit erstellten Nachrichtenkanälen/Themen dienen. Weitere Informationen finden Sie unter https://redis.com/glossary/pub-sub/ oder https://redis.io/docs/interact/pubsub/.

Die Implementierung eines solchen Connectors war trivial und erforderte keinen lösungsspezifischen Code, solange das Nachrichtenformat des für die Handler-Logik relevanten Teils das einer Produktionseinstellung ist (z. B. JSON). Es kann auch über Flags oder eine umgebungsspezifische Konfiguration umgeschaltet werden.

Interaktion mit der Datenbank

In der Phase des Projekts, als wir nur mit Sicherheit wussten, dass wir eine relationale Datenbank verwenden wollten, aber aufgrund der sich ändernden Umstände hatten wir die Auswahl des spezifischen Produktionstools noch nicht abgeschlossen, entschieden wir uns für eines der am weitesten verbreiteten ORMs im .NET-Ökosystem, nämlich EF-Kern, das es uns in Kombination mit einer angemessenen Schichtung der Anwendung ermöglichte, ein robustes und flexibles System zu schaffen.

EF Core ist ausführlich dokumentiert und bietet Einblicke in effiziente Nutzung sowie mögliche Einschränkungen. Mit Unterstützung für verschiedene Datenbank-Engines, einschließlich NoSQL, legt bei EF Core Wert auf Benutzerfreundlichkeit für Entwickler, sprachorientierte Semantik, Parallelitätsunterstützung und Leistungsoptimierungen, sodass es für verschiedene Anwendungen geeignet ist. Du kannst lesen hier für weitere Informationen.

Während ORM-Lösungen wie EF Core möglicherweise führen manchmal zu Ineffizienzen bei der Abfrage, Sie optimieren allgemeine Abruf- und Persistenzaufgaben und reduzieren die Projektkomplexität, indem sie die Datenbankabfragesprache wegabstrahieren.

Für Szenarien, in denen komplexere Abfragen erforderlich sind, haben wir in Betracht gezogen, eine andere beliebte Bibliothek namens zu verwenden Dapper, das die direkte Verwendung von Abfragesprache und gespeicherten Prozeduren sicher ermöglicht und vor Angriffsvektoren wie SQL-Injection schützt.

Integration mit Altdaten

Da sich unser Projekt auf eine Neufassung konzentriert und die Benutzer schrittweise auf neue Funktionen umsteigen, während das alte System aktiv bleibt, haben wir die Strategien zur Datenintegration sorgfältig abgewogen.

Um dies zu implementieren, haben wir einige Szenarien in Betracht gezogen, die unserer Meinung nach möglicherweise unseren Anforderungen entsprechen, nämlich:

Geplante Jobs: Geplante oder On-Demand-Synchronisierungsaufträge (oder eine Kombination) könnten zwar verwendet werden, um unser Problem zu lösen, indem wir eine differenzierte Strategie implementieren oder den gesamten Zustand des alten Systems replizieren, aber wir haben festgestellt, dass dies zu viele Auswirkungen auf die Leistung haben kann und eine Menge Herausforderungen für das Erreichen einer echten Konsistenz mit sich bringt. Wir haben auch festgestellt, dass die Datenabfrage keinen Einblick in die seit der letzten Abfrageiteration gelöschten Daten hat, wodurch das neue System möglicherweise übermäßig mit unnötigen Daten gekoppelt wird, was zukünftige Probleme riskiert.

Dies birgt naturgemäß auch das Risiko, dass Schema-Interna außerhalb des Systems, das die Daten steuert, durchsickern, weshalb Vorsicht und Disziplin walten lassen müssen, um das neue System nicht zu umfangreich zu koppeln.

API verwenden: Die Nutzung der API des alten Systems für den Datenabruf mag zwar verlockend sein, da sie sicherstellt, dass die Daten durch eine etablierte Logik verlassen werden, aber sie kann das System belasten und eine direkte Kopplung einführen, die in vielen Kontexten möglicherweise nicht mit der Rewrite-Initiative übereinstimmt.

Es besteht auch das Risiko, dass das Legacy möglicherweise nicht die richtige API bereitstellt. Daher kann eine solche Strategie zusätzliche Arbeiten vor der eigentlichen Integration beinhalten.

Erweiterung des alten Systems um Event Production: Ein detaillierterer Ansatz beinhaltet die Identifizierung von Stellen im alten System, die Ereignisse für Zustandsänderungen auslösen. Je nach Komplexität des Systems kann dies eine Herausforderung und ein hohes Risiko darstellen und zusätzliche Mechanismen zur Fehlerbehandlung erfordern. Die Komplexität der ereignisgesteuerten Übertragung und die erforderlichen Investitionen in das Verständnis und die Modifizierung der internen Komponenten des bestehenden Systems können variieren.

Erfassung von Datenänderungen auf Datenbankebene:

Ein anderer Ansatz ist die Verwendung der Datenbankmechanismus für Datenerfassung ändern, das die Vorteile eines Push-basierten Ansatzes mit geringem Overhead zur Weitergabe von Daten bietet, ohne den Code des vorhandenen Systems zu ändern.

Dieser Ansatz ist sowohl für den Veranstalter als auch für den Verbraucher von Veranstaltungen transparent und kann sich als die optimale Wahl erweisen, wenn die Änderung eines stabilen Altsystems keine Investition darstellt, die sich lohnt.

Lösungen wie Debezium stellen vorgefertigte Konnektoren für die Veröffentlichung von Datenänderungsereignissen bereit. Es ist erwähnenswert, dass dafür möglicherweise eine gewisse Geschäftslogik implementiert werden muss erfasste Änderungen in aussagekräftigere Informationen umwandeln, wodurch der Mindestdatensatz zur Verfügung gestellt wird, den andere zur Verarbeitung benötigen.

Testansatz

Auf dem Weg zu einer robusten und anpassungsfähigen Systemarchitektur ist die Wahl der richtigen Teststrategie unerlässlich. Da unser Fokus darauf liegt, nicht nur die Stabilität und Funktionalität unseres Systems sicherzustellen, sondern auch dessen nahtlose Integration mit bestehenden Legacy-Komponenten, haben wir unsere Aufmerksamkeit voll und ganz auf Integrationstests gerichtet.

Eine der größten Herausforderungen bei Integrationstests besteht in der Verwaltung von Abhängigkeiten, insbesondere in komplexen Umgebungen, in denen mehrere Dienste orchestriert werden müssen. Um diesen Prozess zu rationalisieren, haben wir Testbehälter—ein leistungsstarkes Tool, das die Einrichtung von Docker-Containern für unsere Abhängigkeiten vereinfacht. Indem wir Testcontainer gleichzeitig nutzen Die Testvorrichtungen von Xunit, wir richten eine einheitliche Testumgebung ein, die den Aufwand des manuellen Abhängigkeitsmanagements überflüssig macht.

Dieser Ansatz verbessert nicht nur die Effizienz unserer Testverfahren, sondern erhöht auch die Zuverlässigkeit und Konsistenz unserer Testumgebung. Da alle erforderlichen Abhängigkeiten nahtlos gelöst sind, können wir die Interoperabilität unseres Systems mit älteren Komponenten sicher validieren, was eine reibungslosere und sicherere Integration ermöglicht und die Systemstabilität verbessert.

.Net und zukunftssichere Lösungen

Unsere Überlegungen und Annahmen, die auf der modularen, monolithischen, ereignisgesteuerten und funktionsorientierten Architektur basieren, die den Schwerpunkt auf der Minimierung von Kopplungen und der Bereitstellung einer vollständig beobachtbaren Plattform legt, sind vielversprechend für ein erfolgreiches Systemdesign und eine erfolgreiche Weiterentwicklung.

Dies alles wird durch die Wahl von.NET als Ökosystem mit seiner Stabilität, Robustheit, Vollständigkeit und bekannten und praktizierten Konventionen erleichtert.

Mit diesen können wir uns darauf konzentrieren, das Problem des Kunden mit Zuversicht und Leichtigkeit zu lösen, da wir wissen, dass Der Code, den wir schreiben, wird nicht nur einfach zu pflegen sein und schnell einen Mehrwert bieten, sondern auch von zukünftigen Verbesserungen in Bezug auf Benutzerfreundlichkeit und Leistung profitieren, das im .NET-Design seit langem stabil ist.

Lesen Sie auch unseren Artikel über Wichtige architektonische Entscheidungen für den Projekterfolg.

Frequently Asked Questions

No items found.

Our promise

Every year, Brainhub helps 750,000+ founders, leaders and software engineers make smart tech decisions. We earn that trust by openly sharing our insights based on practical software engineering experience.

Authors

Jan Król
github
.Net Softwareingenieur

Jan ist .Net Software Engineer bei Brainhub und hat eine Leidenschaft für AWS Public Cloud, Domain Driven Design und Softwareentwicklung.

Kamil Sałabun
github
.Net Softwareingenieur

Entwickler mit einem Hintergrund in mehreren Technologien. Sein Hauptaugenmerk liegt auf dem .NET-Ökosystem, der Anwendungs- und Systemarchitektur sowie der Optimierung der Leistung robuster Lösungen.

Michał Szopa
github
JavaScript-Softwareentwickler

JavaScript- und AWS-Spezialist, Cloud-Enthusiast, mit 7 Jahren Berufserfahrung in der Softwareentwicklung.

Jan Król
github
.Net Softwareingenieur

Jan ist .Net Software Engineer bei Brainhub und hat eine Leidenschaft für AWS Public Cloud, Domain Driven Design und Softwareentwicklung.

Kamil Sałabun
github
.Net Softwareingenieur

Entwickler mit einem Hintergrund in mehreren Technologien. Sein Hauptaugenmerk liegt auf dem .NET-Ökosystem, der Anwendungs- und Systemarchitektur sowie der Optimierung der Leistung robuster Lösungen.

Michał Szopa
github
JavaScript-Softwareentwickler

JavaScript- und AWS-Spezialist, Cloud-Enthusiast, mit 7 Jahren Berufserfahrung in der Softwareentwicklung.

Read next

No items found...

previous article in this collection

No items found.

It's the first one.

next article in this collection

It's the last one.