Praktische Architekturmuster

Hey Habr!

Aufgrund der aktuellen Ereignisse im Zusammenhang mit dem Coronavirus kommt es zu einer erhöhten Auslastung einiger Internetdienste. Zum Beispiel, Eine der britischen Einzelhandelsketten hat ihre Online-Bestellseite einfach eingestellt., weil nicht genügend Kapazität vorhanden war. Und es ist nicht immer möglich, einen Server durch einfaches Hinzufügen leistungsstärkerer Geräte zu beschleunigen, sondern Client-Anfragen müssen bearbeitet werden (sonst gehen sie an die Konkurrenz).

In diesem Artikel werde ich kurz auf beliebte Vorgehensweisen eingehen, mit denen Sie einen schnellen und fehlertoleranten Dienst erstellen können. Aus den möglichen Entwicklungsplänen habe ich jedoch nur diejenigen ausgewählt, die es derzeit gibt Einfach zu verwenden. Für jedes Element verfügen Sie entweder über vorgefertigte Bibliotheken oder Sie haben die Möglichkeit, das Problem mithilfe einer Cloud-Plattform zu lösen.

Horizontale Skalierung

Der einfachste und bekannteste Punkt. Herkömmlicherweise sind die beiden gebräuchlichsten Lastverteilungsschemata die horizontale und die vertikale Skalierung. In einem Dauerfall Sie ermöglichen die parallele Ausführung von Diensten und verteilen so die Last zwischen ihnen. In der Sekunde Sie bestellen leistungsfähigere Server oder optimieren den Code.

Ich nehme zum Beispiel einen abstrakten Cloud-Dateispeicher, also ein Analogon zu OwnCloud, OneDrive usw.

Unten finden Sie ein Standardbild einer solchen Schaltung, das jedoch nur die Komplexität des Systems veranschaulicht. Schließlich müssen wir die Dienste irgendwie synchronisieren. Was passiert, wenn der Benutzer eine Datei vom Tablet speichert und sie dann auf dem Telefon anzeigen möchte?

Praktische Architekturmuster
Der Unterschied zwischen den Ansätzen: Bei der vertikalen Skalierung sind wir bereit, die Leistung der Knoten zu erhöhen, und bei der horizontalen Skalierung sind wir bereit, neue Knoten hinzuzufügen, um die Last zu verteilen.

CQRS

Verantwortungstrennung für Befehlsabfragen Ein ziemlich wichtiges Muster, da es verschiedenen Clients nicht nur ermöglicht, sich mit unterschiedlichen Diensten zu verbinden, sondern auch dieselben Ereignisströme zu empfangen. Für eine einfache Anwendung sind die Vorteile nicht so offensichtlich, für einen stark ausgelasteten Dienst ist es jedoch äußerst wichtig (und einfach). Sein Wesen: Eingehende und ausgehende Datenströme sollten sich nicht überschneiden. Das heißt, Sie können keine Anfrage senden und eine Antwort erwarten; stattdessen senden Sie eine Anfrage an Dienst A, erhalten aber eine Antwort von Dienst B.

Der erste Vorteil dieses Ansatzes ist die Möglichkeit, die Verbindung (im weitesten Sinne des Wortes) zu unterbrechen, während eine lange Anfrage ausgeführt wird. Nehmen wir zum Beispiel eine mehr oder weniger standardmäßige Sequenz:

  1. Der Client hat eine Anfrage an den Server gesendet.
  2. Der Server hat eine lange Verarbeitungszeit gestartet.
  3. Der Server antwortete dem Client mit dem Ergebnis.

Stellen wir uns vor, dass in Punkt 2 die Verbindung unterbrochen wurde (oder die Netzwerkverbindung wiederhergestellt wurde oder der Benutzer eine andere Seite aufgerufen hat und dabei die Verbindung unterbrochen hat). In diesem Fall wird es für den Server schwierig sein, dem Benutzer eine Antwort mit Informationen darüber zu senden, was genau verarbeitet wurde. Bei Verwendung von CQRS wird die Reihenfolge etwas anders sein:

  1. Der Kunde hat Updates abonniert.
  2. Der Client hat eine Anfrage an den Server gesendet.
  3. Der Server antwortete: „Anfrage angenommen.“
  4. Der Server antwortete mit dem Ergebnis über den Kanal ab Punkt „1“.

