Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions assistant_app/knowledge/arbeitsweise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
titel: Arbeitsweise
quelle: Arbeitsweise
---

## Wie Benjamin eine neue Aufgabe angeht

Benjamin zerlegt Aufgaben, bevor er anfängt. Zuerst sammelt er, welche großen
Aufgaben es überhaupt gibt. Diese zerlegt er in Teilaufgaben, und diese
wiederum, bis einzelne To-dos übrig bleiben, die klein genug sind, dass eine
Person sie abschließen kann.

Das Verfahren stammt aus der Teamarbeit an Join und hat sich für ihn auch
allein bewährt. Der Nutzen liegt weniger in der Liste als in dem, was beim
Zerlegen auffällt: Eine Aufgabe, die sich nicht sauber zerlegen lässt, ist
meistens noch nicht verstanden.

Beim Umsetzen gilt dieselbe Reihenfolge wie beim Zerlegen — erst verstehen,
dann Lösungswege erarbeiten, dann sauber umsetzen. Der mittlere Schritt ist ihm
wichtig, weil die erste Lösung, die einem einfällt, selten die beste ist.

## Wie Benjamin im Team arbeitet

An Join hat Benjamin in einem Team aus vier Personen gearbeitet, koordiniert
über Trello. Aufgaben wurden gemeinsam zerlegt, dann hat sich jeder eines der
entstandenen To-dos genommen und es abgearbeitet.

Der Sinn dahinter ist, dass niemand dem anderen in die Quere kommt: keine
doppelte Arbeit, keine Blockaden, und der Stand ist für alle jederzeit sichtbar.

Benjamins Erfahrung aus dem Projekt ist deutlich: Die Koordination war
schwieriger als der Code. Das ist keine Klage, sondern eine Einschätzung, die
jeder teilt, der einmal zu viert an einer Anwendung gearbeitet hat. Die
technischen Probleme lassen sich nachlesen, die organisatorischen nicht.

## Was Benjamin an Code wichtig ist

Qualität geht vor Schnelligkeit. Lieber ein paar Minuten länger für eine
saubere Lösung als ein verfrühtes "ist fertig", das später jemand
auseinandernehmen muss.

Bei Fehlern sucht Benjamin die Ursache, statt das Symptom zu überdecken. Ein
Fehler, der nur kaschiert wurde, kommt an anderer Stelle wieder, dann aber ohne
erkennbaren Zusammenhang.

Wichtig sind ihm außerdem Lesbarkeit, Performance und die Bedienbarkeit für den
Anwender. Code kommentiert er sparsam: Was der Code tut, soll er selbst zeigen.
Kommentare hebt er sich für Entscheidungen auf, die man nicht ansieht — etwa
warum ein unsichtbares Formularfeld außerhalb des Sichtbereichs positioniert ist
statt mit display:none, weil manche automatisierten Absender ausgeblendete
Felder erkennen.

## Wie Benjamin mit Git arbeitet

Benjamin arbeitet nicht direkt auf dem Hauptzweig. Jede Änderung entsteht in
einem eigenen Feature-Branch und kommt über einen Pull Request in den
Hauptzweig — auch dann, wenn er allein am Projekt arbeitet und den Pull Request
selbst zusammenführt. Seine Commit-Nachrichten folgen den Conventional Commits
und sind auf Englisch.

Diese Arbeitsweise hat er sich bewusst angewöhnt, und zwar seit er mit CI/CD
arbeitet. Vorher hat er direkt auf dem Hauptzweig gearbeitet.

Zwei Gründe nennt er dafür. Der erste ist Übung: Er arbeitet meistens allein,
ist aber der Meinung, dass er das können muss — im Team führt an diesem Ablauf
nichts vorbei, und ihn erst dann zu lernen, wenn andere davon abhängen, ist der
schlechtere Zeitpunkt.

Der zweite ist Schutz vor eigenen Fehlern. In seinen Projekten laufen bei jedem
Pull Request die vollständigen Prüfungen durch: Codeformatierung, statische
Analyse, Tests, Build. Ausgerollt wird aber nur, was im Hauptzweig landet. Ein
direkter Push auf den Hauptzweig würde also ungeprüft live gehen. Der Umweg
über den Pull Request sorgt dafür, dass zwischen "geschrieben" und
"ausgeliefert" immer eine automatische Kontrolle liegt.

## Welche Projekte nach Vorgabe entstanden sind und welche frei

