Praktische Architektur-Muster

Hallo, Habra!

Angesichts der aktuellen Ereignisse aufgrund des Coronavirus haben viele Internetdienste eine erhöhte Nachfrage erfahren. Zum Beispiel hat eine der Einzelhandelsketten in Großbritannien einfach die Website für Online-Bestellungen gestoppt, da die Kapazitäten nicht ausreichten. Und es ist nicht immer möglich, einen Server einfach durch leistungsstärkere Hardware zu beschleunigen, jedoch müssen die Kundenanfragen bearbeitet werden (oder sie wechseln zu den Konkurrenten).

In diesem Artikel werde ich kurz über beliebte Praktiken sprechen, die es ermöglichen, einen schnellen und ausfallsicheren Dienst zu schaffen. Ich habe jedoch nur die Entwicklungsansätze ausgewählt, die man jetzt einfach nutzen kann. Für jeden Punkt haben Sie entweder bereits fertige Bibliotheken zur Verfügung, oder Sie können das Problem mithilfe einer Cloud-Plattform lösen.

Horizontale Skalierung

Der einfachste und allgemein bekannte Punkt. Man kann grob sagen, dass es zwei häufig vorkommende Lastverteilungsansätze – horizontale und vertikale Skalierung – gibt. Im ersten Fall erlauben Sie es den Diensten, parallel zu arbeiten, wodurch die Last zwischen ihnen verteilt wird. Im zweiten bestellen Sie leistungsstärkere Server oder optimieren den Code.

Als Beispiel nehme ich einen abstrakten Cloud-Speicher für Dateien, also eine Art Äquivalent zu OwnCloud, OneDrive und so weiter.

Das Standardbild eines solchen Schemas sehen Sie unten, es zeigt jedoch nur die Komplexität des Systems. Denn wir müssen die Dienste irgendwie synchronisieren. Was passiert, wenn ein Benutzer eine Datei von seinem Tablet speichert und sie dann mit seinem Telefon anzeigen möchte?

Praktische Architektur-Muster
Der Unterschied zwischen den Ansätzen: Bei der vertikalen Skalierung sind wir bereit, die Leistung der Knoten zu steigern, während wir bei der horizontalen Skalierung neue Knoten hinzufügen, um die Last zu verteilen.

CQRS

Command Query Responsibility Segregation ist ein ziemlich wichtiges Muster, da es verschiedenen Clients nicht nur ermöglicht, sich mit verschiedenen Diensten zu verbinden, sondern auch gleiche Ereignisströme zu erhalten. Die Vorteile sind für einfache Anwendungen nicht so offensichtlich, aber es ist äußerst wichtig (und einfach) für stark ausgelastete Dienste. Das Wesentliche ist: Eingehende und ausgehende Datenströme sollten sich nicht überschneiden. Das bedeutet, dass Sie eine Anfrage nicht senden und eine Antwort erwarten können; stattdessen senden Sie eine Anfrage an Dienst A, erhalten aber die Antwort von Dienst B.

Der erste Vorteil dieses Ansatzes ist die Möglichkeit, die Verbindung (im weitesten Sinne) während der Ausführung einer langwierigen Anfrage zu unterbrechen. Nehmen wir als Beispiel eine mehr oder weniger Standardabfolge:

  1. Der Client hat eine Anfrage an den Server gesendet.
  2. Der Server hat die langwierige Verarbeitung gestartet.
  3. Der Server hat dem Client das Ergebnis zurückgemeldet.

Angenommen, es gab in Punkt 2 einen Verbindungsabbruch (entweder wurde das Netzwerk neu verbunden oder der Benutzer wechselte zu einer anderen Seite und unterbrach die Verbindung). In diesem Fall wird es für den Server schwierig sein, dem Benutzer mitzuteilen, was genau verarbeitet wurde. Durch die Anwendung von CQRS wird die Abfolge jedoch etwas anders sein:

  1. Der Client hat sich für Updates angemeldet.
  2. Der Client hat eine Anfrage an den Server gesendet.
  3. Der Server hat mit "Anfrage akzeptiert" geantwortet.
  4. Der Server hat das Ergebnis über den Kanal aus Punkt "1" übermittelt.