Praktische Architekturmuster

Wie Sie sehen, ist das Schema etwas komplizierter. Zudem fehlt hier der intuitive Request-Response-Ansatz. Wie Sie sehen, führt ein Verbindungsabbruch während der Bearbeitung einer Anfrage jedoch nicht zu einem Fehler. Wenn der Benutzer tatsächlich von mehreren Geräten aus mit dem Dienst verbunden ist (z. B. von einem Mobiltelefon und einem Tablet), können Sie außerdem sicherstellen, dass die Antwort auf beiden Geräten erfolgt.

Interessanterweise wird der Code zur Verarbeitung eingehender Nachrichten sowohl für Ereignisse, die vom Client selbst beeinflusst wurden, als auch für andere Ereignisse, einschließlich solcher von anderen Clients, gleich (nicht 100 %).

In Wirklichkeit erhalten wir jedoch einen zusätzlichen Bonus aufgrund der Tatsache, dass der unidirektionale Fluss auf funktionale Weise gehandhabt werden kann (mithilfe von RX und ähnlichem). Und das ist bereits ein gravierendes Plus, da die Anwendung im Wesentlichen vollständig reaktiv und auch mit einem funktionalen Ansatz gestaltet werden kann. Bei fetten Programmen können dadurch Entwicklungs- und Supportressourcen erheblich eingespart werden.

Wenn wir diesen Ansatz mit horizontaler Skalierung kombinieren, erhalten wir als Bonus die Möglichkeit, Anfragen an einen Server zu senden und Antworten von einem anderen zu empfangen. So kann der Kunde den für ihn passenden Dienst auswählen und das System im Inneren ist dennoch in der Lage, Ereignisse korrekt zu verarbeiten.

Event-Beschaffung

Wie Sie wissen, ist eines der Hauptmerkmale eines verteilten Systems das Fehlen einer gemeinsamen Zeit, eines gemeinsamen kritischen Abschnitts. Für einen Prozess können Sie eine Synchronisierung (auf denselben Mutexes) durchführen, bei der Sie sicher sind, dass niemand sonst diesen Code ausführt. Dies ist jedoch für ein verteiltes System gefährlich, da es einen Overhead erfordert und auch alle Schönheiten der Skalierung zunichte macht – alle Komponenten warten immer noch auf eine.

Daraus ergibt sich eine wichtige Tatsache: Ein schnelles verteiltes System kann nicht synchronisiert werden, da wir dann die Leistung verringern. Andererseits benötigen wir oft eine gewisse Konsistenz zwischen den Komponenten. Und dafür können Sie den Ansatz mit verwenden eventuelle Konsistenz, wobei gewährleistet ist, dass alle Abfragen den zuletzt aktualisierten Wert zurückgeben, wenn es nach der letzten Aktualisierung („irgendwann“) für einen bestimmten Zeitraum keine Datenänderungen gibt.

Es ist wichtig zu verstehen, dass es bei klassischen Datenbanken häufig verwendet wird starke Konsistenz, wobei jeder Knoten über die gleichen Informationen verfügt (dies wird häufig dann erreicht, wenn die Transaktion erst als abgeschlossen gilt, nachdem der zweite Server geantwortet hat). Aufgrund der Isolationsstufen gibt es hier einige Lockerungen, aber der Grundgedanke bleibt derselbe – man kann in einer völlig harmonisierten Welt leben.

Kehren wir jedoch zur ursprünglichen Aufgabe zurück. Wenn ein Teil des Systems damit gebaut werden kann eventuelle Konsistenz, dann können wir das folgende Diagramm erstellen.

Praktische Architekturmuster