Diese Einordnung nimmt Benjamin von sich aus vor, weil sie für die Bewertung
seiner Projekte wichtig ist.

Bei den Projekten aus der Developer Akademie — Join, El Pollo Loco, Pokédex,
Coderr, Videoflix — waren Stack und Technologien vorgegeben. Die Aufgabe bestand
nicht darin, die Werkzeuge auszuwählen, sondern damit ein funktionierendes und
sauber gebautes Ergebnis zu liefern.

Cardelia ist die Ausnahme: Es entsteht ohne Vorgaben und außerhalb der Akademie.
Stack, Architektur und Zuschnitt hat Benjamin dort selbst entschieden und
verantwortet sie entsprechend auch selbst.

Dass ein Stack vorgegeben war, heißt dabei nicht, dass er nichts dazu sagen
kann. Bei Videoflix kann er begründen, warum dort RQ und nicht Celery passt und
warum die Tokens in HttpOnly-Cookies liegen und nicht im LocalStorage — bei
Cardelia hat er beides andersherum entschieden, weil das Projekt anders
zugeschnitten ist.
76 changes: 76 additions & 0 deletions assistant_app/knowledge/in-arbeit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
titel: Woran Benjamin gerade arbeitet
quelle: Aktuelle Arbeit
---

## Videoflix, eine Streaming-Plattform mit Hintergrundverarbeitung

Videoflix ist eine Videoplattform, an der Benjamin aktuell arbeitet. Das
Backend steht, das eigene Frontend ist noch im Aufbau, und öffentlich
erreichbar ist das Projekt bisher nicht.

Technisch ist es sein anspruchsvollstes Backend. Nach dem Hochladen eines
Videos läuft die Konvertierung mit FFmpeg zu HLS in drei Auflösungen, 480p,
720p und 1080p, dazu wird ein Vorschaubild herausgeschnitten. Das passiert
nicht während der Anfrage, sondern als Hintergrundjob — ein Video zu
konvertieren dauert Minuten, und solange darf niemand vor einer wartenden Seite
sitzen.

Die Jobs laufen über zwei Warteschlangen mit unterschiedlicher Priorität: eine
schnelle für E-Mails wie Kontoaktivierung und Passwort-Zurücksetzen, eine
langsame für die Videokonvertierung. Der Grund ist einfach: Eine
Aktivierungsmail, die hinter einer halbstündigen Videokonvertierung in der
Schlange steht, kommt zu spät. Abgearbeitet wird von zwei Arbeitsprozessen in
eigenen Docker-Containern, mit Redis als Unterbau.

## Warum Videoflix RQ verwendet und nicht Celery

Benjamin hat in seinen Projekten beide Werkzeuge eingesetzt: Celery bei
Cardelia, RQ bei Videoflix. Die Entscheidung fiel jeweils nach Projektgröße.

Für Videoflix reicht eine einfache Warteschlange auf Redis-Basis. Celery
entfaltet seine Stärken erst mit einem zusätzlichen Vermittler wie RabbitMQ,
und damit auch dessen Betriebsaufwand. RQ ist leichtgewichtiger, schneller
aufgesetzt und bringt über django-rq eine Oberfläche mit, auf der sich Jobs,
Fehler und vollständige Fehlerausgaben ansehen lassen.

Bei zwei Warteschlangen und im Kern einem einzigen Job-Typ war das der
passendere Zuschnitt. Die Frage ist für Benjamin nicht, welches Werkzeug
mächtiger ist, sondern welches zur Größe der Aufgabe passt.

## Warum Videoflix JWT verwendet und nicht Token-Authentifizierung

Videoflix nutzt Simple-JWT mit Access- und Refresh-Token, beide als
HttpOnly-Cookies statt im LocalStorage abgelegt.

Der Unterschied ist sicherheitsrelevant: Ein Token im LocalStorage ist für
JavaScript lesbar. Gelingt irgendwo auf der Seite eine Cross-Site-Scripting-
Lücke, ist der Token mit abgeräumt. Ein HttpOnly-Cookie kommt gar nicht erst in
JavaScript-Reichweite. Nebenbei muss das Frontend den Token dann auch nicht
selbst verwalten.

Dazu kommt die Ablaufsteuerung: Der Access-Token gilt 30 Minuten, der
Refresh-Token 7 Tage, und der Access-Token lässt sich über einen eigenen
Endpunkt sauber erneuern. Die klassische Token-Authentifizierung des Django
REST Frameworks, wie Benjamin sie bei Coderr verwendet, kennt beides nicht: Der
Token ist dort unbefristet gültig und hat keine eingebaute Erneuerung.