Praktische Architektur-Muster

Wie zu sehen ist, ist das Schema etwas komplexer. Darüber hinaus fehlt der intuitive Ansatz von Anfrage und Antwort. Doch wie bereits erwähnt, wird ein Verbindungsabbruch während der Verarbeitung der Anfrage nicht zu einem Fehler führen. Außerdem, falls der Benutzer tatsächlich von mehreren Geräten mit dem Dienst verbunden ist (z.B. von einem Mobiltelefon und einem Tablet), kann sichergestellt werden, dass die Antwort auf beide Geräte kommt.

Interessanterweise wird der Code zur Verarbeitung eingehender Nachrichten fast identisch (nicht zu 100%) für Ereignisse, die vom Client selbst beeinflusst werden, sowie für andere Ereignisse, einschließlich solcher von anderen Clients.

In der Praxis erhalten wir jedoch zusätzliche Vorteile, da der unidirektionale Fluss im funktionalen Stil verarbeitet werden kann (unter Verwendung von RX und ähnlichen Technologien). Das ist bereits ein erheblicher Vorteil, da die Anwendung im Grunde vollständig reaktiv gestaltet werden kann, und das auch mit einem funktionalen Ansatz. Für umfangreiche Programme kann dies die Ressourcen für Entwicklung und Wartung erheblich sparen.

Wenn wir diesen Ansatz mit horizontaler Skalierung kombinieren, erhalten wir die Möglichkeit, Anfragen an einen Server zu senden und die Antworten von einem anderen zu erhalten. Auf diese Weise kann der Client den für ihn passenden Dienst auswählen, während das System innerlich die Ereignisse dennoch korrekt verarbeiten kann.

Event Sourcing

Wie Sie wissen, ist eine der Hauptmerkmale eines verteilten Systems das Fehlen einer gemeinsamen Zeit und eines gemeinsamen kritischen Abschnitts. Für einen Prozess können Sie eine Synchronisation (mit denselben Mutexen) durchführen, innerhalb derer Sie sicher sind, dass niemand sonst diesen Code ausführt. Für ein verteiltes System ist das jedoch riskant, da dies mit Overhead verbunden ist und die gesamte Schönheit des Skalierens verloren geht – letzten Endes müssen alle Komponenten auf eine warten.

Hieraus ergibt sich eine wichtige Erkenntnis – ein schnelles verteiltes System kann nicht synchronisiert werden, da wir sonst die Leistung verringern würden. Andererseits ist es oft notwendig, eine bestimmte Konsistenz der Komponenten zu gewährleisten. Und dafür kann man den Ansatz mit eventual consistency, der gewährleistet, dass in Abwesenheit von Datenänderungen nach einer bestimmten Zeitspanne nach der letzten Aktualisierung ("letztlich") alle Anfragen den zuletzt aktualisierten Wert zurückgeben werden.

Es ist wichtig zu verstehen, dass für klassische Datenbanken häufig strikte Konsistenz, bei der jeder Knoten die gleiche Information besitzt (dies wird oft erreicht, wenn eine Transaktion erst als abgeschlossen betrachtet wird, nachdem der zweite Server geantwortet hat). Hier gibt es einige Erleichterungen durch Isolationsebenen, aber das Grundprinzip bleibt dasselbe – Sie können in einer vollständig konsistenten Welt leben.

Kommen wir jedoch zurück zur ursprünglichen Aufgabe. Wenn ein Teil des Systems mit eventual consistencyaufgebaut werden kann, dann kann das folgende Schema erstellt werden.

Praktische Architektur-Muster

