Continuous Integration (CI) ist für moderne Softwareentwicklungsprozesse unverzichtbar geworden. Es handelt sich um die Praxis, Codeänderungen regelmäßig zu integrieren, zu testen und zu verifizieren, um Fehler frühzeitig zu erkennen und zu beheben. Während viele Unternehmen in der Vergangenheit mit langsamen und komplizierten Pipelines zu kämpfen hatten, setzt Nerve mit seiner CI-Philosophie neue Maßstäbe. Schnell, stabil und eigenständig – so sieht die Idealvorstellung für CI bei Nerve aus. Diese Strategie entstand aus eigenen Erfahrungen und dem Wunsch, eine Entwicklererfahrung zu schaffen, die Motivation und Produktivität fördert.
In den folgenden Abschnitten wird die Herangehensweise erläutert, die Architektur beleuchtet und die daraus entstehenden Vorteile für das Entwicklerteam hervorgehoben. Die Inspiration hinter Nerve's CI-System rührt nicht zuletzt von Erlebnissen früherer Arbeitsstationen her, an denen sich monatelanger Frust angesammelt hatte, da eine CI-Laufzeit von mehreren Stunden den Entwicklungsfluss massiv behinderte. Das Ziel war klar: schnelle Rückmeldung und ein robustes, nachvollziehbares System, das auch über längere Zeit stabil bleibt und sich unabhängig von bestimmten Vendoren oder Spezialwerkzeugen reproduzieren lässt. Das Resultat zeigt sich in durchweg kurzen CI-Durchlaufzeiten von drei bis dreieinhalb Minuten, was sich bei einem Monorepo mit etwa 300.000 Zeilen TypeScript-Code als bemerkenswerte Leistung zeigt.
Die Architektur von Nerve ist eine wichtige Grundlage für dieses CI-Konzept. Nerve ist ein universelles SDK, das es ermöglicht, APIs zu verwenden, für die keine passenden SDKs existieren oder die nicht den Anforderungen entsprechen. Dabei besteht Nerve aus einer webbasierten Anwendung, die als Kontrollzentrum und Konfigurationsplattform dient, und einer Abfrage-Engine, die tatsächlich die Ausführung der Anfragen übernimmt. Beide Komponenten sind in TypeScript umgesetzt und bilden gemeinsam ein homogenes, stark modularisiertes System innerhalb eines Monorepos. Hier wird der sogenannte Google-ähnliche Monorepo-Ansatz verfolgt, wenn auch im deutlich kleineren Maßstab.
Alle Pakete teilen sich zentrale Konfigurationsdateien, Skripte und Build-Prozesse. Ein weiterer Grundbaustein für die schnelle und stabile CI sind die Prinzipien, die bei der Entwicklung der Build-Pipeline beachtet wurden. Die Vermeidung von speziellem oder umständlichem Code spielt dabei ebenso eine Rolle wie die Möglichkeit, sämtliche Prozesse auch lokal reproduzieren und debuggen zu können. Dabei setzt man bei Nerve auf Standard-Tools und hauptsächlich auf etablierte Technologien und Praktiken, um die Hürde für neue Entwickler niedrig und die Wartbarkeit hoch zu halten. Hilfreich ist hier vor allem die strikte Trennung von Code in reine, zustandslose Funktionen und stateful Adapter.
Diese Architektur erlaubt es, Tests schnell und zuverlässig zu schreiben und durchzuführen, da pure Funktionen leicht parallelisiert getestet werden können und keine umfangreiche Infrastruktur oder Mocks benötigen. Die Integrationstests bei Nerve folgen ebenfalls einem innovativen Ansatz: Statt klassische End-to-End-Tests mit aufwendiger Infrastruktur werden viele Integrationstests als „Intra-Process Integration Tests“ ausgeführt, also innerhalb eines einzigen Prozesses. Das bedeutet, dass verschiedene Komponenten, die in Produktion auf unterschiedlichen Hosts laufen, im Test zusammengeführt und dadurch schnell in Memory getestet werden. Das steigert nicht nur die Geschwindigkeit, sondern auch die Zuverlässigkeit, da externe Faktoren weitgehend entfallen. Ein wichtiger Aspekt der CI-Pipeline ist das effiziente Build- und Caching-System.
Da das Monorepo aus über hundert verschiedenen Paketen besteht, ist die vollständige Neukompilierung zeitintensiv. Nerve nutzt daher die TypeScript Build-Mode-Option (-b), die inkrementelle Builds möglich macht. Um wirklich von diesem Mechanismus zu profitieren, hat man eine eigene Utility namens „hydrator“ entwickelt. Hydrator verwaltet Build-Artefakte und deren Verteilung innerhalb der Infrastruktur. Dabei werden die kompilierten Dateien nicht kopiert, sondern verschoben, was die Vorgänge beschleunigt und Speicherplatz effizient nutzt.
Zudem sorgt ein Signierungsmechanismus für Sicherheit, sodass ausschließlich vertrauenswürdige Builds verwendet werden können. Ein weiterer kritischer Punkt ist die Abhängigkeitsverwaltung mittels Yarn Workspaces. Die gesamte Paketstruktur wird durch ein zentrales Yarn-System gemanagt, wodurch Entwickler mit einem einzigen Befehl alle nötigen Abhängigkeiten installieren können. Um Netzwerk- und Performanceprobleme zu minimieren, werden die Abhängigkeiten ebenfalls gepuffert und über den bereits erwähnten hydrator gecached. So sind viele Pakete bereits lokal vorhanden, was den Installationsprozess in CI massiv beschleunigt.
Ein besonderes Augenmerk gilt hier der Sicherheit der Paketquellen. Dank der Hash-basierten Verifikation in „yarn.lock“ wird verhindert, dass manipulierte oder falsche Pakete unbemerkt ins System gelangen. Die Containerisierung der einzelnen Nerve-Komponenten ist hingegen auf schlanke, minimalistische Images fokussiert. Über einen eigens eingerichteten, temporären Verdaccio-Registry-Server werden die internen Pakete publiziert und über diesen Ordner in den Containern installiert.
Dadurch wird verhindert, dass das komplette Monorepo in den Images landet, was Größe und Boot-Zeiten minimiert. Als Basis-Images dienen schlanke Alpine Linux-Container, die eine gute Grundlage für schnelle und sichere Container bereitstellen. Dieser Prozess, der streng auf Standard-Tools aufbaut und händisch kontrolliert wird, dauert trotz aller Komplexität nur knapp anderthalb Minuten pro Container, was im Vergleich zu vielen anderen Systemen sehr effizient ist. Die gesamte CI-Workflow-Konfiguration ist offen und modular aufgebaut. GitHub Actions orchestriert die Abläufe, von der initialen Hydration über das Kompilieren, Testen bis hin zum Container-Build und dem Erstellen der Frontend-Bundles.
Praktisch alle Schritte sind in Shell-Skripten und Node-basierten Hilfsprogrammen organisiert. Die Frontend-Bundles werden mit esbuild kompiliert, was für schnelle Builds sorgt und automatisch Tree-Shaking sowie Minifizierung ermöglicht. Trotz der Tatsache, dass die Frontend-Anwendung ein umfangreicher Thick Client ist, bleibt die Bundle-Größe überschaubar, ungefähr 400kb gzipped. Wie man an diesen Methoden sieht, ist Nerve sehr klar darin, seine Prinzipien und Werte in jeder Ebene umzusetzen. Die Dev-Experience steht im Mittelpunkt, weshalb lokale Reproduzierbarkeit, Verständlichkeit und Stabilität über alles gestellt werden.
Die Entwickler müssen keine „Spezialbehandlungen“ für den CI-Prozess erlernen oder befolgen. Alles läuft vollautomatisiert und transparent im Hintergrund. Natürlich ist so ein System niemals „fertig“. Die Entwickler haben eine Reihe von Plänen für die Zukunft, zum Beispiel die Umstellung auf tsc 7, eine neue, in Go geschriebene TypeScript-Version, die den Buildprozess nochmals massiv beschleunigen wird. Auch die Idee, Shell-Skripte sukzessive in TypeScript zu überführen, steht im Raum, um den Entwicklungsprozess noch zugänglicher und wartbarer zu gestalten.
Ebenso ist geplant, die Abhängigkeits-Cache-Mechanismen weiter zu verfeinern, um den Speicherverbrauch bei langfristiger Nutzung zu optimieren. Die Erweiterung des Testportfolios steht ebenfalls ganz oben auf der Agenda, besonders im Bereich Intra-Process Integration Tests, die mit ihrem eleganten Ansatz die Testgeschwindigkeit und Verlässlichkeit weiter steigern können. Besonders spannend ist auch der Ansatz, Performance-Tests in die CI zu integrieren. Da Nerve selbst ein Entwickler-Tool ist, wird viel Wert auf die Schnelligkeit der Engine gelegt, die unmittelbaren Einfluss auf die Nutzererfahrung der Kunden hat. Automatisierte Messungen von Metriken wie Anfragezeiten oder der Time To Interactive (TTI) für die Web-App sollen zukünftig dazu beitragen, Regressionen früh zu erkennen und gezielt zu verbessern.
Zusammenfassend zeigt das CI-System von Nerve, wie mit klaren Prinzipien, hohem Automatisierungsgrad und schlanker, aufeinander abgestimmter Infrastruktur eine moderne Pipeline realisiert werden kann, die kurze Feedback-Loops ermöglicht und Entwicklern Freude macht. Die Kombination aus gezieltem Caching, pragmatischen Tools und einer durchdachten Architektur ermöglicht es, zahlreiche Stolpersteine großer und komplexer Softwareprojekte zu vermeiden. Wer auf der Suche nach bewährten Best Practices für CI in TypeScript-Projekten ist, findet bei Nerve zahlreiche Inspirationen – angefangen von der Monorepo-Verwaltung über den Einsatz von Yarn Workspaces bis hin zur innovativen Teststrategie und der Containerisierung. Zudem ist die Offenheit und Dokumentation des Workflows ein weiterer Pluspunkt, der nachvollziehbare Automatisierung und einfache Wartung ermöglicht. Entwickler, die sich mit ähnlichen Herausforderungen konfrontiert sehen, können von der Flexibilität und Zuverlässigkeit des beschriebenen Systems profitieren und viele Ideen für die eigene Infrastruktur gewinnen.
Letztendlich ist der klare Anspruch von Nerve ein wichtiger Treiber: Die Technik soll dem Menschen dienen, ihn motivieren und nicht frustrieren. Mit einem CI-System, das schnell, stabil und eigenständig läuft, werden Entwickler nicht ausgebremst, sondern können sich auf das konzentrieren, was sie am besten können – großartigen Code zu schreiben und innovative Produkte zu entwickeln.