## Der Assistent auf dieser Seite

Das zweite laufende Vorhaben ist der Assistent, mit dem gerade gesprochen wird.
Er beantwortet Fragen zu Benjamin und seinen Projekten aus einer gepflegten
Wissensbasis, statt sich Antworten auszudenken. Das Verfahren dahinter heißt
Retrieval-Augmented Generation: Zu jeder Frage werden zuerst die passenden
Abschnitte aus der Wissensbasis gesucht, und nur diese Abschnitte bekommt das
Sprachmodell als Grundlage.

Findet die Suche nichts Passendes, sagt der Assistent das — und das
Sprachmodell wird dann gar nicht erst gefragt.

Davor sitzt zusätzlich ein Filter, der Versuche erkennt, den Assistenten aus
seiner Rolle zu holen oder ihm fremde Anweisungen unterzuschieben. Solche
Anfragen werden abgewiesen, bevor sie Rechenzeit kosten.

Benjamin hat den Assistenten gebaut, weil er RAG nicht nur in der Theorie
verstehen wollte, sondern an einem System, das öffentlich läuft und auf das
echte Besucher losgelassen werden.
80 changes: 80 additions & 0 deletions assistant_app/knowledge/infrastruktur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
titel: Server und Betrieb
quelle: Infrastruktur
---

## Benjamin betreibt seine Projekte auf einem eigenen Server

Benjamins Projekte laufen nicht bei einem Hosting-Baukasten, sondern auf einem
eigenen virtuellen Server unter Ubuntu, den er selbst aufgesetzt hat und selbst
administriert. Das schließt alles ein, was dazugehört: Betriebssystem
einrichten, Dienste installieren und absichern, Zertifikate ausstellen, Updates
einspielen, Fehler im laufenden Betrieb finden.

Das ist der Unterschied zwischen "ich habe eine Anwendung geschrieben" und "ich
betreibe eine Anwendung". Auf demselben Server laufen mehrere seiner Projekte
nebeneinander, jedes unter einer eigenen Adresse.

## Wie die Anwendungen ausgeliefert werden

Vor den Anwendungen steht Nginx als Reverse Proxy. Er nimmt alle Anfragen
entgegen, liefert statische Dateien direkt aus und reicht alles andere an die
Anwendung dahinter weiter. Die Django-Anwendung selbst läuft unter Gunicorn mit
mehreren Arbeitsprozessen, angebunden über einen lokalen Socket statt über
einen offenen Port.

Die Verschlüsselung läuft über Zertifikate von Let's Encrypt, die sich
automatisch erneuern. HSTS ist aktiv, der Zugriff erfolgt ausschließlich über
HTTPS.

Als Datenbank kommt PostgreSQL zum Einsatz, erreichbar nur vom Server selbst
und nicht aus dem Netz.

## Absicherung des Servers

Die Firewall lässt nur die Ports offen, die tatsächlich gebraucht werden. Ein
Dienst zur Erkennung wiederholter Fehlanmeldungen sperrt auffällige Adressen
automatisch. Zugangsdaten und Schlüssel liegen in einer Konfigurationsdatei mit
eng gesetzten Dateirechten und stehen nicht im Quellcode.

Öffentliche Formulare sind zusätzlich abgesichert: Eine Begrenzung der Anfragen
pro Absender verhindert massenhaftes Absenden, und ein für Menschen unsichtbares
Feld erkennt automatisierte Einsendungen.

## Datensicherung

Die Datenbank wird jede Nacht automatisch gesichert, zusätzlich vor jedem
Ausrollen einer neuen Version. Die Sicherungen werden über einen festen
Zeitraum aufbewahrt und ältere automatisch gelöscht.

Benjamin prüft die Sicherungen auch auf Wiederherstellbarkeit. Eine Sicherung,
die noch nie zurückgespielt wurde, ist keine Sicherung, sondern eine Annahme.

## Wie neue Versionen live gehen

Das Ausrollen passiert nicht von Hand, sondern über eine Pipeline in GitHub
Actions. Bei jeder Änderung laufen zuerst die Prüfungen: Codeformatierung,
statische Analyse, automatische Tests, ein vollständiger Build. Erst wenn alles
davon durchläuft und die Änderung im Hauptzweig landet, wird ausgerollt.

Ein Pull Request durchläuft dieselben Prüfungen, rollt aber nie aus. Dadurch ist
der Moment, in dem etwas live geht, eine bewusste Entscheidung und kein
Nebeneffekt.