Wichtige Merkmale dieses Ansatzes:

  • Jede eingehende Anfrage wird in eine Warteschlange gestellt.
  • Während der Bearbeitung einer Anfrage kann der Dienst Aufgaben auch in andere Warteschlangen stellen.
  • Jedes eingehende Ereignis verfügt über eine Kennung (die für die Deduplizierung erforderlich ist).
  • Die Warteschlange funktioniert ideologisch nach dem „Append Only“-Schema. Sie können keine Elemente daraus entfernen oder neu anordnen.
  • Die Warteschlange funktioniert nach dem FIFO-Schema (Entschuldigung für die Tautologie). Wenn Sie eine parallele Ausführung benötigen, sollten Sie Objekte zu einem bestimmten Zeitpunkt in verschiedene Warteschlangen verschieben.

Ich möchte Sie daran erinnern, dass wir uns mit der Online-Dateispeicherung befassen. In diesem Fall sieht das System etwa so aus:

Praktische Architekturmuster

Wichtig ist, dass die Dienste im Diagramm nicht unbedingt einen separaten Server bedeuten. Sogar der Prozess kann derselbe sein. Wichtig ist noch etwas: Ideologisch sind diese Dinge so getrennt, dass eine horizontale Skalierung problemlos angewendet werden kann.

Und für zwei Benutzer sieht das Diagramm so aus (Dienste, die für verschiedene Benutzer gedacht sind, werden in unterschiedlichen Farben angezeigt):

Praktische Architekturmuster

Boni aus einer solchen Kombination:

  • Informationsverarbeitungsdienste werden getrennt. Auch die Warteschlangen sind getrennt. Wenn wir den Systemdurchsatz erhöhen müssen, müssen wir einfach mehr Dienste auf mehr Servern starten.
  • Wenn wir Informationen von einem Nutzer erhalten, müssen wir nicht warten, bis die Daten vollständig gespeichert sind. Im Gegenteil, wir müssen nur mit „OK“ antworten und dann nach und nach mit der Arbeit beginnen. Gleichzeitig gleicht die Warteschlange Spitzen aus, da das Hinzufügen eines neuen Objekts schnell erfolgt und der Benutzer nicht auf einen vollständigen Durchlauf des gesamten Zyklus warten muss.
  • Als Beispiel habe ich einen Deduplizierungsdienst hinzugefügt, der versucht, identische Dateien zusammenzuführen. Wenn es in 1 % der Fälle über einen längeren Zeitraum funktioniert, wird es vom Kunden kaum wahrgenommen (siehe oben), was ein großes Plus ist, da wir nicht mehr zu XNUMX % auf Geschwindigkeit und Zuverlässigkeit angewiesen sind.

Die Nachteile sind jedoch sofort sichtbar:

  • Unser System hat seine strenge Konsistenz verloren. Das heißt, wenn Sie beispielsweise verschiedene Dienste abonnieren, können Sie theoretisch einen anderen Status erhalten (da einer der Dienste möglicherweise keine Zeit hat, eine Benachrichtigung aus der internen Warteschlange zu erhalten). Als weitere Konsequenz verfügt das System nun über keine gemeinsame Zeit. Das heißt, es ist beispielsweise unmöglich, alle Ereignisse einfach nach der Ankunftszeit zu sortieren, da die Uhren zwischen den Servern möglicherweise nicht synchron sind (außerdem ist die gleiche Zeit auf zwei Servern eine Utopie).
  • Es können jetzt keine Ereignisse mehr einfach zurückgesetzt werden (wie dies mit einer Datenbank möglich wäre). Stattdessen müssen Sie ein neues Ereignis hinzufügen − Entschädigungsereignis, wodurch der letzte Status in den erforderlichen Status geändert wird. Als Beispiel aus einem ähnlichen Bereich: Ohne den Verlauf neu zu schreiben (was in manchen Fällen schlecht ist), können Sie einen Commit in Git nicht zurücksetzen, aber Sie können einen besonderen machen Rollback-Commit, was im Wesentlichen nur den alten Zustand zurückgibt. Allerdings bleiben sowohl das fehlerhafte Commit als auch das Rollback im Verlauf.
  • Das Datenschema kann sich von Release zu Release ändern, alte Ereignisse können jedoch nicht mehr auf den neuen Standard aktualisiert werden (da Ereignisse grundsätzlich nicht geändert werden können).

