Ein Zuhause für den Dev-Log — und die stille Arbeit danach
Foto: Ian Livesey · StockSnap (CC0 1.0), via Openverse
Beim letzten Mal habe ich die wöchentliche Serie beendet — mit einem Versprechen: Der Dev-Log gehe weiter, sobald es das nächste große Stück zu erzählen gibt. Dieses Stück ist jetzt da, und es hat eine hübsche Doppelbödigkeit. Denn worüber ich schreibe, ist der Ort, an dem dieser Text selbst erscheint.
Der Dev-Log lag bisher als lose Sammlung von Markdown-Dateien im Repository — geschrieben, aber ohne eigenes Zuhause. Diese Phase hat ihm eines gegeben: eine eigenständige Blog-Engine, sauber in die Marketing-Website integriert, zweisprachig unter /de/blog und /en/blog. Und weil ein solcher Umzug selten ohne Überraschungen bleibt, erzählt dieser Beitrag beides — wie der Blog entstand, bis hinein in das SDK, das ihn trägt, und was in der stilleren Woche danach geschah, in der eine wachsende Testabdeckung gleich drei echte Produktionsfehler ans Licht holte.
Ein Blog, der sich selbst trägt
Die naheliegende Lösung für einen Firmenblog ist, ihn irgendwie in die bestehende Website zu quetschen — ein paar Markdown-Dateien, ein Renderer, fertig. Ich habe mich bewusst dagegen entschieden. Der Blog läuft jetzt auf einer eigenständigen, gehosteten Blog-Engine, und die Xircuit-Website bindet ihn über deren .NET-SDK ein. Das klingt nach mehr Aufwand, und kurzfristig ist es das auch. Aber es trennt sauber, was ohnehin nichts miteinander zu tun hat: das Schreiben und Verwalten von Beiträgen auf der einen Seite, das Ausliefern der Marketing-Seiten auf der anderen. Der Blog bekommt Kategorien, Tags, Entwürfe, geplante Veröffentlichung, RSS und eine Mediathek, ohne dass ich davon eine Zeile selbst pflegen muss — und die Website bleibt schlank.
Nach außen ist davon nichts zu sehen, und genau so soll es sein. Die Beiträge erscheinen unter /de/blog und /en/blog, im selben Layout wie der Rest der Seite, mit Canonical-URLs, Sitemap-Einträgen, RSS und strukturierten Daten für Suchmaschinen. Ein Aufruf des nackten /blog leitet dauerhaft auf die passende Sprachvariante um. Der Blog fühlt sich an wie ein Teil der Website — er ist aber ein eigenständiges System, das nur an genau den richtigen Stellen durchscheint.
Die Tücke steckt im Routing
Dass zwei Systeme sich nach einem anfühlen, ist Arbeit — und die Reibungspunkte lagen erwartungsgemäß dort, wo Website und Blog sich berühren. Der erste war die Sprache. Die Marketing-Website bestimmt ihre Kultur aus einem /{culture}-Präfix im Pfad; der Blog-Bereich des SDK dagegen brachte seine eigene Vorstellung mit und routete über ein {lang}-Segment. Das Ergebnis: /de/blog zeigte zwar die deutschen Beiträge, aber mit englischem Rahmen — Navigation, Fußzeile, alles ringsherum in der falschen Sprache. Die Korrektur war ein zweiter Kultur-Anbieter, der eigens auf das {lang}-Segment des Blogs hört und die Kultur daraus ableitet, zusätzlich zum bestehenden für den Rest der Seite. Zwei Anbieter, die sich die Zuständigkeit teilen, statt einem, der beides erraten muss.
Der zweite Reibungspunkt waren Bilder. Beiträge referenzieren ihre Bilder mit wurzel-relativen Pfaden — /media/blob/…, so wie sie in der Mediathek der Blog-Engine liegen. Auf der Website liefen diese Pfade ins Leere, weil dort unter /media schlicht nichts liegt; die Bilder wohnen auf dem Blog-Host. Eine kleine Middleware leitet solche /media-Anfragen jetzt an den Blog-Host weiter, sodass die Bilder im Beitrag erscheinen, obwohl sie physisch woanders liegen. Der lehrreiche Teil war eine Ausnahme davon: Das mitgelieferte Stylesheet des SDK liegt unter /_content — dem Standardpfad für statische Dateien einer Razor-Class-Library. Ohne eine ausdrückliche Ausnahme hätte dieselbe Weiterleitung auch dieses Stylesheet auf den Blog-Host geschickt, wo es nicht liegt, und die Seite wäre ungestylt geblieben. Solche Ein-Zeilen-Ausnahmen sind unsichtbar, bis sie fehlen.
Vom Bootstrap-Unfall zum semantischen Markup
Die erste Version des eingebundenen Blogs sah falsch aus, und zwar auf eine aufschlussreiche Weise. Das SDK brachte sein Markup im Bootstrap-Stil mit — Klassen und eine Struktur, die eine ganz bestimmte CSS-Bibliothek voraussetzen. Die Xircuit-Website ist aber mit Tailwind gebaut. Zwei Design-Welten, die nichts voneinander wissen: Das Ergebnis war ein Blog, der zwar funktionierte, aber wie ein Fremdkörper wirkte. Mein erster Reflex war, das gegenzusteuern — ein eigenes, auf .pn-blog eingegrenztes Stylesheet, das die Bootstrap-Struktur nachbaute und an das Erscheinungsbild der Seite anglich. Das funktionierte, war aber im Grunde eine Reimplementierung fremder Annahmen: rund 360 Zeilen CSS, nur um gegen ein Markup anzukämpfen, das ich gar nicht wollte.
Die saubere Lösung lag nicht in mehr CSS, sondern im SDK selbst. Es bekam eine neue Option — einen Markup-Stil, der framework-freies, semantisches HTML ausgibt: schlichte pn-*-Klassen ohne Bootstrap-Annahmen, dazu ein mitgeliefertes, über CSS-Variablen thematisierbares Stylesheet. Mit dieser Option ließ sich der Blog auf den semantischen Modus umstellen, die Marke über eine Handvoll --pn-*-Variablen einfärben — und die 360 Zeilen Nachbau schrumpften auf knapp 60. Das ist die Art von Änderung, die ich am liebsten mag: nicht mehr Code, der ein Problem umschifft, sondern weniger Code, weil das Problem an der Wurzel verschwindet.
Foto: Aaron Burden · StockSnap (CC0 1.0), via Openverse
Dogfooding bis ins SDK
Der aufmerksame Leser hat es schon bemerkt: Zweimal war die richtige Lösung nicht, an der Website zu schrauben, sondern das SDK zu erweitern — einmal für die konfigurierbare Sprach-Route, einmal für den semantischen Markup-Stil. Das ist kein Zufall, sondern eine Haltung. Die Blog-Engine ist selbst ein Produkt aus demselben Haus, und Xircuit ist einer ihrer ersten ernsthaften Nutzer. Was mir hier an Kanten auffiel, fiel damit auch dem SDK auf — und die sauberste Reparatur war fast immer die, die stromaufwärts ansetzt, dort, wo die fehlende Option herkommt, statt sie stromabwärts kosmetisch zu kaschieren.
Konkret sind aus dieser Phase zwei SDK-Versionen auf NuGet entstanden: eine, die das konfigurierbare Sprach-Routing samt umfassender SEO-Ausgabe brachte, und eine zweite mit dem semantischen Markup-Stil. Xircuit konsumiert beide. Dieses Hin und Her — ein echter Anwendungsfall deckt eine Lücke im Werkzeug auf, das Werkzeug wird besser, der Anwendungsfall profitiert sofort — ist das eigentliche Argument fürs Dogfooding. Und es hatte einen unerwarteten Wächter: Ein plattformübergreifender Fehler in der URL-Erzeugung, der auf meinem Windows-Rechner unsichtbar blieb, aber unter Linux relative statt absolute URLs produziert hätte, fiel erst im Linux-Lauf der CI auf — noch bevor irgendetwas veröffentlicht war. Genau dafür baut man eine Pipeline, die auf mehreren Betriebssystemen läuft: damit das Naheliegende nicht das Trügerische wird.
Die stille Arbeit danach
Mit dem Blog live wurde es äußerlich wieder ruhig — und genau das war der Boden für die zweite Hälfte dieser Phase. Ich habe die Testabdeckung dort weitergetrieben, wo die letzten unbeobachteten Pfade lagen, und dabei ein Muster fortgeführt, das sich schon vorher bewährt hatte: eine gemeinsame Szenario-Bibliothek, die dasselbe Szenario einmal gegen die Live-Plattform und einmal gegen die lokale Entwicklungsumgebung fährt. Ein Szenario — etwa: die aktive Organisation wechseln, einen Dienst anlegen, bearbeiten und wieder löschen, oder ein Support-Ticket erstellen und beantworten — wird ein einziges Mal beschrieben und in beiden Welten ausgeführt. Das schließt eine ganze Fehlerklasse aus: das leise Auseinanderdriften von „so testen wir lokal" und „so verhält es sich in echt".
Dazu kam ein sichtbares Stück Verwaltung: Aktionen für den Lebenszyklus einer Mitgliedschaft, direkt in den Mitglieder-Listen. Ein Administrator kann ein Mitglied sperren, wieder aktivieren, entfernen oder seine Rolle ändern — über alle Branchen hinweg, vom Gym bis zum Verein, jede dieser Aktionen mit einem Audit-Eintrag hinterlegt und mit Komponententests abgedeckt. Dazu einheitliche Löschen-Schaltflächen mit Rückfrage in den Konfigurationsansichten. Nichts davon ist spektakulär. Aber es sind die Handgriffe, die eine Plattform im Alltag tragbar machen — und sie automatisiert abzudecken hieß, sie einmal wirklich von Anfang bis Ende durchzuspielen.
Drei echte Bugs, die das Netz fing
Der Lohn dieser Mühe kam, wie so oft, in Form von roten Ergebnissen, die berechtigt waren. Der erste Fehler war der unangenehmste. In einem Pfad der Web-App wurde ein Zeitüberschreitungs-Schutz aus der Resilienz-Schicht ausgelöst — der Mechanismus, der eine hängende Abhängigkeit nach einer Frist abbricht, statt ewig zu warten. So weit richtig. Nur lief die Behandlung in einem async void-Kontext: einem Feuer-und-vergiss-Ablauf, dessen geworfene Ausnahme niemand mehr auffängt. Aus der ordnungsgemäßen Zeitüberschreitung wurde so eine unbeobachtete Ausnahme, und die riss nicht nur die eine Anfrage nieder, sondern den gesamten Web-Host. Ein Schutzmechanismus, der die App am Leben halten soll, brachte sie in dieser Konstellation zum Absturz — die tückischste Sorte Fehler, weil sie aus einer an sich korrekten Sicherung entsteht. Die Korrektur fängt die Zeitüberschreitung jetzt dort ab, wo sie entsteht, und lässt den Host stehen.
Der zweite Fehler war leiser, aber ähnlich lehrreich. Beim Hochfahren der Entwicklungsumgebung wurden Test-Nutzer aus den Fixtures gegen Auth0, den Identitätsanbieter, abgeglichen — und dieser Abgleich schrieb bei jedem Start zurück, auch wenn sich nichts geändert hatte. Aus vielen solchen Rückschreibungen wurde ein Sturm: genug Schreibzugriffe gegen die Auth0-Verwaltungs-API, um die Entwicklungs-API auszubremsen. Der Fix unterbindet die überflüssigen Rückschreibungen für diese Fixture-Nutzer — sie werden nur noch dann angefasst, wenn es wirklich etwas zu ändern gibt.
Der dritte Fehler kam ausgerechnet aus dem Szenario, das eben noch als sauberes Beispiel diente: ein Support-Ticket erstellen und beantworten. Der Assistent, mit dem ein Administrator im Namen eines Mitglieds ein Ticket anlegt, ordnete es stillschweigend der falschen Kategorie zu — der Plattform-Ebene statt der Organisation. Die Folge war so unauffällig wie ärgerlich: Das Ticket wurde zwar erstellt, landete aber nie in der Warteschlange des Gym-Teams, das es hätte bearbeiten sollen. Kein Fehler, kein roter Bildschirm — nur eine Anfrage, die ins Leere lief. Erst weil das lokale Gegenstück des Szenarios den ganzen Weg vom Anlegen bis zur Zustellung durchspielte, fiel die Lücke auf; der Fix legt das Ticket nun in der richtigen, organisationsbezogenen Kategorie ab.
Alle drei Fehler eint dieselbe Moral: Sie tauchten nicht im Betrieb auf, wo sie teuer gewesen wären, sondern beim bewussten Durchspielen der Pfade, auf denen sie lauerten. Ein Testnetz beweist seinen Wert nicht dadurch, dass es grün leuchtet, sondern dadurch, dass es an der richtigen Stelle reißt.
Was kommt als Nächstes?
Der Blog hat jetzt ein Zuhause, und die Testabdeckung reicht ein Stück weiter in die Ecken, in denen sich Fehler gern verstecken. Beides ist die Art Fundamentarbeit, die man nicht vorzeigen kann und trotzdem jeden Tag spürt. Was als Nächstes kommt, weiß ich noch nicht genau — aber der Dev-Log hat jetzt seinen eigenen Ort, um es zu erzählen, sobald es so weit ist.
Kommentare
Noch keine Kommentare. Teilen Sie als Erste(r) Ihre Gedanken!
Kommentare sind für diesen Beitrag geschlossen.