Nach dem Ausrollen prüft die Pipeline selbst, ob die Seite erreichbar ist und
ob die ausgelieferten Dateien den richtigen Inhaltstyp haben. Ein fehlerhaftes
Ausrollen fällt dadurch sofort auf, statt unbemerkt zu bleiben.

## Was Benjamin dabei gelernt hat

Die Fehler, die im Betrieb auftreten, sind andere als die beim Entwickeln. Ein
Beispiel: Nach dem Hochladen von Dateien kamen Unterordner mit zu engen Rechten
auf dem Server an. Der Webserver durfte sie nicht betreten und lieferte
daraufhin für Bilder und Sprachdateien die Startseite aus — mit dem Statuscode
200 und ohne jede Fehlermeldung. Die Seite sah funktionierend aus und war es
nicht.

Aus solchen Fällen hat er sich angewöhnt, nach jedem Ausrollen nicht nur zu
schauen, ob eine Seite lädt, sondern zu prüfen, ob auch das Richtige
ausgeliefert wird. Genau diese Prüfung läuft heute automatisch in der Pipeline
mit.
67 changes: 67 additions & 0 deletions assistant_app/knowledge/projekt-cardelia.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
titel: Cardelia
quelle: Projekt Cardelia
---

## Was Cardelia ist

Cardelia ist ein Kartenkatalog mit Sammlungsverwaltung für Pokémon-Karten. Der
Bestand umfasst rund 77.000 Karten in vier Sprachen, dazu Preise aus mehreren
Quellen. Die Anwendung läuft, ist aber noch nicht öffentlich zugänglich.

Cardelia ist Benjamins ambitioniertestes Projekt und das einzige, das er ohne
Vorgaben und außerhalb der Developer Akademie baut. Stack, Architektur und
Umfang hat er vollständig selbst entschieden.

Unter benjaminblarr.de/cardelia ist die Laufzeit-Architektur als Schaubild
einsehbar: welche Bestandteile es gibt, woher die Daten kommen und wohin sie
gehen.

## Die Architektur von Cardelia

Cardelia trennt zwei Ebenen, die oft verwechselt werden.

**Django ist das Backend.** Dort liegen die Domänenlogik, die API und die
Hintergrundjobs — also alles, was Cardelia inhaltlich ausmacht.

**Supabase ist Infrastruktur, kein Backend.** Es liefert PostgreSQL,
Authentifizierung und Objektspeicher als gemanagten Dienst. Diese Bestandteile
müsste Benjamin sonst selbst betreiben.

Die beiden konkurrieren also nicht miteinander, sondern liegen auf
verschiedenen Ebenen. Das ist eine Unterscheidung, die Benjamin ausdrücklich
macht: Wer Supabase als Backend bezeichnet, meint meistens den Fall, in dem es
gar kein eigenes Backend gibt und die Oberfläche direkt auf die Datenbank
zugreift. Bei Cardelia ist das nicht so.

Für die Hintergrundverarbeitung kommen Celery mit einem Worker und Celery Beat
als Zeitplaner zum Einsatz, mit Redis als Vermittler. Zusätzlich läuft eine
tägliche Sicherung.

## Woher die Daten in Cardelia kommen

Cardelia zieht seine Daten aus acht externen Quellen: TCGdex, Cardmarket,
PokeTrace, CardTrader, Limitless, PokeWallet, pokemontcg.io und tcggo.

Einmal täglich läuft ein Lauf, der die Preise aktualisiert und prüft, ob neue
Kartensets erschienen sind. Dieser Lauf ist der Kern des Systems: Ein Katalog
mit 77.000 Karten in vier Sprachen ist keine Datenmenge, die man von Hand
pflegt, und Preise, die eine Woche alt sind, sind für eine Sammlungsverwaltung
wertlos.

Mehrere Quellen für dasselbe bedeuten dabei nicht mehr Sicherheit, sondern mehr
Arbeit: Die Quellen widersprechen sich, benennen Dinge unterschiedlich und
fallen unterschiedlich oft aus.

## Warum Cardelia als Projekt zählt

Bei den Ausbildungsprojekten war der Stack vorgegeben. Cardelia ist die
Ausnahme: Hier hat Benjamin selbst entschieden, was er einsetzt und wie er es
zuschneidet — und muss diese Entscheidungen entsprechend auch selbst
verantworten.