Wie Sie sehen, funktioniert Event Sourcing gut mit CQRS. Darüber hinaus ist die Implementierung eines Systems mit effizienten und komfortablen Warteschlangen, jedoch ohne Trennung der Datenflüsse, an sich schon schwierig, da Sie Synchronisationspunkte hinzufügen müssen, die den gesamten positiven Effekt der Warteschlangen zunichte machen. Bei gleichzeitiger Anwendung beider Ansätze ist eine geringfügige Anpassung des Programmcodes erforderlich. In unserem Fall kommt beim Senden einer Datei an den Server nur die Antwort „ok“, was nur bedeutet, dass „der Vorgang zum Hinzufügen der Datei gespeichert wurde“. Formal bedeutet dies nicht, dass die Daten bereits auf anderen Geräten verfügbar sind (z. B. kann der Deduplizierungsdienst den Index neu aufbauen). Nach einiger Zeit erhält der Client jedoch eine Benachrichtigung im Stil von „Datei X wurde gespeichert“.

Als Ergebnis:

  • Die Anzahl der Dateiversandstatus nimmt zu: Anstelle des klassischen „Datei gesendet“ erhalten wir zwei: „Die Datei wurde zur Warteschlange auf dem Server hinzugefügt“ und „Die Datei wurde im Speicher gespeichert“. Letzteres bedeutet, dass andere Geräte bereits mit dem Empfang der Datei beginnen können (angepasst an die Tatsache, dass die Warteschlangen mit unterschiedlichen Geschwindigkeiten arbeiten).
  • Da die Einreichungsinformationen mittlerweile über unterschiedliche Kanäle eingehen, müssen wir Lösungen finden, um den Bearbeitungsstatus der Datei zu erhalten. Dies hat zur Folge: Im Gegensatz zum klassischen Request-Response-Verfahren kann der Client während der Verarbeitung der Datei neu gestartet werden, der Status dieser Verarbeitung selbst ist jedoch korrekt. Darüber hinaus funktioniert dieser Artikel im Wesentlichen sofort. Die Folge: Wir sind nun toleranter gegenüber Fehlern.

Schärfen

Wie oben beschrieben, mangelt es Event-Sourcing-Systemen an strenger Konsistenz. Dies bedeutet, dass wir mehrere Speicher verwenden können, ohne dass eine Synchronisierung zwischen ihnen erforderlich ist. Wenn wir unser Problem angehen, können wir:

  • Trennen Sie Dateien nach Typ. Beispielsweise können Bilder/Videos dekodiert und ein effizienteres Format ausgewählt werden.
  • Getrennte Konten nach Ländern. Aufgrund vieler Gesetze kann dies erforderlich sein, aber dieses Architekturschema bietet eine solche Möglichkeit automatisch

Praktische Architekturmuster

Wenn Sie Daten von einem Speicher auf einen anderen übertragen möchten, reichen Standardmittel nicht mehr aus. Leider müssen Sie in diesem Fall die Warteschlange stoppen, die Migration durchführen und sie dann starten. Im Allgemeinen können Daten nicht „on the fly“ übertragen werden. Wenn die Ereigniswarteschlange jedoch vollständig gespeichert ist und Sie Snapshots früherer Speicherzustände haben, können wir die Ereignisse wie folgt wiedergeben:

  • In der Ereignisquelle hat jedes Ereignis seine eigene Kennung (idealerweise nicht abnehmend). Das bedeutet, dass wir dem Speicher ein Feld hinzufügen können – die ID des zuletzt verarbeiteten Elements.
  • Wir duplizieren die Warteschlange, sodass alle Ereignisse für mehrere unabhängige Speicher verarbeitet werden können (der erste ist der, in dem die Daten bereits gespeichert sind, und der zweite ist neu, aber noch leer). Die zweite Warteschlange wird natürlich noch nicht bearbeitet.
  • Wir starten die zweite Warteschlange (das heißt, wir beginnen mit der Wiedergabe von Ereignissen).
  • Wenn die neue Warteschlange relativ leer ist (d. h. der durchschnittliche Zeitunterschied zwischen dem Hinzufügen eines Elements und dem Abrufen desselben akzeptabel ist), können Sie damit beginnen, Leser auf den neuen Speicher umzustellen.

