Tech-Stack-Entscheidung
Status: Entwurf · Spec-Kandidat: nein (Entscheidungs-Vorlage)
Dieses Dokument bündelt Stack-Vorschläge, Entscheidungskriterien und ihre Bewertung pro Kandidat. Es wählt noch nichts aus — es bereitet die Wahl vor. Endgültige Entscheidungen werden später am Ende dieses Dokuments unter „Entscheidung” festgehalten.
Festgelegt
Frontend
- Next.js (App Router, latest) als Web-Framework.
- shadcn/ui (base-ui-Variante) als Komponentenbibliothek.
- Tailwind CSS 4 als Style-Layer.
- TypeScript überall.
Identitätsanbieter
- Authentik (self-hosted) als OIDC-Provider — gesetzt. Detail siehe Authentifizierung. Backend muss JWTs (RS256) gegen den Authentik-JWKS-Endpoint validieren; Frontend nutzt Auth.js / NextAuth gegen Authentik.
- State/Data: TanStack Query für Serverdaten, TanStack Form für Formulare, Zustand für lokalen UI-State (siehe Skill
hm-webapp-stack:create-hm-webappfür die opinionated Vorlage). - Auth: Auth.js / NextAuth gegen Authentik (OIDC-Discovery), Access-Token wird an die Backend-API durchgereicht.
- Validierung: Zod (Schema einmal, geteilt zwischen Form, API-Client und ggf. Backend wenn TS-Backend).
- Konvention zu shadcn
Select(base-ui):<SelectValue>rendert Raw-Value — Render-Function-Pattern verwenden, nichtplaceholder. Detail siehe globale CLAUDE.md. - CSS-Pflicht:
scrollbar-gutter: stableaufhtml(Layout-Shift verhindern).
Begründung: bewusste Vor-Entscheidung des Auftraggebers, deckt SSR/SSG/RSC, hat lebendiges Ökosystem, integriert sauber mit OIDC-Providern (Auth.js / NextAuth) und bringt produktionsreife Tooling-Defaults mit.
Datenbank
- PostgreSQL latest (siehe Schema und Konventionen). Begründet durch JSONB,
pgcrypto,tsvector, partial Indexes, Trigger-System, ACL/RBAC-View-Modell.
Persistenz-Pattern
- Transactional Outbox (siehe
AA_OUTBOX_EVENT). Bindet die Wahl Backend an einen Worker-fähigen Stack (Background-Job/Stream-Konsument muss in derselben Codebase oder als zweiter Service realisierbar sein).
Offen: Backend
Backend bedient die REST-API, schreibt Audit/Outbox/Versionen in derselben Transaktion und betreibt den Outbox-Dispatch-Worker.
Aktuelle Präferenz (nicht final): Java (latest, ≥ 21) + Spring Boot 4. Diese Frage wird vom Auftraggeber explizit zur Wahl gestellt — die Spec wirft sie nicht proaktiv auf. Die folgende Matrix bleibt als Diskussionsgrundlage erhalten.
Entscheidungskriterien
Diese Kriterien werden für jeden Kandidaten 1–5 bewertet (5 = sehr gut). Gewichtung folgt unten in der Übersicht.
| ID | Kriterium | Was wir konkret prüfen |
|---|---|---|
| K-01 | Postgres-Treiber-Reife | Connection-Pooling, prepared statements, JSONB-Mapping, LISTEN/NOTIFY für Outbox-Wakeup |
| K-02 | JSONB-Ergonomie | Wie schmerzhaft ist es, DDM_ENTITY.attributes typisiert zu lesen/schreiben? Gibt es JSON-Path-Hilfen? |
| K-03 | Migrations-Tooling | Versionierte SQL-Migrationen mit Rollback, Trigger/Funktionen verwaltbar, Seed-Daten |
| K-04 | OIDC/JWT-Bibliotheken | Konkret: Validierung von Authentik-JWTs (RS256), JWKS-Discovery + Cache, iss/aud/exp-Prüfung, Custom-Claim-Auslesen (tenant_id, groups), Resource-Server-Patterns |
| K-05 | OpenAPI-Generierung | Spec-First oder Code-First? Auto-Client für Next.js? |
| K-06 | Validierungs-Ökosystem | Schema-Sprache passend zu Zod im Frontend (geteilte Schemas wären ideal) |
| K-07 | Background-Worker / Outbox-Dispatcher | In-Process-Scheduler, Long-Running-Worker, Retry/Backoff, exponential available_at |
| K-08 | Type-Safety | Statische Typen, exhaustive Pattern-Matching, ADTs für permission_action/acl_scope |
| K-09 | Performance pro Worker | Throughput für JSONB-lastige Schreib-/Lese-Pfade, Speicher-Footprint, Cold-Start |
| K-10 | Observability | OpenTelemetry-SDK-Reife, strukturierte Logs, Metrics, Tracing |
| K-11 | Testing | Container-/Integration-Tests gegen echte Postgres-Instanz, Mocking-Aufwand |
| K-12 | Team-/Markt-Verfügbarkeit | Wie leicht finden und onboarden wir Engineers für diesen Stack? |
| K-13 | Deployment-Footprint | Image-Größe, Speicher/CPU im Idle, Kaltstart, Eignung für Coolify/Container-PaaS |
| K-14 | Long-running Connections / pgbouncer-Verträglichkeit | Verträgt der Treiber session-mode vs. transaction-mode? |
| K-15 | Reife für RBAC/Multi-Tenancy | Existieren Bibliotheken/Patterns für policy-driven Authz, Tenant-Context-Propagation? |
| K-16 | DDD/Service-Layer-Konventionen | Wie natürlich lässt sich ein 3-Schichten-Modell (UI/API → Service → DB) abbilden? |
| K-17 | Reife der Roadmap-Komponenten | Match/Merge (OP-29), pg_trgm-Suche, später optional Reporting-Schema |
Kandidaten — kompakte Eignungsmatrix
Bewertung 1–5. Werte sind Einschätzung, kein gemessenes Benchmark.
| Kandidat | K-01 Treiber | K-02 JSONB | K-03 Migrations | K-04 OIDC | K-05 OpenAPI | K-06 Schema-Share Frontend | K-07 Worker | K-08 Typen | K-09 Perf | K-10 OTel | K-11 Test | K-12 Markt | K-13 Footprint | K-14 pgbouncer | K-15 Authz | K-16 Layering | K-17 Roadmap |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Java (≥ 21) + Spring Boot 4 ★ | 5 | 4 | 5 (Flyway/Liquibase) | 5 | 4 | 2 | 5 | 4 | 4 | 5 | 5 | 5 | 2 | 5 | 5 | 5 | 4 |
| Kotlin + Spring Boot 4 | 5 | 4 | 5 | 5 | 4 | 3 | 5 | 5 | 4 | 5 | 5 | 4 | 2 | 5 | 5 | 5 | 4 |
| Kotlin + Ktor + Exposed/jOOQ | 5 | 4 | 4 | 4 | 3 | 3 | 4 | 5 | 4 | 4 | 4 | 3 | 3 | 5 | 4 | 4 | 4 |
| TypeScript + NestJS (Node) | 4 | 5 | 4 (drizzle/kysely + node-pg-migrate) | 4 | 4 | 5 (Zod geteilt) | 4 (BullMQ) | 5 | 3 | 4 | 4 | 5 | 4 | 4 | 4 | 5 | 3 |
| TypeScript + Fastify + Drizzle | 4 | 5 | 4 | 4 | 4 (typebox) | 5 | 4 | 5 | 3 | 4 | 4 | 5 | 4 | 4 | 3 | 4 | 3 |
| TypeScript + Bun + Hono + Drizzle | 4 | 5 | 4 | 4 | 4 | 5 | 3 (jung) | 5 | 4 | 3 | 3 | 3 | 5 | 4 | 3 | 4 | 3 |
| Python + FastAPI + SQLAlchemy 2 | 4 | 4 | 4 (Alembic) | 4 | 5 (auto OpenAPI) | 3 (Pydantic ↔ Zod) | 4 (RQ/Celery/APScheduler) | 3 | 3 | 4 | 4 | 5 | 3 | 4 | 3 | 4 | 4 |
| Go + Chi/Echo + sqlc | 5 | 3 (manueller JSONB-Cast) | 4 (golang-migrate) | 4 | 3 | 2 | 4 | 4 | 5 | 4 | 4 | 4 | 5 | 5 | 3 | 4 | 3 |
| Rust + Axum + sqlx | 5 | 4 | 4 (sqlx-migrate/refinery) | 4 | 4 (utoipa) | 3 (typeshare/specta → Zod) | 4 (Tokio + LISTEN/NOTIFY) | 5 | 5 | 4 | 4 | 3 | 5 | 5 | 4 | 4 | 4 |
| C# .NET 8 + Minimal API + EF Core / Dapper | 5 | 4 | 5 (EF Migrations / FluentMigrator) | 5 | 4 | 3 | 5 | 5 | 4 | 5 | 5 | 4 | 3 | 5 | 5 | 5 | 4 |
Vor- und Nachteile pro Kandidat (Kurzform)
Java (≥ 21) + Spring Boot 4 ★ (aktuelle Präferenz)
| Vorzüge | Nachteile |
|---|---|
| Riesiges Ökosystem für Postgres, OIDC, Outbox-Worker, OpenTelemetry. Spring Modulith, Spring Batch, jOOQ verfügbar. | Höhere Speicherbelegung, langsamer Cold-Start (Container-Footprint). |
| Reifste Migrations-Tools (Flyway, Liquibase). | Kein einfacher Schema-Share mit Zod im Frontend. |
| Sehr stabile Treiber, exzellente PgBouncer-Eignung in transaction-mode mit prepared statements (Java 21+ JDBC). | Boilerplate höher als bei TS/Kotlin/Python — Implementation und Tests umfangreicher. |
| Spring Boot 4 baut auf Spring Framework 7 + Jakarta EE 11; Virtual Threads (Project Loom) sind first-class für hohen Connection-Throughput. | Spring Boot 4 ist zum Zeitpunkt dieser Spec frisch — Dokumentation und Drittbibliotheken holen ggf. noch auf. |
| Größter Talentpool im DACH-Markt. |
Kotlin + Spring Boot 4
| Vorzüge | Nachteile |
|---|---|
| Wie Java, aber ergonomischer (data classes, Coroutines, Result types, weniger Boilerplate). | Etwas kleinerer Talentpool als reines Java. |
| Gute Interop mit existierenden Java-Bibliotheken. | JVM-Footprint bleibt. |
Eignet sich gut für ADT-artige Modellierung von permission_action/acl_scope. |
Kotlin + Ktor + Exposed/jOOQ
| Vorzüge | Nachteile |
|---|---|
| Schlanker als Spring, gute DSL-Erfahrung. | Weniger fertige Building-Blocks (z. B. weniger Komfort für RBAC-Patterns). |
| Coroutines-native für Worker. | Migrations-Tooling weniger reif als Spring/Flyway-Kombi. |
| Kleinerer Talentpool. |
TypeScript + NestJS (Node)
| Vorzüge | Nachteile |
|---|---|
| Schemas geteilt mit Frontend (Zod im API-Layer + im Next.js-Client). | Single-threaded (Worker pro Prozess); JSONB-lastige Workloads brauchen Skalierung über mehr Instanzen. |
| Sehr großer Talentpool, gleicher Kontext wie Frontend. | Background-Worker (BullMQ) braucht Redis als zusätzlichen Service oder einen separaten Postgres-basierten Worker. |
| Drizzle ORM oder Kysely sehr ergonomisch für Postgres + JSONB. | OpenTelemetry-SDK weniger reif als JVM/.NET. |
| Niedriger Container-Footprint. |
TypeScript + Fastify + Drizzle
| Vorzüge | Nachteile |
|---|---|
| Schlanker als NestJS, höhere Throughput-Werte. | Weniger Konventionen — DDD-Layering muss man selbst aufbauen. |
| TypeBox/Zod direkt für Schema + OpenAPI. | |
| Gleicher Schema-Share-Vorteil. |
TypeScript + Bun + Hono + Drizzle
| Vorzüge | Nachteile |
|---|---|
| Sehr schneller Cold-Start, gute Performance. | Bun ist jünger; Risiko bei Edge-Cases (PgBouncer, OTel). |
| Eingebaute Test-/Bundler-Tools. | Worker-Stack weniger reif. |
| Schemas geteilt mit Frontend. |
Python + FastAPI + SQLAlchemy 2
| Vorzüge | Nachteile |
|---|---|
| Auto-OpenAPI aus Pydantic-Modellen, sehr schnelle Iteration. | GIL begrenzt CPU-parallele Workloads; Worker oft separat (Celery). |
| Großer Talentpool, viel ML/Daten-Integration möglich (für spätere DQ-/Match-Features). | Type-System schwächer als Kotlin/TS. |
| Alembic ist ein bewährtes Migrations-Tool. | Keine direkte Zod-Schema-Synchronisation. |
Go + Chi/Echo + sqlc
| Vorzüge | Nachteile |
|---|---|
| Sehr schlanker Footprint, hohe Performance. | JSONB ist umständlich (manuelles Marshal/Unmarshal pro Attribut), genau das, was wir am meisten anfassen. |
| Goroutines sind ideal für Outbox-Worker. | Schwächere Ergonomie für stark dynamische Modelle. |
| Statische Binaries → einfach in Coolify deploybar. | OpenAPI-Tooling weniger reif. |
Rust + Axum + sqlx
| Vorzüge | Nachteile |
|---|---|
| Höchste Performance und niedrigster Footprint (statisch gelinktes Binary, ~10–30 MB Container möglich). | Höhere Ramp-up-Zeit; Talentpool kleiner als TS/Java in DACH. |
| Memory-Safety ohne GC: keine Use-after-free, kein Datenrennen — relevant für langlebigen API-Service mit Outbox-Worker. | Compile-Zeiten spürbar (release-Build im CI mehrere Minuten). |
Sum-Types/Enums ideal für permission_action, acl_scope, delete_policy — illegaler State ist nicht repräsentierbar. | sqlx-Macros brauchen DATABASE_URL zur Compile-Time oder sqlx prepare-Cache im Repo. |
sqlx mit Compile-Time-SQL-Verification: jede Query wird gegen reales Postgres-Schema geprüft, bevor das Binary entsteht. | OIDC-Bibliotheken kleiner als auf JVM/.NET, aber openidconnect-Crate ist ausreichend reif. |
Tokio + LISTEN/NOTIFY als nativer Outbox-Wakeup ohne Polling. | Kein direkter Schema-Share mit Zod — Brücke via typeshare/specta (TS-Typgenerierung aus Rust-Strukturen) nötig. |
tracing + tracing-opentelemetry ist eines der durchdachtesten Observability-Frameworks im gesamten Vergleich. | Bei JSONB sind Casts manueller als in TS/Python, aber mit sqlx::types::Json<T> + serde strukturell sauber. |
C# .NET 8 + Minimal API + EF Core / Dapper
| Vorzüge | Nachteile |
|---|---|
| Reife wie JVM, oft besserer Footprint als Spring (NativeAOT optional). | Talentpool kleiner als Java/TS in DACH-Region. |
| Erstklassige OIDC-/Identity-Integration. | EF Core ist für JSONB nutzbar, aber stark relational geprägt. |
| Hervorragende OTel-/Logging-Integration. | Closed-Source-Erfahrung höher als bei JVM/Node. |
Empfehlungs-Heuristik
Ohne Ziel-Bewertungs-Gewichtung sind das die naheliegenden Pfade:
- „Schemas geteilt mit dem Frontend zählt am meisten” → TypeScript-Backend (NestJS oder Fastify+Drizzle). Vorteil: Zod einmal definieren, vom Form-Validator bis zur DB-Validierung. Nachteil: Single-threaded, Worker-Skalierung über Instanzen.
- „Größtmögliche Reife / langfristige Stabilität” → Spring Boot 4 mit Java oder .NET 8. Vorteil: alles ist schon mal gebaut worden. Nachteil: kein Schema-Share zum Frontend, höherer Footprint.
- „Niedrigster Footprint, höchste Performance” → Go oder Rust. Vorteil: Container-Minimalismus. Nachteil: JSONB-Ergonomie und/oder Ramp-up.
- „Schnelle Time-to-First-Endpoint” → FastAPI. Vorteil: Auto-OpenAPI, kurze Iterationen. Nachteil: weniger Type-Safety, Worker-Setup separat.
Aus reiner Kohärenz mit dem Frontend (Next.js/TypeScript/Zod) und dem stark JSONB-getriebenen Domänenmodell wäre TypeScript + NestJS + Drizzle der Pfad mit der niedrigsten Reibung — vorausgesetzt, der Outbox-Worker wird sauber als zweiter Prozess (oder Postgres-LISTEN-getriebener In-Process-Loop) modelliert. Java + Spring Boot 4 ist die aktuelle Präferenz und naheliegende Wahl, da Stabilität, Reife und DACH-Talentpool stärker gewichtet werden als Schema-Share. Rust + Axum + sqlx ist die Wahl, wenn Memory-Safety, Performance und langfristig niedriger Betriebs-Footprint höher gewichtet werden als kurzfristige Liefergeschwindigkeit (siehe eigener Abschnitt unten).
Diese Empfehlung ist nicht entscheidungsbindend — sie soll die Diskussion strukturieren.
Empfehlung Rust-Pfad (Memory-Safety + Performance)
Eigener detaillierter Vorschlag, falls Memory-Safety und Performance als primäre Kriterien gesetzt werden (z. B. langlebiger API-Service + Outbox-Worker mit hoher Eventrate, regulatorische Anforderungen an Speicher-Sicherheit, niedrige Hosting-Kosten / kleine Container).
Crate-Stack (konkret)
| Schicht | Crate | Begründung |
|---|---|---|
| Async-Runtime | tokio | Quasi-Standard, multithreaded, beste Treiber-Unterstützung |
| Web-Framework | axum (auf tower/hyper) | Reife, type-safe Extractors, nahtlose Tower-Middleware (Auth, Tracing, Rate-Limit) |
| HTTP-Layer-Middleware | tower-http | Compression, CORS, Trace, RequestId out of the box |
| DB-Treiber + Query-Layer | sqlx (Postgres) | Compile-time-geprüfte SQL-Queries, kein ORM-Overhead, JSON<T>-Typ für DDM_ENTITY.attributes |
| Migrations | sqlx::migrate! (oder refinery) | Versionierte SQL-Migrations im Repo; Trigger/Funktionen als Repeatable-Migrations |
| OIDC / JWT | openidconnect + jsonwebtoken | Authentik-Discovery, JWKS-Cache, RS256-Signaturprüfung, Auslesen Custom-Claims (tenant_id, groups) |
| Authz-Modell | eigener Crate (intern) auf Basis von enum-ADTs | permission_action, acl_scope, permission_effect als Enum mit erschöpfendem Pattern-Matching — Compiler erzwingt Vollständigkeit |
| OpenAPI | utoipa (Doc-Macros) + utoipa-swagger-ui | Spec wird aus Handler-Annotations generiert; Output als JSON für Codegen |
| Schema-Share zu Frontend | typeshare oder specta | Generiert TS-Typen aus Rust-Strukturen → in Next.js-Client einbinden, dort optional mit Zod schemata wrappen |
| Validierung (Service-Layer) | garde (oder validator) | Deklarative Constraints auf DTOs, ergänzt DB-Constraints |
| Serialisierung | serde + serde_json | Standard, mit JsonSchema-Ableitung für utoipa |
| Hashing/Crypto | argon2, ring / rustls | Argon2 für lokale Geheimnisse (z. B. Service-Account-Keys), TLS via rustls |
| Observability | tracing + tracing-opentelemetry + opentelemetry-otlp | Structured logging, OTLP-Export, Korrelation über X-Correlation-Id-Layer |
| Metrics | metrics + metrics-exporter-prometheus | Pull-Endpoint /metrics, kompatibel mit klassischem Prometheus-Stack |
| Outbox-Worker | eigener Binary-Crate im Workspace, tokio + sqlx::LISTEN/NOTIFY | Kein zusätzlicher Broker für die Wakeup-Logik; Lease via SELECT … FOR UPDATE SKIP LOCKED |
| HTTP-Client (Webhooks) | reqwest mit rustls | Async, HTTP/2, HMAC-Signing per Tower-Layer |
| Tests | tokio::test + sqlx::test + testcontainers | Echte Postgres-Instanz pro Testfall mit auto-rollback; Trigger und Views werden real getestet |
| Build/Layout | Cargo-Workspace mit Crates api, worker, domain, infra-db, infra-oidc | Saubere Schichtgrenze, gemeinsame Domain-Crate, zwei Binaries (api + worker) |
Architektur-Skizze
+----------------------------+ | Next.js Frontend | | (Auth.js gegen IdP-OIDC) | +-------------+--------------+ | v +------------------------+----------------------+ | Rust Cargo-Workspace | | | | bin: api bin: outbox-worker | | ---------- -------------------- | | axum + tower tokio + sqlx + reqwest | | utoipa OpenAPI LISTEN/NOTIFY + SKIP LOCKED | | \ / | | v v | | +-----------------+ | | | domain crate | (Service-Layer, | | | pure logic, | Authz, Validation, | | | no I/O) | Backfill, RBAC) | | +--------+--------+ | | | | | v | | +-----------------+ | | | infra-db crate | (sqlx repos, | | | Postgres I/O) | transactions, | | +--------+--------+ listen/notify) | +----------------|------------------------------+ v +-----------+ | Postgres | (mdm + mdm_sys schema) +-----------+Pflichten / verbindliche Defaults
- Workspace-Layout: Trennung in
domain(reine Logik, keine Tokio/Sqlx-Abhängigkeit),infra-db(sqlx, Repository-Trait-Implementierungen),api-Binary (axum),worker-Binary (Outbox-Dispatch). Tests laufen pro Crate. - Authz-Enum:
permission_actionundacl_scopewerden in Rust als#[derive(strum::EnumString, sqlx::Type)]-Enums angelegt — Compiler erzwingt vollständige Verzweigung in der Auflösungslogik. - JSONB:
DDM_ENTITY.attributeswird alssqlx::types::Json<HashMap<String, serde_json::Value>>gemappt; Service-Layer wandelt vor dem Schreiben in die typisierte Domänen-Form. - Transaktion: jede Mutation läuft in einer
sqlx::Transaction; Reihenfolge entspricht Abläufe – Verbindliche Reihenfolge in einer Mutationstransaktion. Ein Tower-Layerwith_request_txreicht den Tx-Handle in den Handler. - Outbox-Wakeup: Worker setzt
LISTEN mdm_outbox_newab; Service emitiert nach Insert inAA_OUTBOX_EVENTeinNOTIFY. Polling-Fallback alle 5 s als Sicherheitsnetz. - OpenAPI: utoipa-Spec wird im CI als Artefakt erzeugt; Frontend-Client wird via
openapi-typescriptdaraus generiert. - Schema-Share: Domain-DTOs sind mit
#[derive(typeshare::Typeshare)]annotiert; CI-Job renderttypes.tsfür das Frontend. Für Form-Validierung im Frontend ergänzen wir Zod-Schemas (von Hand auf die generierten Typen abgestimmt) — keine vollautomatische Synchronisation, aber ein strukturierter Sync-Pfad. - Container: Multi-Stage-Dockerfile mit
cargo cheffür Caching → finales Image aufgcr.io/distroless/cc-debian12oderscratchmit musl-Build. Erwarteter Image-Footprint: 10–30 MB.
Memory-Safety- und Performance-Argumente konkret
| Aspekt | Auswirkung |
|---|---|
| Borrow-Checker | Kein Daten-Rennen im Outbox-Worker zwischen mehreren Tokio-Tasks; Pool-Handles sind Send + Sync mit klaren Lifetimes. |
| Kein GC | Keine Pause-Spitzen, deterministische Latenz — relevant für SLO-getriebene Systeme. |
| Statische Binaries | Kein Klassenpfad, kein Interpreter, kein JIT-Warmup; --init-Container ist sofort einsatzbereit. |
sqlx Compile-Time-Checks | SQL-Fehler (Tippfehler, fehlende Spalte) werden im cargo build gemeldet — nicht erst im Integrationstest. |
tokio Multithread-Runtime | CPU-bound Validierungs-Workloads skalieren über alle Cores ohne separate Worker-Instanz. |
| Niedriger RAM | typischer Idle-Footprint < 50 MB für API-Binary; ermöglicht dichtere Container-Packung. |
Risiken und Gegenmaßnahmen
| Risiko | Gegenmaßnahme |
|---|---|
| Lange Compile-Zeiten bremsen Entwicklung | cargo-watch + sccache lokal; CI mit cargo chef und Layer-Caching; Workspace klein halten |
| Talentpool kleiner | Klare Layering-Konvention dokumentieren; Onboarding-Repo-Tour als Pflichtdokument; pair-programming für die ersten Features |
| OIDC-Crate-Reife schwankt | Auf openidconnect (von KILLA Heimdall geprüft) festlegen; JWKS-Cache mit klar definierter TTL |
| Schema-Share zum Frontend manueller als in TS-Backend | CI-Gate: wenn types.ts aus der API nicht synchron mit dem Frontend-Repo, schlägt der Build fehl |
sqlx-Macros brauchen lebende DB im Build | sqlx prepare erzeugt .sqlx-Cache, der im Repo eingecheckt wird → CI-Builds brauchen keine DB |
| utoipa-Annotationsmenge hoch | Macro-Wrapper im internen Crate, der Standard-Patterns bündelt (Pagination, Error-Envelope, RFC 7807) |
Wann Rust besonders sinnvoll
- Stabile, langlaufende Plattform — Rust spielt seine Vorteile bei mehrjährigem Betrieb aus.
- Hohes Schreibvolumen über die Outbox (Webhook-Fanout, Search-Index-Replikation) — Rust skaliert dort am günstigsten.
- Compliance-getriebene Umgebungen, in denen Memory-Safety als Nicht-funktionale Anforderung explizit auftaucht.
- Hosting-Kostenoptimierung wichtiger als Time-to-First-Endpoint.
Wann eher nicht Rust
- Sehr enges Zeitbudget für die ersten Features (TS-Pfad ist 2–3× schneller bis zur ersten lauffähigen API).
- Team ohne Rust-Erfahrung, ohne dezidierte Onboarding-Phase.
- Hohe Iterationsrate auf der Domänenmodell-Form (häufige Refactorings sind in TS billiger).
Offen: Migrations-Tool
Pro Backend gilt etwas anderes. Generelle Anforderungen:
- versionierte SQL-Migrationen, idempotent abspielbar
- Trigger und Funktionen sind erststerklassig (kein “alles in ORM-Migrations rein”)
- separater Repeatable-Migrations-Pfad für Views (
mdm.v_<key>) - Rollback-Strategie definiert (oder explizit als „forward-only” beschlossen)
Kandidaten je Stack:
| Stack | Empfehlung |
|---|---|
| Java/Kotlin | Flyway (oder Liquibase, falls Rollback gewünscht) |
| TypeScript | drizzle-kit oder node-pg-migrate (SQL-first) |
| Python | Alembic |
| Go | golang-migrate |
| Rust | sqlx-migrate oder refinery |
| .NET | FluentMigrator (für SQL-first) |
Offen: Outbox-Dispatcher-Strategie
Drei Optionen, unabhängig vom Backend:
| Option | Vorzüge | Nachteile |
|---|---|---|
| In-Process-Worker im Backend-Service | Ein Deployment, geteilter Code, geteilte DB-Connection-Konfiguration | API-Service skaliert horizontal — alle Replicas konkurrieren um Outbox-Rows (Lock-Pattern via SELECT … FOR UPDATE SKIP LOCKED nötig) |
| Eigenständiger Worker-Service | Saubere Trennung, eigene Skalierung, kein Lärm im API-Hot-Path | Zwei Deployments, Code-Duplikation oder Mono-Repo-Disziplin |
| CDC via Debezium | Standardisiert, geringe Eigenentwicklung | Zusätzliche Infrastruktur (Kafka/Connect), schwerer zu betreiben für Greenfield |
Empfehlung Default: eigenständiger Worker-Service mit SELECT … FOR UPDATE SKIP LOCKED. Saubere Skalierung, einfaches Backpressure, klares Restart-Verhalten.
Offen: Konsumenten-Mechanismus
| Variante | Wann sinnvoll |
|---|---|
| Webhooks (HTTP + HMAC) | Externe SaaS-Integrationen, kleine bis mittlere Eventraten |
| NATS / RabbitMQ | Mehrere interne Konsumenten, Pub/Sub, niedriger Aufwand |
| Kafka | Hohe Volumina, lange Retention, Stream-Processing-Pipelines |
| Externer Suchindex (OpenSearch) | Volltext-/Match-Anforderung wächst über Postgres tsvector hinaus |
Die Auswahl ist konsumentenseitig austauschbar — Outbox-Worker kann mehrere Backends bedienen.
Offen: Test-Strategie
Unabhängig vom Stack:
- Unit-Tests für Service-Layer-Logik (Validierung, Authz-Auflösung, Backfill).
- Integration-Tests gegen echte Postgres (Testcontainers o. ä.) — Trigger, partial Indexes und
v_effective_permissionmüssen real getestet werden. - Contract-Tests zwischen Backend-API und Frontend (OpenAPI + generierter TS-Client, oder geteilte Zod-Schemas).
- End-to-End-Tests auf Next.js-Seite (Playwright).
Offen: Observability-Stack
- Tracing: OpenTelemetry, Backend an OTLP-Collector. Korrelation über
X-Correlation-Id. - Metrics: Prometheus (Pull) oder OTLP-Push.
- Logs: strukturiertes JSON, Korrelation-ID auf jedem Log-Eintrag, Audit/Outbox-IDs als Felder.
- Konkrete Provider (Grafana Cloud, Tempo, Loki, Datadog, …) später.
Deployment-Plattform (gesetzt)
- Coolify (self-hosted) als Container-PaaS.
- GitHub als Source-Hosting.
- Build über
docker-compose.yamlim Repo — Coolify pullt den Branch, baut Image lokal, deployt rolling.
Daraus folgt für Backend (verbindlich):
- Pro Service ein
docker-compose.yamlim Repo (API, Outbox-Worker, optional Migrations-Job). - Container-Port via
expose:/EXPOSE— kein Host-Port-Mapping (ports:) für deployte Services. Coolifys Traefik routet TLS + Domain auf den Container-Port; Host-Ports bypassen Proxy/TLS und kollidieren auf dem Host. - Migrations als One-Shot-Service in compose (
depends_onder App) oder als Pre-Start-Command im App-Container. - Outbox-Worker ist eigener Container.
- Secrets in Coolifys Environment-UI gepflegt, nicht im Image, nicht im Repo.
- Health Checks (
HEALTHCHECKin Dockerfile oderhealthcheck:in compose) Pflicht für Rolling-Updates.
Detail siehe Laufzeitumgebung und CI/CD und Migrationen.
Verwandte Dokumente
Entscheidung
Wird ergänzt, sobald Stack festgelegt. Bis dahin gelten die Empfehlungen oben als Diskussionsgrundlage, nicht als Festlegung.
| Schicht | Wahl | Begründung | Datum |
|---|---|---|---|
| Frontend-Framework | Next.js (App Router) | Vorgabe Auftraggeber | 2026-04-26 |
| Frontend-UI | shadcn/ui (base-ui) + Tailwind 4 | Vorgabe Auftraggeber | 2026-04-26 |
| Datenbank | PostgreSQL latest | Spec-Anforderungen (JSONB, Trigger, partial Indexes, tsvector) | 2026-04-26 |
| Identity-Provider | Authentik (self-hosted) | Vorgabe Auftraggeber | 2026-04-26 |
| Source-Hosting | GitHub | Vorgabe Auftraggeber | 2026-04-26 |
| Deployment-Plattform | Coolify (self-hosted) | Vorgabe Auftraggeber | 2026-04-26 |
| Image-Build | über docker-compose.yaml durch Coolify | Vorgabe Auftraggeber | 2026-04-26 |
| Backend-Sprache/-Framework | offen — Präferenz: Java (≥ 21) + Spring Boot 4 | Auftraggeber-Vorzug, finale Wahl auf Anforderung | – |
| ORM/Query-Layer | offen | abhängig von Backend | – |
| Migrations-Tool | offen | abhängig von Backend | – |
| Outbox-Dispatcher | offen | Default-Vorschlag: eigenständiger Worker | – |
| Eventkonsument | offen | Default-Vorschlag: Webhooks zuerst, Broker bei Bedarf | – |