Es ist außerdem das Projekt, das am längsten läuft und dadurch Fragen aufwirft,
die in einem abgeschlossenen Übungsprojekt nie auftauchen: Was passiert, wenn
eine Datenquelle ihr Format ändert? Wie merkt man überhaupt, dass ein
nächtlicher Lauf nur halb durchgelaufen ist? Was tut man mit Daten, die
widersprüchlich sind?
72 changes: 72 additions & 0 deletions assistant_app/knowledge/projekt-coderr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
---
titel: Coderr
quelle: Projekt Coderr
---

## Was Coderr ist

Coderr ist eine REST-API, die Benjamin mit dem Django REST Framework gebaut
hat. Sie bildet eine Plattform ab, auf der Dienstleistungen angeboten, bestellt
und bewertet werden: Angebote, Bestellungen und Bewertungen, dazu Registrierung
und Anmeldung mit Token-Authentifizierung.

Das Frontend stammt von der Developer Akademie und war vorgegeben. Benjamins
Arbeit ist das Backend dahinter, das die vorgegebene Schnittstelle exakt
bedienen muss.

Coderr läuft unter coderr.benjaminblarr.de auf Benjamins eigenem Server, der
Quelltext liegt öffentlich auf GitHub.

## Wie Coderr aufgebaut ist

Coderr ist in sechs Django-Apps aufgeteilt, jede mit einem eigenen Bereich für
die Schnittstelle: Serializer, Views, URLs und Berechtigungen liegen getrennt
voneinander. Der Zuschnitt folgt der Fachlichkeit — Anmeldung, Angebote,
Bestellungen, Bewertungen — statt alles in eine große Anwendung zu legen.

Die API ist über drf-spectacular dokumentiert und lässt sich als Swagger-
Oberfläche aufrufen.

Die Standardeinstellung für Berechtigungen ist bewusst geschlossen: Jeder
Endpunkt verlangt zunächst eine Anmeldung, und öffentliche Endpunkte müssen das
ausdrücklich erlauben. Der umgekehrte Weg — alles offen, einzelne Endpunkte
abgesichert — verzeiht keinen Fehler, weil ein vergessener Endpunkt dann offen
steht statt zu.

## Das Kontaktformular des Portfolios läuft über Coderr

Das Kontaktformular auf benjaminblarr.de schickt seine Nachrichten an dieselbe
Django-Instanz. An diesem kleinen Endpunkt hängen mehrere Entscheidungen, die
Benjamin gern erklärt, weil sie zeigen, woran man bei einem öffentlichen
Formular denken muss.

Ein für Menschen unsichtbares Feld erkennt automatisierte Einsendungen. Wird es
ausgefüllt, antwortet der Server trotzdem mit einer Erfolgsmeldung und verwirft
die Nachricht still. Eine ehrliche Fehlermeldung würde dem Absender verraten,
dass die Falle erkannt wurde, und ihm beim nächsten Versuch helfen.

Die Begrenzung der Anfragen pro Absender richtet sich nach der IP-Adresse, die
der Webserver einträgt, und nicht nach der, die der Absender selbst behaupten
kann. Der Unterschied entscheidet darüber, ob die Begrenzung überhaupt wirkt.

Die Nachricht wird zuerst gespeichert und erst danach als E-Mail verschickt. Ist
der Mailversand gestört, ist die Nachricht trotzdem da. Als Absender steht dabei
Benjamins eigene Adresse, die Adresse des Besuchers steht in der
Antwort-an-Angabe — eine fremde Adresse als Absender zu setzen, lässt Mails in
Spamfiltern hängen.

## Warum der Zwischenspeicher in der Datenbank liegt

Eine Besonderheit an Coderr: Der Zwischenspeicher liegt nicht im
Arbeitsspeicher, sondern in der Datenbank. Das ist langsamer und trotzdem die
richtige Wahl.

Der Grund liegt in der Zählung der Anfragen pro Absender. Die Anwendung läuft
mit mehreren Arbeitsprozessen nebeneinander. Läge der Zähler im Arbeitsspeicher,
hätte jeder Prozess seinen eigenen — bei drei Prozessen dürfte derselbe Absender
das Dreifache des erlaubten Limits verschicken, weil keiner von den anderen
weiß. Erst ein gemeinsamer Speicher macht die Begrenzung zu einer echten
Begrenzung.

Das ist ein Beispiel für einen Fehler, den man im Betrieb nicht bemerkt: Es
funktioniert scheinbar, es zählt nur falsch.
Loading
Loading