Architektur: Domain-Driven Design — Analyse (nur Bewertung, keine Umgestaltung) #136
Labels
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: tobias/gwoe-antragspruefer#136
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Kontext
Das Repository ist organisch gewachsen (Solo-Entwicklung, ~1 Jahr, derzeit
ca. 22 Module in
webapp/app/, technisch geschichtet nach Funktionalität:models.py,database.py,analyzer.py,parlamente.py, …). Das ist eineTechnical Layering-Architektur — sauber genug für den aktuellen Scope,
aber wenn das Projekt reift (mehr Bundesländer, Abstimmungsverhalten,
Trendanalysen, externe Konsumenten der API) droht der klassische
Big-Ball-of-Mud-Drift.
Dieser Issue fragt: Wie müsste das Repo aussehen, wenn wir konsequent
Domain-Driven Design anwenden würden? — reine Analyse, keine
Umgestaltung. Ergebnis ist ein Dokument mit Zielbild, Trade-off-Bewertung
und Empfehlung, ob/in-welchem-Umfang die Migration sich lohnt.
Explizit kein Code-Change in diesem Issue. Wenn die Analyse zu einer
Empfehlung "umsetzen" kommt, werden konkrete Umsetzungs-Issues separat
angelegt.
Aufgabe
Als DDD-Experte das Repo durchsehen und als Markdown-Deliverable in
webapp/docs/analysen/ddd-bewertung.md(neue Datei) ablegen:1. Ist-Zustand dokumentieren
app/*.py× Verantwortung × erkennbare Verletzungen(anämisches Modell, Infrastruktur-Leaks, fehlende Abstraktion)
analyzer.py:231mischtDomain-Logik, Infrastruktur-Call und Retry-Loop)
2. Bounded Contexts identifizieren
Minimum zu benennen und zu charakterisieren (Core / Supporting / Generic):
Analyse prüft diese Liste, ergänzt/verwirft/kombiniert, begründet.
3. Zielbild skizzieren (nur als Referenz, nicht umsetzen)
analyzer.py→
antragsbewertung/application/bewerte_antrag.py). Nur zur Illustration,keine Implementation
4. Ubiquitous Language
Glossar-Skelett mit den 20-30 wichtigsten Begriffen, Deutsch als
Domänen-Sprache (weil Domäne = deutsche Landtage), Englisch nur für
Infrastruktur-Konzepte.
5. Trade-off-Bewertung — ehrlich
kognitive Einstiegshürde, geschätzte Nettozeit)
Nutzens bei 15% des Aufwands (z.B. Repository-Pattern + DTO-Trennung,
ohne Voll-Package-Split)?
1 Jahr / 3 Jahren / bei 3× so vielen Contexts?
6. Empfehlung
Drei Optionen bewertet und priorisiert:
Strukturzwang)
Mit klarer Empfehlung welche Option unter Berücksichtigung von Projekt-
Charakter (Solo, Beta-artig, hoher Erkenntnisgewinn-pro-Feature) passt.
Deliverables
webapp/docs/analysen/ddd-bewertung.md— Volltext-Analyse nachobigem Raster
webapp/docs/glossar.md(Entwurfsstatus)Empfehlung + 3 Begründungs-Bullets)
Akzeptanzkriterien
nicht nur "DDD ist immer besser"
separat, wenn die Empfehlung das hergibt
Nicht-Ziele
Bezug
probeweise dort testen will)
Ports-and-Adapters
Architektur: Domain-Driven Design konsequent anwenden — Analyse + Migrationspfadto Architektur: Domain-Driven Design — Analyse (nur Bewertung, keine Umgestaltung)Executive Summary
Empfehlung: Option B — Lightweight DDD (ca. 5-8 Tage, ohne Package-Split).
Volltext in
webapp/docs/analysen/ddd-bewertung.md.Begründung in 3 Bullets
Voll-DDD rechnet sich nicht für Solo-Projekt — die Domäne ist mittel-komplex, aber Change-Rate hoch (tgl. Features). 4-8 Wochen Big-Refactor würden den Feature-Flow abwürgen, während der Hauptgewinn (Team-Parallelisierbarkeit) hier nicht zutrifft.
Drei gezielte Lightweight-Eingriffe lösen die akuten Schmerzpunkte:
AntragRepository/BewertungRepository→ DB-Leak aus 5 Call-Sites (database.pyhat heute 0% Coverage bei 299 LOC)LlmBewerter-Port →AsyncOpenAIraus ausanalyzer.py:234-237, machtconftest.py-Stubbing obsoletanalyzer.py:167-171) gehören als Methoden aufBewertung/MatrixFeldStatus-quo ist auch nicht kostenlos — Coverage-Zahlen aus #134 (database.py 0%, mail.py 0%, main.py 1%) zeigen, dass die Flachstruktur den Test-Unterbau ausdünnt. Lightweight-DDD adressiert das ohne Big-Bang.
Nicht-Empfehlungen
Reihenfolge (wenn Option B gewählt)
LlmBewerter-Port (2-3 Tage) — Test-HygieneGreenfield-Test
#135 Monitoring hat noch keinen Code — guter Kandidat, die neue Struktur probeweise direkt richtig zu bauen, ohne Migrationsrisiko.
Analyse durch Claude Code autonom 2026-04-20. Kein Code-Change, wie vom Issue-Scope gefordert.
Analyse vertieft — fertig (Opus 4.7)
webapp/docs/analysen/ddd-bewertung.md: 439 → 1237 Zeilen (≈2.8×), kein Code angefasst.Neue/ausgebaute Kapitel
parlamente.py3397 statt vorher 1425 geschätzt,main.py1746 statt 680,analyzer.py379 statt 138. Das Projekt ist deutlich größer als die alte Analyse angenommen hatte (11789 LOC gesamt).Bewertung,MatrixFeld, Ports, Application-Service); Repository-Vorlage mit SQLite+In-Memory-Fake; DI-Urteil pro FastAPIDependsKernerkenntnis
Die Invarianten aus
analyzer.py:168-171und:101-106sind heute schon testbarer Python-Code, der nur im LLM-System-Prompt lebt. Das LLM kann sie jederzeit still verletzen — strukturelles Risiko, das DDD-Lightweight (Option B) durch Domain-Verhalten aufBewertung/MatrixFelddirekt adressieren würde.Empfehlung unverändert
Option B (Lightweight, 5-8 Tage) — Vertiefung stützt die Empfehlung, kippt sie nicht. Tages-Roadmap (Kap. 10) ist jetzt ausdruckbar und abarbeitbar.
Willst du die Umsetzung starten, eigenes Issue #139 mit der Roadmap aus Kap. 10?
DDD-Lightweight-Migration — Tag 1-4 abgeschlossen
Umgesetzt
Neu
app/repositories/{antrag,bewertung,abonnement}_repository.py— Protocol + Sqlite-Impl + InMemory-Fakeapp/ports/llm_bewerter.py—LlmBewerter-Protocol +LlmRequest-Dataclassapp/adapters/qwen_bewerter.py— QwenBewerter, kapselt AsyncOpenAI + Retry-Loop + Markdown-Strippingdocs/adr/0008-ddd-lightweight-migration.mdGeändert
app/analyzer.py::analyze_antragnimmt optionalbewerter-Parameter; AsyncOpenAI-Import raus; nutzt Portapp/models.py— 5 neue Domain-Methoden:Assessment.ist_ablehnung(),ist_uneingeschraenkt_unterstuetzend(),hat_fundamental_kritisches_feld(),verletzt_score_cap();MatrixEntry.ist_fundamental_kritisch(),to_symbol()analyze_antragloggtWARNINGwenn LLM-Output die Score-Cap-Invariante verletzt (Score ≤ 3 bei Feld ≤ -4)Tests
+105 neue Tests (105 = 21 AntragRepo + 5 BewertungRepo + 11 AbonnementRepo + 13 LlmBewerter + 26 Domain-Behavior + 29 via parametrize). Gesamt: 574 passed, 0 Regressions.
Deploy-Risiko
Niedrig bis mittel. Kein API-Breaking-Change. Einziger Laufzeit-Unterschied: Retry-Logs gehen jetzt unter
app.adapters.qwen_bewerterstattapp.analyzer— wer log-filter setzt, muss das nachpflegen.Nicht-Migriert (Folge-PR)
main.py— 21 direktedatabase.*-Calls aufDepends(get_antrag_repository)umstellen. Rein mechanisch, aber wegen 1746 LOC in einer Datei mit Konflikt-Potenzial bewusst ausgelagertmail.py,monitoring.py)conftest.py::_stub("openai")entfernen — geht, sobald alle Caller nur noch lazy importierenDie Fundament-Arbeit ist da. Weitere Migration kann inkrementell im Folge-PR erfolgen.
Tag 1-4 in Commits
8f0f6d6+20b33c7(Release 1.0) gelandet. Callsite-Migration in main.py als Folge-PR. ADR 0008 schreibt das Vorgehen fest. Schliesse als 1.0-Done.