In der Vergangenheit ist mir oft ein gängiger Ansatz aufgefallen, bei dem Entwickler (einschließlich mir selbst natürlich) in jedem Fall dieselbe API sowohl für Lese- als auch für Schreibvorgänge verwendeten. Noch häufiger verließen wir uns für die Verarbeitung beider Vorgänge auf dieselbe Datenquelle, beispielsweise MySQL/PostgreSQL.
Dies bedeutet, dass in dieselben Spalten geschrieben und aus ihnen gelesen wird, was häufig zu Problemen bei der Indexoptimierung für stark abgefragte Felder führt.
So mussten wir beispielsweise häufig Indizes optimieren, um neue Filter zu berücksichtigen oder die Abfrageleistung zu verbessern. Felder, die mit Operatoren wie LIKE verwendet wurden, stellten aufgrund ihrer Auswirkungen auf die Leistung eine besondere Herausforderung dar.
Diese Änderungen führen häufig zu weiteren Anpassungen am Backend, einschließlich der Änderung von APIs, um die aktualisierte Funktionalität verfügbar zu machen, gemessenen Zeiten aufgrund zusätzlicher JOINs und so weiter …
Um die Herausforderung zu bewältigen, neue Filter und andere Dinge in die API einzufügen, gab es Versuche, den Prozess mithilfe von Tools und Standards wie Apicalypse und natürlich GraphQL zu optimieren.
Diese Lösungen zielten darauf ab, die Generierung von API-Abfragen zu optimieren und den manuellen Aufwand für die Implementierung neuer Filter und Funktionen zu reduzieren und einen dynamischeren Ansatz für die Handhabung des Datenzugriffs zu bieten. Allerdings war die Lernkurve steil.
Mit dem Aufkommen von CQRS (Command Query Responsibility Segregation) begann sich ein neuer Ansatz abzuzeichnen. Diese Denkweise förderte die Verwendung separater Quellen für Schreib- und Lesevorgänge. Schreibvorgänge konnten Ereignisse ausgeben und Lesevorgänge konnten aus diesen Ereignissen an dedizierten Stellen Ansichten erstellen. Selbst wenn die Lese- und Schreibvorgänge in derselben Datenbank (aber in unterschiedlichen Tabellen) verwaltet wurden, brachte diese Trennung erhebliche Vorteile und konnte natürlich die zweite Herausforderung beseitigen – JOINs und Suchanfragen bei Domänenmodellen, da Lesemodelle üblicherweise in Form von denormalisierten JSONs vorliegen.
Dies brachte jedoch ein weiteres Problem mit sich. Bei Lesevorgängen mussten wir Schreibvorgänge skalieren, was bedeutete, dass der einzige Grund, warum wir Instanzen unserer Anwendung von X auf Y skalieren mussten, Lesevorgänge waren. Dieses Problem könnte teilweise durch Caching gemildert werden, und in der Welt der Microservices könnten wir dedizierte Microservices für Lesevorgänge haben.
Aber...
Dies war jedoch keine ideale Lösung für andere Architekturstile wie modulare Monolithen, bei denen eine solche Trennung möglicherweise nicht gut mit der Designphilosophie des Systems vereinbar ist. Außerdem war das ganze Produkt ausgefallen, wenn die API ausgefallen war, und wenn man bedenkt, dass die meisten Produkte mehr Lese- als Schreibvorgänge erfordern, konnte dies unnötige Auswirkungen auf das Geschäft haben (natürlich bei Geräten mit ausgefallener API ;) )
Was wäre also, wenn wir diese „Ansichten“, auch Lesemodelle genannt, direkt abfragen könnten, ohne die API einzubeziehen und Lasten zu verarbeiten? Hier kommen Lösungen wie Meilisearch , AppSearch und andere ins Spiel, die ein Muster namens „Valet Key“ nutzen. Durch die Verwendung dieses Musters können Frontends direkt auf leseoptimierte Modelle zugreifen und so die Abhängigkeit von Backend-APIs verringern. Natürlich muss das Frontend die API immer noch nach dem „Valet Key“ „abfragen“, aber das Frontend kann Schlüssel zwischenspeichern, sodass das Frontend auch dann noch kommunizieren und Inhalte anzeigen kann, wenn die API ausgefallen ist.
Mit diesem Ansatz können wir uns auf die Lesedatenbank konzentrieren und müssen uns nicht um die Handhabung des Datenverkehrs für Lesevorgänge in unserer API kümmern. Der „Valet Key“, der dem Frontend über unsere API bereitgestellt wird, ist so gesichert, dass das Frontend ihn nicht ändern kann. Er enthält vordefinierte Filter und Indizes.
Wenn das Frontend zusätzliche Funktionen benötigt, kann es diese über die API anfordern, wo die API überprüfen kann, ob sie zugelassen werden. Es sind trotzdem weniger Aufrufe erforderlich.
Einige Vorteile, die ich sehe, sind:
Aber es gibt immer auch Nachteile:
Dieser Ansatz ist also kein Allheilmittel und bringt seine eigenen Herausforderungen mit sich. Wenn Sie jedoch mit den Nachteilen einverstanden sind, erfordert eine kleine Änderung am Frontend wahrscheinlich keine Einbeziehung des Backend-Teams. Dadurch wird der Entwicklungsprozess rationalisiert und die allgemeine Agilität verbessert. Und natürlich sollte auch die Skalierbarkeit einfacher sein.