Eine neue Architektur für heise online: Janus – Teil 1

Die Web-Entwicklung von heise online hat ihre Architektur auf ein eventbasiertes System umgestellt und stellt dieses vor.

In Pocket speichern vorlesen Druckansicht 40 Kommentare lesen
Lesezeit: 6 Min.
Inhaltsverzeichnis

Die Web-Entwicklung von heise online hat ihre Architektur auf ein eventbasiertes System umgestellt und stellt dieses vor.

Schon in einem Beitrag zu 25 Jahren heise online gaben meine Kollegen Philipp Busse und Benjamin Deutsch unter anderem einen Ausblick auf unsere neue Architektur namens Janus. Heute stellen wir sie einmal im Detail vor.

heise online funktioniert seit langem wie eine klassische Website: Ein User ruft eine URL auf, der Cache-Dienst liefert sie direkt bei Vorhandensein aus oder fragt sie sonst beim Webserver an. Der Webserver gibt die Anfrage weiter an den Backend-Code, der die benötigten Daten zusammenstellt. Anschließend wandern sie wieder die Kette zurück bis zum User. Diese Kette funktioniert nun (bald) ganz anders.

Als Ausgangspunkt für redaktionelle Inhalte bleibt weiterhin das CMS bestehen, aber fast alles, was bisher zwischen CMS und Cache-Server kam, wird abgelöst. Neben der Architektur vollziehen wir damit auch einen Sprachenwechsel. Da Perl nicht mehr unbedingt das "New Kid On The Block" ist (ähnlich wie dieser Vergleich), haben wir uns schon im vorletzten Jahr intensiv mit einer Sprachnachfolge auseinandergesetzt. Es wurden viele Diskussionen in großer Runde geführt, kleine Test-Projekte mit verschiedenen Sprachen geschrieben und Vor- sowie Nachteile abgewogen. Zum Schluss fiel die Wahl auf JavaScript (Node.js) mit TypeScript und dem Framework NestJS.

Zentraler Bestandteil der neuen Architektur ist ein NATS Streaming Server. Passiert irgendetwas in unserem System wird ein Event ausgelöst. Prozesse abonnieren bestimmte Events, führen daraufhin etwas aus und lösen eventuell nochmal selbst Events aus.

Um es konkreter zu machen: Durch das Speichern eines Beitrags im CMS wird ein Event ausgelöst, woraufhin ein Worker die benötigten Daten in eine MongoDB schreibt. Anschließend sind alle Daten des Beitrags in der Datenbank und wir können von dort mittels GraphQL je nach Bedarf die benötigten Daten mit einem Request abfragen. Hat sich an dem Artikel beispielsweise auch der Status von "in Bearbeitung" zu "Veröffentlicht" geändert wird nun ein weiteres Event für die Veröffentlichung ausgelöst. Zwei Worker, die für die Bestückung der heise-online-Startseite und für die Befüllung des RSS-Feeds zuständig sind, haben dieses Event abonniert und werden nun ihrerseits aktiv. Ein weiterer Worker reagiert auf ein Event bei neuen Forenbeiträgen und passt die dargestellte Beitragszahl der Artikel an.

So können in diesem System natürlich noch dutzende weitere Worker aktiv werden, sobald das von ihnen abonnierte Event ausgelöst wird. Dabei ist ein Worker ein Service, der in einem Container läuft und daher nach Bedarf gut skaliert werden kann, wenn von einem bestimmten Event einmal mehr auflaufen.

Wir haben damit unsere Architektur von einem Pull- zu einem Push-System gewechselt. Die ausspielenden Schichten fragen nicht mehr regelmäßig nach, ob sich Inhalte verändert haben, sondern die verändernden Schichten teilen mit, dass es Änderungen gegeben hat.

Die gesamte Architektur des Systems ist in Ringen aufgebaut (Onion Architecture), die möglichst losgelöst voneinander sind – ein äußerer Ring hat nur Abhängigkeiten zum Inneren, nie andersherum. Diese Trennung hat außerdem den Nebeneffekt, dass einzelne Bereiche leicht ausgetauscht werden können. Beispielsweise wird die bereits erwähnte MongoDB in einem Ring behandelt, der für das Persistieren der Daten zuständig ist. Alle anderen Bereiche sprechen nur noch mit dieser Schicht, aber gar nicht mehr direkt mit der MongoDB, sodass weder (Fach-)Wissen über die MongoDB bestehen muss, noch es später einmal dutzende Stellen zum Anpassen gäbe, wenn wir stattdessen eine andere Datenbank einsetzen wollten.

Die Struktur unserer Code-Repositories hat sich mit Janus ebenfalls geändert: Wir sind auf ein Monorepo umgestiegen. Hauptgrund für uns waren schlechte Erfahrungen mit mehreren Repositories. Nach außen ist heise online eine Website, intern besteht es jedoch aus diversen Projekten in eigenen Repositories. Diese Projekte haben hier und da Überlappungen und greifen ineinander. Das ist häufig kein Problem, jedoch kam es immer wieder vor, dass für bestimmte Umsetzungen drei Projekte in verschiedenen Branches zur Entwicklung ausgecheckt werden mussten. Man kann sich vorstellen, dass sich der Rollout ähnlich kompliziert gestaltete.

Mit dem Monorepo gibt es nun in solchen Fällen einen Branch und auch Commits können sinnvoll über mehrere Projekte zusammengehörige Code-Anpassungen erfassen. Die Wartung des Repositories ist ebenfalls einfacher geworden. CI-Einstellungen müssen nicht mehrfach gepflegt werden und das aktuell halten der Abhängigkeiten ist überschaubarer.

Ein weiterer Aspekt der neuen Architektur ist die durchgehende Typisierung durch TypeScript. Es ist klar definiert welche Datentypen in der Datenbank liegen, welche Datentypen von Methoden als Parameter entgegengenommen werden und welchen Typ der Rückgabewert hat. Auch Code-Refaktorierungen gehen so viel leichter von der Hand und Laufzeitfehler sind nahezu verschwunden. Hat man versehentlich mal Code geschrieben, der gegen den definierten Typen verstößt, weißt einen der Editor sofort darauf hin und man kann den Fehler gleich beheben. Ein Pre-Commit-Hook sorgt schlussendlich dafür, dass übersehene Editor-Hinweise definitiv nicht im Repository landen. Die Typisierung besteht nicht nur im Backend-Code, sie setzt sich bis zum Template fort.

Auch im Frontend hat sich einiges getan. Darauf werden wir in Kürze in einem separaten Beitrag näher eingehen. (hih)