Wichtige Merkmale dieses Ansatzes:

  • Jede eingehende Anfrage wird in eine Warteschlange eingefügt.
  • Während der Verarbeitung einer Anfrage kann der Dienst auch Aufgaben in andere Warteschlangen einfügen.
  • Jedes eingehende Ereignis hat eine Identifikationsnummer (die für die Deduplication erforderlich ist).
  • Die Warteschlange funktioniert ideologisch nach dem Prinzip "append only". Elemente können nicht entfernt oder umsortiert werden.
  • Die Warteschlange arbeitet nach dem FIFO-Prinzip (entschuldigen Sie die Tautologie). Wenn Parallelverarbeitung erforderlich ist, sollten die Objekte in einer der Phasen in verschiedene Warteschlangen verschoben werden.

Ich erinnere daran, dass wir den Fall eines Online-Dateispeichers betrachten. In diesem Fall würde das System ungefähr so aussehen:

Praktische Architektur-Muster

Es ist wichtig, dass die in der Diagramm dargestellten Dienste nicht unbedingt einen separaten Server bedeuten. Selbst der Prozess kann derselbe sein. Wichtig ist etwas anderes: Ideologisch sind diese Dinge so getrennt, dass horizontale Skalierung einfach angewendet werden kann.

Für zwei Benutzer würde das Diagramm folgendermaßen aussehen (Dienste, die für verschiedene Benutzer gedacht sind, sind farblich unterschiedlich gekennzeichnet):

Praktische Architektur-Muster

Die Vorteile einer solchen Kombination:

  • Die Informationsverarbeitungsdienste sind getrennt. Auch die Warteschlangen sind getrennt. Wenn wir die Kapazität des Systems erweitern müssen, genügt es, mehr Dienste auf einer größeren Anzahl von Servern zu starten.
  • Wenn wir Informationen vom Benutzer erhalten, müssen wir nicht unbedingt auf die vollständige Speicherung der Daten warten. Im Gegenteil, wir können einfach mit "ok" antworten und dann schrittweise beginnen, zu arbeiten. Gleichzeitig glättet die Warteschlange die Spitzen, da die Hinzufügung eines neuen Objekts schnell erfolgt und der Benutzer nicht auf den gesamten Durchlauf durch den Zyklus warten muss.
  • Zum Beispiel habe ich einen Deduplicierungsdienst hinzugefügt, der versucht, identische Dateien zu kombinieren. Wenn er lange bei 1 % bleibt, wird der Kunde dies praktisch nicht bemerken (siehe oben), was ein großer Vorteil ist, da von uns nicht mehr eine hundertprozentige Geschwindigkeit und Zuverlässigkeit verlangt wird.

Es sind jedoch auch sofortige Nachteile sichtbar:

  • Unserem System fehlt strikte Konsistenz. Das bedeutet, dass man theoretisch bei der Anmeldung zu verschiedenen Diensten unterschiedliche Zustände erhalten kann (da einer der Dienste möglicherweise nicht rechtzeitig die Benachrichtigung von der internen Warteschlange erhält). Ein weiteres Ergebnis davon ist, dass das System jetzt keine gemeinsame Zeit hat. Man kann die Ereignisse also nicht einfach nach der Ankunftszeit sortieren, da die Uhren zwischen den Servern möglicherweise nicht synchron sind (darüber hinaus ist die gleiche Zeit auf zwei Servern eine Utopie).
  • Keine Ereignisse können jetzt einfach zurückgesetzt werden (wie man es mit einer Datenbank tun könnte). Stattdessen muss ein neues Ereignis hinzugefügt werden — compensation event, das den letzten Zustand in den erforderlichen ändern wird. Ein Beispiel aus einem ähnlichen Bereich: Ohne die Historie zu überschreiben (was in einigen Fällen schlecht ist) kann man in git einen Commit nicht zurücksetzen, aber man kann einen speziellen rollback commit, der im Grunde nur den alten Zustand wiederherstellt. Allerdings bleibt der fehlerhafte Commit auch in der Historie erhalten und es erfolgt ein Rollback.
  • Das Datenschema kann von Version zu Version variieren, jedoch können alte Ereignisse nun nicht mehr auf den neuen Standard aktualisiert werden (da Ereignisse grundsätzlich nicht geändert werden können).