Wie Sie sehen, hatten und haben wir in unserem System keine strikte Konsistenz. Es gibt nur Eventualkonsistenz, also eine Garantie dafür, dass Ereignisse in der gleichen Reihenfolge (aber möglicherweise mit unterschiedlichen Verzögerungen) verarbeitet werden. Und auf diese Weise können wir relativ einfach Daten an die andere Seite der Welt übertragen, ohne das System zu stoppen.

Wenn wir also unser Beispiel über die Online-Speicherung von Dateien fortsetzen, bietet uns eine solche Architektur bereits eine Reihe von Vorteilen:

  • Wir können Objekte auf dynamische Weise näher an Benutzer heranbringen. Auf diese Weise können Sie die Servicequalität verbessern.
  • Wir können einige Daten innerhalb von Unternehmen speichern. Beispielsweise verlangen Unternehmensbenutzer oft, dass ihre Daten in kontrollierten Rechenzentren gespeichert werden (um Datenlecks zu vermeiden). Durch Sharding können wir dies leicht unterstützen. Und die Aufgabe wird noch einfacher, wenn der Kunde über eine kompatible Cloud verfügt (z. B. Azure selbst gehostet).
  • Und das Wichtigste ist, dass wir das nicht tun müssen. Schließlich wären wir zunächst einmal mit einem Speicher für alle Konten zufrieden (um schnell mit der Arbeit beginnen zu können). Und das Hauptmerkmal dieses Systems ist, dass es zwar erweiterbar, aber im Anfangsstadium recht einfach ist. Sie müssen einfach nicht sofort Code schreiben, der mit einer Million separater unabhängiger Warteschlangen usw. funktioniert. Bei Bedarf kann dies in Zukunft erfolgen.

Statisches Content-Hosting

Dieser Punkt mag ziemlich offensichtlich erscheinen, ist aber für eine mehr oder weniger standardmäßig geladene Anwendung dennoch notwendig. Das Wesentliche ist einfach: Alle statischen Inhalte werden nicht von demselben Server verteilt, auf dem sich die Anwendung befindet, sondern von speziellen Servern, die speziell für diese Aufgabe bestimmt sind. Dadurch werden diese Vorgänge schneller ausgeführt (bedingtes Nginx stellt Dateien schneller und kostengünstiger bereit als ein Java-Server). Plus CDN-Architektur (Content Delivery Network) ermöglicht es uns, unsere Dateien näher an den Endbenutzern zu platzieren, was sich positiv auf die Benutzerfreundlichkeit des Dienstes auswirkt.

Das einfachste und gängigste Beispiel für statischen Inhalt ist eine Reihe von Skripten und Bildern für eine Website. Bei ihnen ist alles einfach: Sie sind im Voraus bekannt, dann wird das Archiv auf CDN-Server hochgeladen, von wo aus sie an Endbenutzer verteilt werden.

