GET /documents/{id}/changes haelt die Anfrage offen und antwortet, sobald
sich etwas tut — kumuliertes Diff seit der bekannten Version, dazu die
Ereignisse mit ihrem Absender. Ist die Basis verdichtet oder hat der Client
noch gar nichts, kommt der Volltext statt der Operationen: ein Roundtrip und
ein Sonderzustand weniger als ein eigener Fehlerpfad.
Der Feed arbeitet auf der Historie, nicht am Dokument — ein geloeschtes
Dokument muss sein DELETED noch zustellen koennen.
Blockierend auf virtuellen Threads statt DeferredResult (D76-Nachtrag 5):
So behaelt der Endpunkt die aus der Spezifikation generierte Signatur, und
API-First bleibt fuer ihn unangetastet; ein Wartender kostet trotzdem fast
nichts. Geweckt wird nach dem Commit, nie davor, und ueber einen Stempel, den
der Aufrufer VOR dem Nachsehen liest — sonst ginge ein Signal aus der Luecke
dazwischen verloren.
122 Tests. Das Szenario "ein Wartender wird geweckt" misst die Dauer: Ohne
das bestuende es auch dann, wenn der Wartende bloss in den Timeout liefe und
danach die Aenderung vorfaende. Gegenprobe: Benachrichtigung entfernt ->
genau dieses Szenario faellt; Volltext-Rueckfall entfernt -> genau jenes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3.0 KiB
Werkbaum · Backend
Kotlin/Spring-Boot-Anwendung. Aufgaben: Persistenz der Notationstexte,
Live-Editing (D76), Taiga-Integration (REST-API, #ref-Auflösung,
Status-Sync), später Tenzu-Adapter.
Stand: Gerüst steht — Dokumenten-CRUD mit Historie und Wiederherstellung,
API-First aus src/main/resources/openapi/api.yaml, H2 mit Liquibase.
Kommandos in README.md hier. Live-Editing (D76,
docs/live-editing-proposal.md) ist in Arbeit: Schritte 1–4 der Reihenfolge
dort sind gebaut (Zeilen-Diff in de.werkbaum.diff, Historie in zwei Ebenen,
PATCH /content und der Änderungsfeed im LiveEditingService); offen sind
PATCH /title, das Master-Passwort und der Client.
Konventionen
- Kotlin, Spring Boot 4, Gradle (Kotlin DSL), JDK 21.
- Paketwurzel
de.werkbaum; Schichten:api(Controller),domain,service,repository(Interfaces),persistence(JPA), späterintegration.taiga(Client, Mapping). - API First: Interfaces und Modelle werden aus der OpenAPI-Spezifikation generiert; der Controller implementiert sie. Ändert sich die Spec, schlägt der Compile fehl — genau so ist es gewollt.
- Tests mit JUnit 5 als Runner + Kotest-Assertions (
shouldBe,shouldContain,shouldThrow) und MockK; Verhalten per Cucumber gegen die laufende Anwendung (RestTestClient, nicht TestRestTemplate — das ist in Boot 4 Auslaufmodell). Taiga-Client gegen aufgezeichnete Antworten (WireMock), nie gegen Live-Instanzen. - Konfiguration über
application.yaml+ Umgebungsvariablen; keine Zugangsdaten im Repository. - Keine neuen Laufzeit-Abhängigkeiten ohne Rückfrage (Wurzel-CLAUDE.md); Test-Abhängigkeiten sind unkritisch, sie landen in keinem Artefakt.
Spring Boot 4 — drei Fallen (D13-Nachtrag)
Vieles ist aus dem Kern in eigene Module gewandert. Was uns getroffen hat:
spring-boot-starter-testbringt keinTestRestTemplate/RestTestClientmit — dafürspring-boot-resttestclient.@SpringBootTeststellt die Test-Client-Bean nicht mehr von selbst bereit:@AutoConfigureRestTestClientgehört an die Testkonfiguration.org.liquibase:liquibase-coreallein bringt die Autokonfiguration nicht mehr mit; ohnespring-boot-starter-liquibaseläuft keine Migration, und der Fehler zeigt sich erst spät als „Schema validation: missing table".
Wichtig (D14 — Parser-Hoheit)
Das Backend parst die Notation nicht. Es speichert den Text als Ganzes und arbeitet mit expliziten Metadaten. Sollte Backend-Parsen doch nötig werden: zuerst DECISIONS ergänzen, dann gegen die gemeinsamen Fixtures aus docs/SPEC.md §10 testen — niemals eine zweite, abweichende Grammatik pflegen.
Taiga-Mapping (Vorgabe aus docs/ROADMAP.md)
#123referenziert Epic/User Story/Task/Issue; Auflösung liefert Titel, URL, Status. Status-Mapping Taiga-Workflow → Notation konfigurierbar (Default: „New"→[ ], „In progress"→[~], „Ready for test"→[/], „Done"→[x], „Archived"→[^]).