Wie zu sehen ist, harmoniert Event Sourcing hervorragend mit CQRS. Darüber hinaus ist es schon schwierig, ein System mit effektiven und benutzerfreundlichen Warteschlangen zu implementieren, ohne Datenströme zu trennen, da Synchronisationspunkte hinzugefügt werden müssen, die den gesamten positiven Effekt der Warteschlangen mindern. Wenn man beide Ansätze kombiniert, müssen die Programmcode nur geringfügig angepasst werden. In unserem Fall erhält man beim Hochladen einer Datei auf den Server lediglich die Rückmeldung 'ok', was nur bedeutet, dass 'die Datei zum Hinzufügen gespeichert wurde'. Formal bedeutet das nicht, dass die Daten bereits auf anderen Geräten verfügbar sind (zum Beispiel kann der Deduplication-Service den Index neu aufbauen). Nach einiger Zeit erhält der Kunde jedoch eine Benachrichtigung im Stil von 'Datei X wurde gespeichert'.

Als Ergebnis:

  • Die Anzahl der Versandstatus für Dateien steigt: Statt des klassischen 'Datei gesendet' erhalten wir zwei: 'Datei in die Warteschlange auf dem Server eingefügt' und 'Datei im Speicher gespeichert'. Letzteres bedeutet, dass andere Geräte bereits mit dem Empfang der Datei beginnen können (unter Berücksichtigung, dass die Warteschlangen mit unterschiedlicher Geschwindigkeit arbeiten).
  • Da die Informationen über den Versand nun über verschiedene Kanäle kommen, müssen wir Lösungen finden, um den Status der Datei Verarbeitung zu erhalten. Dies bedeutet, dass der Client im Gegensatz zum klassischen Request-Response während der Verarbeitung der Datei neu gestartet werden kann, der Status dieser Verarbeitung jedoch korrekt bleibt. Und dieser Punkt funktioniert im Grunde genommen 'out of the box'. Als Konsequenz sind wir jetzt fehlertoleranter.

Sharding

Wie bereits oben beschrieben, gibt es in Systemen mit Event Sourcing keine strikte Konsistenz. Das bedeutet, dass wir mehrere Speicher ohne jegliche Synchronisation zwischen ihnen nutzen können. In Anlehnung an unsere Aufgabe können wir:

  • Dateien nach Typen trennen. Zum Beispiel können Bilder/Videos dekodiert und in ein effizienteres Format ausgewählt werden.
  • Konten nach Ländern trennen. Aufgrund vieler Gesetze kann dies erforderlich sein, jedoch bietet diese Architektur die Möglichkeit, dies automatisch zu tun.

Praktische Architektur-Muster

Wenn Sie Daten von einem Speicherort zu einem anderen übertragen möchten, sind die Standardmittel nicht ausreichend. Leider muss in diesem Fall die Warteschlange angehalten, die Migration durchgeführt und dann erneut gestartet werden. Im Allgemeinen können Daten nicht „live“ übertragen werden, es sei denn, die Ereigniswarteschlange wird vollständig gespeichert, und Sie haben Kopien früherer Speicherzustände, dann können wir Ereignisse wie folgt erneut abspielen:

  • In Event Source hat jedes Ereignis seine eigene Kennung (idealerweise nicht abnehmend). Das bedeutet, dass wir im Speicher ein Feld hinzufügen können – die ID des letzten verarbeiteten Elements.
  • Wir duplizieren die Warteschlange, damit alle Ereignisse für mehrere unabhängige Speicherorte verarbeitet werden können (das erste ist das, in dem die Daten derzeit gespeichert sind, und das zweite ist neu, jedoch zunächst leer). Die zweite Warteschlange wird natürlich vorerst nicht verarbeitet.
  • Wir starten die zweite Warteschlange (d.h. wir beginnen mit der Wiedergabe der Ereignisse).
  • Wenn die neue Warteschlange relativ leer ist (d.h. der durchschnittliche Zeitunterschied zwischen dem Hinzufügen eines Elements und dessen Entnahme akzeptabel ist), können wir beginnen, die Leser auf den neuen Speicherort umzuschalten.