In der Realität können Sie jedoch für statische Inhalte einen Ansatz verwenden, der der Lambda-Architektur ähnelt. Kehren wir zu unserer Aufgabe (Online-Dateispeicherung) zurück, bei der wir Dateien an Benutzer verteilen müssen. Die einfachste Lösung besteht darin, einen Dienst zu erstellen, der für jede Benutzeranfrage alle erforderlichen Prüfungen (Autorisierung usw.) durchführt und die Datei dann direkt aus unserem Speicher herunterlädt. Der Hauptnachteil dieses Ansatzes besteht darin, dass statische Inhalte (und eine Datei mit einer bestimmten Revision ist tatsächlich statischer Inhalt) von demselben Server verteilt werden, der die Geschäftslogik enthält. Stattdessen können Sie das folgende Diagramm erstellen:

  • Der Server stellt eine Download-URL bereit. Es kann die Form file_id + key haben, wobei key eine kleine digitale Signatur ist, die das Recht gibt, für die nächsten XNUMX Stunden auf die Ressource zuzugreifen.
  • Die Datei wird von Simple Nginx mit den folgenden Optionen verteilt:
    • Inhalts-Caching. Da sich dieser Dienst auf einem separaten Server befinden kann, haben wir uns für die Zukunft eine Reserve mit der Möglichkeit geschaffen, alle zuletzt heruntergeladenen Dateien auf der Festplatte zu speichern.
    • Überprüfung des Schlüssels zum Zeitpunkt der Verbindungserstellung
  • Optional: Verarbeitung von Streaming-Inhalten. Wenn wir beispielsweise alle Dateien im Dienst komprimieren, können wir das Entpacken direkt in diesem Modul durchführen. Die Konsequenz: IO-Operationen werden dort ausgeführt, wo sie hingehören. Ein Archivierer in Java wird problemlos viel zusätzlichen Speicher zuweisen, aber das Umschreiben eines Dienstes mit Geschäftslogik in Rust/C++-Bedingungen kann ebenfalls wirkungslos sein. In unserem Fall werden unterschiedliche Prozesse (oder sogar Dienste) verwendet, und daher können wir Geschäftslogik und IO-Operationen recht effektiv trennen.

Praktische Architekturmuster

Dieses Schema ist der Verteilung statischer Inhalte nicht sehr ähnlich (da wir nicht das gesamte statische Paket irgendwo hochladen), aber in Wirklichkeit geht es bei diesem Ansatz genau um die Verteilung unveränderlicher Daten. Darüber hinaus kann dieses Schema auf andere Fälle verallgemeinert werden, in denen der Inhalt nicht einfach statisch ist, sondern als eine Reihe unveränderlicher und nicht löschbarer Blöcke dargestellt werden kann (obwohl sie hinzugefügt werden können).

Als weiteres Beispiel (zur Verstärkung): Wenn Sie mit Jenkins/TeamCity gearbeitet haben, wissen Sie, dass beide Lösungen in Java geschrieben sind. Bei beiden handelt es sich um einen Java-Prozess, der sowohl die Build-Orchestrierung als auch die Inhaltsverwaltung übernimmt. Insbesondere haben beide Aufgaben wie „Eine Datei/einen Ordner vom Server übertragen“. Als Beispiel: Ausgabe von Artefakten, Übertragung des Quellcodes (wenn der Agent den Code nicht direkt aus dem Repository herunterlädt, sondern der Server dies für ihn erledigt), Zugriff auf Protokolle. Alle diese Aufgaben unterscheiden sich in ihrer IO-Last. Das heißt, es stellt sich heraus, dass der Server, der für die komplexe Geschäftslogik verantwortlich ist, gleichzeitig in der Lage sein muss, große Datenströme effektiv durch sich selbst zu leiten. Und das Interessanteste ist, dass ein solcher Vorgang nach genau demselben Schema an denselben Nginx delegiert werden kann (außer dass der Datenschlüssel zur Anfrage hinzugefügt werden sollte).

Wenn wir jedoch zu unserem System zurückkehren, erhalten wir ein ähnliches Diagramm:

Praktische Architekturmuster