Wie zu sehen ist, gab es in unserem System nie eine strenge Konsistenz. Es gibt nur eventual consistency, d.h. die Garantie, dass Ereignisse in derselben Reihenfolge verarbeitet werden (jedoch möglicherweise mit unterschiedlicher Verzögerung). Und unter Verwendung dessen können wir Daten relativ einfach ohne Stillstand des Systems auf die andere Seite der Welt übertragen.

So bietet uns diese Architektur, fortschreitend bei unserem Beispiel für einen Online-Speicher für Dateien, bereits einige Vorteile:

  • Wir können Objekte näher an die Benutzer bewegen, und zwar dynamisch. Dadurch kann die Service-Qualität verbessert werden.
  • Wir können einen Teil der Daten innerhalb der Unternehmen speichern. Zum Beispiel verlangen Enterprise-Nutzer häufig, dass ihre Daten in kontrollierten Rechenzentren gespeichert werden (um Datenlecks zu vermeiden). Durch Sharding können wir dies problemlos unterstützen. Die Aufgabe wird noch einfacher, wenn der Kunde eine kompatible Cloud hat (z.B. Azure self hosted).
  • Am wichtigsten ist, dass wir das nicht tun müssen. Zu Beginn würde uns bereits ein einziges Speichersystem für alle Konten ausreichen (um schneller zu starten). Eine entscheidende Eigenschaft dieses Systems ist, dass es zwar erweiterbar ist, aber in der Anfangsphase ausreichend einfach bleibt. Man muss nicht sofort Code schreiben, der mit Millionen von separaten, unabhängigen Warteschlangen arbeitet usw. Wenn nötig, kann man das in der Zukunft erledigen.

Statisches Content Hosting

Dieser Punkt mag auf den ersten Blick offensichtlich erscheinen, ist jedoch dennoch notwendig für ein mehr oder weniger standardmäßiges, stark ausgelastetes Anwendungssystem. Die Idee ist einfach: Sämtlicher statischer Inhalt wird nicht vom gleichen Server bereitgestellt, der auch die Anwendung hostet, sondern von speziellen, dafür reservierten Servern. Als Folge laufen diese Operationen schneller ab (ein typischer nginx gibt Dateien schneller und kostengünstiger aus als ein Java-Server). Zudem ermöglicht die Architektur von CDN (Content Delivery Network) es, unsere Dateien näher beim Endnutzer zu platzieren, was die Nutzung des Dienstes verbessert.

Das einfachste und standardmäßigste Beispiel für statischen Inhalt ist eine Sammlung von Skripten und Bildern für eine Webseite. Bei ihnen ist alles klar — sie sind im Voraus bekannt, dann wird das Archiv auf die CDN-Server hochgeladen, von wo aus sie an die Endnutzer verteilt werden.

In der Praxis kann jedoch ein Ansatz ähnlich der Lambda-Architektur für statischen Inhalt genutzt werden. Lassen Sie uns zu unserem Szenario zurückkehren (Online-Dateispeicher), in dem wir Dateien an Benutzer ausgeben müssen. Die einfachste Lösung in der direkten Herangehensweise wäre es, einen Dienst zu erstellen, der für jede Benutzeranfrage alle notwendigen Überprüfungen vornimmt (Autorisation usw.), und dann die Datei direkt aus unserem Speicher herunterlädt. Der Hauptnachteil dieses Ansatzes ist, dass statischer Inhalt (und eine Datei mit einer bestimmten Revision ist in der Tat statischer Inhalt) vom selben Server bereitgestellt wird, der auch die Geschäftslogik enthält. Stattdessen kann man folgendes Schema erstellen:

  • Der Server gibt eine URL zum Herunterladen aus. Diese kann in der Form file_id + key vorliegen, wobei key eine kleine digitale Signatur ist, die den Zugriff auf die Ressource für die nächsten 24 Stunden erlaubt.
  • Das Bereitstellen der Datei übernimmt ein einfacher nginx mit den folgenden Optionen:
    • Content-Caching. Da dieser Dienst auf einem separaten Server gespeichert werden kann, haben wir uns die Möglichkeit für die Zukunft offen gelassen, alle zuletzt heruntergeladenen Dateien auf der Festplatte zu speichern.
    • Überprüfung des Schlüssels beim Erstellen der Verbindung
  • Optional: Streaming von Inhalten. Wenn wir zum Beispiel alle Dateien im Dienst komprimieren, kann die Dekomprimierung direkt in diesem Modul erfolgen. Folglich finden IO-Operationen dort statt, wo sie hingehören. Ein Archivierungsprogramm in Java könnte viel zusätzlichen Speicher beanspruchen, jedoch könnte es auch ineffektiv sein, den Dienst mit Geschäftslogik auf bedingte Sprachen wie Rust/C++ umzuschreiben. In unserem Fall verwenden wir verschiedene Prozesse (oder sogar Dienste), was es ermöglicht, Geschäftslogik und IO-Operationen effektiv zu trennen.

Praktische Architektur-Muster

Ein solches Schema ähnelt nicht sehr der Bereitstellung von statischen Inhalten (da wir das gesamte Paket statischer Inhalte nicht irgendwo auslagern), dennoch beschäftigt sich dieser Ansatz in der Realität genau mit der Bereitstellung von unveränderbaren Daten. Darüber hinaus kann dieses Schema auch auf andere Fälle verallgemeinert werden, in denen der Inhalt nicht nur statisch ist, sondern als eine Menge unveränderlicher und nicht löschbarer Blöcke präsentiert werden kann (auch wenn sie hinzugefügt werden können).

Ein weiteres Beispiel (zur Festigung): Wenn Sie mit Jenkins/TeamCity gearbeitet haben, wissen Sie, dass beide Lösungen in Java geschrieben sind. Sie sind beide Java-Prozesse, die sowohl die Orchestrierung von Builds als auch das Management von Inhalten übernehmen. Insbesondere haben sie beide Aufgaben, wie zum Beispiel "eine Datei/einen Ordner vom Server zu übergeben". Ein Beispiel: Bereitstellung von Artefakten, Übertragung des Quellcodes (wenn der Agent den Code nicht direkt aus dem Repository herunterlädt, sondern dies der Server übernimmt), Zugriff auf Protokolle. Alle diese Aufgaben unterscheiden sich in ihrer IO-Last. Das bedeutet, dass der Server, der für komplexe Geschäftslogik verantwortlich ist, auch in der Lage sein muss, große Datenströme effizient zu verarbeiten. Und das Interessanteste ist, dass eine solche Operation genau auf dieselbe Weise an nginx delegiert werden kann (vorausgesetzt, wir fügen dem Anfrage einen Datenschlüssel hinzu).

Wenn wir jedoch zu unserem System zurückkehren, ergibt sich folgendes Schema:

Praktische Architektur-Muster

Wie sichtbar ist, hat sich das System radikal kompliziert. Es handelt sich nicht mehr nur um einen Mini-Prozess, der Dateien lokal speichert. Jetzt ist auch eine nicht ganz einfache Unterstützung, Versionierung des API usw. erforderlich. Daher ist es am besten, nachdem alle Diagramme gezeichnet sind, sorgfältig abzuwägen, ob die Skalierbarkeit solche Ausgaben rechtfertigt. Wenn Sie jedoch die Möglichkeit haben möchten, das System auszubauen (insbesondere für die Arbeit mit einer noch größeren Anzahl von Benutzern), müssen Sie solche Lösungen in Betracht ziehen. Als Ergebnis ist das System architektonisch bereit für eine höhere Last (praktisch jede Komponente kann für horizontale Skalierung geklont werden). Das System kann aktualisiert werden, ohne es herunterfahren zu müssen (einige Operationen können lediglich leicht verlangsamt werden).