Wie Sie sehen, ist das System radikal komplexer geworden. Jetzt ist es nicht nur ein Miniprozess, der Dateien lokal speichert. Was jetzt benötigt wird, ist nicht die einfachste Unterstützung, API-Versionskontrolle usw. Nachdem alle Diagramme gezeichnet wurden, ist es daher am besten, im Detail zu bewerten, ob sich die Erweiterbarkeit lohnt. Wenn Sie das System jedoch erweitern möchten (auch um mit einer noch größeren Anzahl von Benutzern zu arbeiten), müssen Sie auf ähnliche Lösungen zurückgreifen. Dadurch ist das System jedoch architektonisch für eine erhöhte Belastung gerüstet (fast jede Komponente kann für die horizontale Skalierung geklont werden). Das System kann aktualisiert werden, ohne es anzuhalten (einige Vorgänge werden lediglich etwas verlangsamt).

Wie ich gleich zu Beginn sagte, werden mittlerweile eine Reihe von Internetdiensten zunehmend ausgelastet. Und einige von ihnen begannen einfach nicht mehr richtig zu funktionieren. Tatsächlich versagten die Systeme genau in dem Moment, in dem das Unternehmen Geld verdienen sollte. Das heißt, statt die Lieferung zu verschieben und den Kunden nicht zu empfehlen: „Planen Sie Ihre Lieferung für die kommenden Monate“, sagte das System einfach: „Gehen Sie zu Ihren Konkurrenten.“ Tatsächlich ist dies der Preis geringer Produktivität: Verluste treten genau dann auf, wenn die Gewinne am höchsten wären.

Abschluss

Alle diese Ansätze waren schon vorher bekannt. Derselbe VK nutzt seit langem die Idee des Static Content Hosting zur Anzeige von Bildern. Viele Online-Spiele verwenden das Sharding-Schema, um Spieler in Regionen zu unterteilen oder Spielorte zu trennen (wenn die Welt selbst eine ist). Der Event-Sourcing-Ansatz wird aktiv in E-Mails eingesetzt. Die meisten Handelsanwendungen, bei denen ständig Daten empfangen werden, basieren tatsächlich auf einem CQRS-Ansatz, um die empfangenen Daten filtern zu können. Nun, die horizontale Skalierung wird in vielen Diensten schon seit geraumer Zeit eingesetzt.

Am wichtigsten ist jedoch, dass alle diese Muster in modernen Anwendungen sehr einfach anzuwenden sind (natürlich nur, wenn sie angemessen sind). Clouds bieten sofort Sharding und horizontale Skalierung, was viel einfacher ist, als selbst verschiedene dedizierte Server in verschiedenen Rechenzentren zu bestellen. CQRS ist viel einfacher geworden, allein schon durch die Entwicklung von Bibliotheken wie RX. Vor etwa 10 Jahren konnte eine seltene Website dies unterstützen. Auch Event Sourcing ist dank vorgefertigter Container mit Apache Kafka unglaublich einfach einzurichten. Vor 10 Jahren wäre das eine Innovation gewesen, heute ist es alltäglich. Dasselbe gilt auch für das Static Content Hosting: Durch komfortablere Technologien (u. a. ausführliche Dokumentation und eine große Datenbank mit Antworten) ist dieser Ansatz noch einfacher geworden.

Dadurch ist die Umsetzung einiger recht komplexer Architekturmuster mittlerweile deutlich einfacher geworden, sodass es sich besser lohnt, sich vorab genauer damit zu befassen. Wenn in einer zehn Jahre alten Anwendung eine der oben genannten Lösungen aufgrund der hohen Implementierungs- und Betriebskosten aufgegeben wurde, können Sie jetzt in einer neuen Anwendung oder nach einem Refactoring einen Dienst erstellen, der sowohl architektonisch als auch erweiterbar ist ( hinsichtlich der Leistung) und bereit für neue Anfragen von Kunden (z. B. zur Lokalisierung personenbezogener Daten).

Und das Wichtigste: Bitte nutzen Sie diese Ansätze nicht, wenn Sie eine einfache Anwendung haben. Ja, sie sind schön und interessant, aber für eine Site mit einem Spitzenbesuch von 100 Personen kommt man oft mit einem klassischen Monolithen aus (zumindest äußerlich lässt sich alles im Inneren in Module unterteilen usw.).

Source: habr.com

Kommentar hinzufügen