Wie ich zu Beginn erwähnt habe, sehen sich derzeit viele Internetdienste einer erhöhten Last gegenüber. Einige von ihnen fangen einfach an, nicht mehr richtig zu funktionieren. Tatsächlich sind die Systeme genau zu dem Zeitpunkt ausgefallen, als das Geschäft Geld verdienen sollte. Anstatt auf ein späteres Versanddatum hinzuweisen, anstatt den Kunden zu sagen: „Planen Sie die Lieferung für die kommenden Monate“, hat das System einfach gesagt: „Geht zu den Wettbewerbern“. Das ist der Preis niedriger Leistung: Verluste treten genau dann auf, wenn der Gewinn am höchsten wäre.

Fazit

All diese Ansätze waren früher bereits bekannt. Das gleiche VK nutzt schon lange die Idee des Static Content Hosting zur Auslieferung von Bildern. Viele Online-Spiele verwenden ein Sharding-System, um Spieler nach Regionen zu trennen oder um Spielstandorte (wenn die Welt einheitlich ist) zu trennen. Der Event Sourcing-Ansatz wird aktiv im E-Mail-Verkehr verwendet. Die meisten Anwendungen von Händlern, bei denen kontinuierlich Daten empfangen werden, basieren tatsächlich auf dem CQRS-Ansatz, um die erhaltenen Daten filtern zu können. Und horizontale Skalierung wird schon seit geraumer Zeit in vielen Diensten eingesetzt.

Aber das Wichtigste ist, dass all diese Muster in modernen Anwendungen sehr leicht anzuwenden sind (sofern sie relevant sind). Die Cloud bietet sofort Sharding und horizontale Skalierung an, was viel einfacher ist, als verschiedene dedizierte Server in verschiedenen Rechenzentren selbst zu bestellen. CQRS ist deutlich einfacher geworden, nicht zuletzt wegen der Entwicklung von Bibliotheken wie RX. Vor 10 Jahren konnte nur selten eine Website so etwas unterstützen. Event Sourcing kann ebenfalls unglaublich einfach konfiguriert werden dank bereits vorhandener Container mit Apache Kafka. Vor 10 Jahren wäre das eine Innovation gewesen, heute ist es alltäglich. Ähnlich verhält es sich mit dem Hosting von statischen Inhalten: Aufgrund besserer Technologien (unter anderem, weil es ausführliche Dokumentation und eine große Wissensbasis gibt) ist dieser Ansatz noch einfacher geworden.

Zusammenfassend lässt sich sagen, dass die Umsetzung einer Reihe von recht komplexen Architekturmustern jetzt viel einfacher geworden ist, und das sollte man sich im Voraus ansehen. Wenn in einer zehnjährigen Anwendung eine der obigen Lösungen aufgrund der hohen Kosten für Implementierung und Betrieb verworfen wurde, kann man jetzt in einer neuen Anwendung oder nach einer Refaktorisierung einen Dienst schaffen, der architektonisch sowohl skalierbar (in Bezug auf die Leistung) als auch bereit für neue Anforderungen von Kunden ist (zum Beispiel für die Lokalisierung personenbezogener Daten).

Und das Wichtigste: Bitte verwenden Sie diese Ansätze nicht, wenn Sie eine einfache Anwendung haben. Ja, sie sind schön und interessant, aber für eine Website mit einer maximalen Besucherzahl von 100 Personen kann man oft mit einem klassischen Monolithen auskommen (zumindest von außen, innen kann alles in Module unterteilt werden usw.).

Quelle: habr.com

60GB SSD 8Gb DDR4