Zum Inhalt springen

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-webapp fü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, nicht placeholder. Detail siehe globale CLAUDE.md.
  • CSS-Pflicht: scrollbar-gutter: stable auf html (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.

IDKriteriumWas wir konkret prüfen
K-01Postgres-Treiber-ReifeConnection-Pooling, prepared statements, JSONB-Mapping, LISTEN/NOTIFY für Outbox-Wakeup
K-02JSONB-ErgonomieWie schmerzhaft ist es, DDM_ENTITY.attributes typisiert zu lesen/schreiben? Gibt es JSON-Path-Hilfen?
K-03Migrations-ToolingVersionierte SQL-Migrationen mit Rollback, Trigger/Funktionen verwaltbar, Seed-Daten
K-04OIDC/JWT-BibliothekenKonkret: Validierung von Authentik-JWTs (RS256), JWKS-Discovery + Cache, iss/aud/exp-Prüfung, Custom-Claim-Auslesen (tenant_id, groups), Resource-Server-Patterns
K-05OpenAPI-GenerierungSpec-First oder Code-First? Auto-Client für Next.js?
K-06Validierungs-ÖkosystemSchema-Sprache passend zu Zod im Frontend (geteilte Schemas wären ideal)
K-07Background-Worker / Outbox-DispatcherIn-Process-Scheduler, Long-Running-Worker, Retry/Backoff, exponential available_at
K-08Type-SafetyStatische Typen, exhaustive Pattern-Matching, ADTs für permission_action/acl_scope
K-09Performance pro WorkerThroughput für JSONB-lastige Schreib-/Lese-Pfade, Speicher-Footprint, Cold-Start
K-10ObservabilityOpenTelemetry-SDK-Reife, strukturierte Logs, Metrics, Tracing
K-11TestingContainer-/Integration-Tests gegen echte Postgres-Instanz, Mocking-Aufwand
K-12Team-/Markt-VerfügbarkeitWie leicht finden und onboarden wir Engineers für diesen Stack?
K-13Deployment-FootprintImage-Größe, Speicher/CPU im Idle, Kaltstart, Eignung für Coolify/Container-PaaS
K-14Long-running Connections / pgbouncer-VerträglichkeitVerträgt der Treiber session-mode vs. transaction-mode?
K-15Reife für RBAC/Multi-TenancyExistieren Bibliotheken/Patterns für policy-driven Authz, Tenant-Context-Propagation?
K-16DDD/Service-Layer-KonventionenWie natürlich lässt sich ein 3-Schichten-Modell (UI/API → Service → DB) abbilden?
K-17Reife der Roadmap-KomponentenMatch/Merge (OP-29), pg_trgm-Suche, später optional Reporting-Schema

Kandidaten — kompakte Eignungsmatrix

Bewertung 1–5. Werte sind Einschätzung, kein gemessenes Benchmark.

KandidatK-01 TreiberK-02 JSONBK-03 MigrationsK-04 OIDCK-05 OpenAPIK-06 Schema-Share FrontendK-07 WorkerK-08 TypenK-09 PerfK-10 OTelK-11 TestK-12 MarktK-13 FootprintK-14 pgbouncerK-15 AuthzK-16 LayeringK-17 Roadmap
Java (≥ 21) + Spring Boot 4545 (Flyway/Liquibase)54254455525554
Kotlin + Spring Boot 454554355455425554
Kotlin + Ktor + Exposed/jOOQ54443345444335444
TypeScript + NestJS (Node)454 (drizzle/kysely + node-pg-migrate)445 (Zod geteilt)4 (BullMQ)5344544453
TypeScript + Fastify + Drizzle45444 (typebox)545344544343
TypeScript + Bun + Hono + Drizzle4544453 (jung)5433354343
Python + FastAPI + SQLAlchemy 2444 (Alembic)45 (auto OpenAPI)3 (Pydantic ↔ Zod)4 (RQ/Celery/APScheduler)3344534344
Go + Chi/Echo + sqlc53 (manueller JSONB-Cast)4 (golang-migrate)43244544455343
Rust + Axum + sqlx544 (sqlx-migrate/refinery)44 (utoipa)3 (typeshare/specta → Zod)4 (Tokio + LISTEN/NOTIFY)5544355444
C# .NET 8 + Minimal API + EF Core / Dapper545 (EF Migrations / FluentMigrator)54355455435554

Vor- und Nachteile pro Kandidat (Kurzform)

Java (≥ 21) + Spring Boot 4 ★ (aktuelle Präferenz)

VorzügeNachteile
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ügeNachteile
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ügeNachteile
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ügeNachteile
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ügeNachteile
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ügeNachteile
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ügeNachteile
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ügeNachteile
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ügeNachteile
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ügeNachteile
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:

  1. „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.
  2. „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.
  3. „Niedrigster Footprint, höchste Performance” → Go oder Rust. Vorteil: Container-Minimalismus. Nachteil: JSONB-Ergonomie und/oder Ramp-up.
  4. „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)

SchichtCrateBegründung
Async-RuntimetokioQuasi-Standard, multithreaded, beste Treiber-Unterstützung
Web-Frameworkaxum (auf tower/hyper)Reife, type-safe Extractors, nahtlose Tower-Middleware (Auth, Tracing, Rate-Limit)
HTTP-Layer-Middlewaretower-httpCompression, CORS, Trace, RequestId out of the box
DB-Treiber + Query-Layersqlx (Postgres)Compile-time-geprüfte SQL-Queries, kein ORM-Overhead, JSON<T>-Typ für DDM_ENTITY.attributes
Migrationssqlx::migrate! (oder refinery)Versionierte SQL-Migrations im Repo; Trigger/Funktionen als Repeatable-Migrations
OIDC / JWTopenidconnect + jsonwebtokenAuthentik-Discovery, JWKS-Cache, RS256-Signaturprüfung, Auslesen Custom-Claims (tenant_id, groups)
Authz-Modelleigener Crate (intern) auf Basis von enum-ADTspermission_action, acl_scope, permission_effect als Enum mit erschöpfendem Pattern-Matching — Compiler erzwingt Vollständigkeit
OpenAPIutoipa (Doc-Macros) + utoipa-swagger-uiSpec wird aus Handler-Annotations generiert; Output als JSON für Codegen
Schema-Share zu Frontendtypeshare oder spectaGeneriert 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
Serialisierungserde + serde_jsonStandard, mit JsonSchema-Ableitung für utoipa
Hashing/Cryptoargon2, ring / rustlsArgon2 für lokale Geheimnisse (z. B. Service-Account-Keys), TLS via rustls
Observabilitytracing + tracing-opentelemetry + opentelemetry-otlpStructured logging, OTLP-Export, Korrelation über X-Correlation-Id-Layer
Metricsmetrics + metrics-exporter-prometheusPull-Endpoint /metrics, kompatibel mit klassischem Prometheus-Stack
Outbox-Workereigener Binary-Crate im Workspace, tokio + sqlx::LISTEN/NOTIFYKein zusätzlicher Broker für die Wakeup-Logik; Lease via SELECT … FOR UPDATE SKIP LOCKED
HTTP-Client (Webhooks)reqwest mit rustlsAsync, HTTP/2, HMAC-Signing per Tower-Layer
Teststokio::test + sqlx::test + testcontainersEchte Postgres-Instanz pro Testfall mit auto-rollback; Trigger und Views werden real getestet
Build/LayoutCargo-Workspace mit Crates api, worker, domain, infra-db, infra-oidcSaubere 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_action und acl_scope werden in Rust als #[derive(strum::EnumString, sqlx::Type)]-Enums angelegt — Compiler erzwingt vollständige Verzweigung in der Auflösungslogik.
  • JSONB: DDM_ENTITY.attributes wird als sqlx::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-Layer with_request_tx reicht den Tx-Handle in den Handler.
  • Outbox-Wakeup: Worker setzt LISTEN mdm_outbox_new ab; Service emitiert nach Insert in AA_OUTBOX_EVENT ein NOTIFY. Polling-Fallback alle 5 s als Sicherheitsnetz.
  • OpenAPI: utoipa-Spec wird im CI als Artefakt erzeugt; Frontend-Client wird via openapi-typescript daraus generiert.
  • Schema-Share: Domain-DTOs sind mit #[derive(typeshare::Typeshare)] annotiert; CI-Job rendert types.ts fü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 chef für Caching → finales Image auf gcr.io/distroless/cc-debian12 oder scratch mit musl-Build. Erwarteter Image-Footprint: 10–30 MB.

Memory-Safety- und Performance-Argumente konkret

AspektAuswirkung
Borrow-CheckerKein Daten-Rennen im Outbox-Worker zwischen mehreren Tokio-Tasks; Pool-Handles sind Send + Sync mit klaren Lifetimes.
Kein GCKeine Pause-Spitzen, deterministische Latenz — relevant für SLO-getriebene Systeme.
Statische BinariesKein Klassenpfad, kein Interpreter, kein JIT-Warmup; --init-Container ist sofort einsatzbereit.
sqlx Compile-Time-ChecksSQL-Fehler (Tippfehler, fehlende Spalte) werden im cargo build gemeldet — nicht erst im Integrationstest.
tokio Multithread-RuntimeCPU-bound Validierungs-Workloads skalieren über alle Cores ohne separate Worker-Instanz.
Niedriger RAMtypischer Idle-Footprint < 50 MB für API-Binary; ermöglicht dichtere Container-Packung.

Risiken und Gegenmaßnahmen

RisikoGegenmaßnahme
Lange Compile-Zeiten bremsen Entwicklungcargo-watch + sccache lokal; CI mit cargo chef und Layer-Caching; Workspace klein halten
Talentpool kleinerKlare Layering-Konvention dokumentieren; Onboarding-Repo-Tour als Pflichtdokument; pair-programming für die ersten Features
OIDC-Crate-Reife schwanktAuf openidconnect (von KILLA Heimdall geprüft) festlegen; JWKS-Cache mit klar definierter TTL
Schema-Share zum Frontend manueller als in TS-BackendCI-Gate: wenn types.ts aus der API nicht synchron mit dem Frontend-Repo, schlägt der Build fehl
sqlx-Macros brauchen lebende DB im Buildsqlx prepare erzeugt .sqlx-Cache, der im Repo eingecheckt wird → CI-Builds brauchen keine DB
utoipa-Annotationsmenge hochMacro-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:

StackEmpfehlung
Java/KotlinFlyway (oder Liquibase, falls Rollback gewünscht)
TypeScriptdrizzle-kit oder node-pg-migrate (SQL-first)
PythonAlembic
Gogolang-migrate
Rustsqlx-migrate oder refinery
.NETFluentMigrator (für SQL-first)

Offen: Outbox-Dispatcher-Strategie

Drei Optionen, unabhängig vom Backend:

OptionVorzügeNachteile
In-Process-Worker im Backend-ServiceEin Deployment, geteilter Code, geteilte DB-Connection-KonfigurationAPI-Service skaliert horizontal — alle Replicas konkurrieren um Outbox-Rows (Lock-Pattern via SELECT … FOR UPDATE SKIP LOCKED nötig)
Eigenständiger Worker-ServiceSaubere Trennung, eigene Skalierung, kein Lärm im API-Hot-PathZwei Deployments, Code-Duplikation oder Mono-Repo-Disziplin
CDC via DebeziumStandardisiert, geringe EigenentwicklungZusä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

VarianteWann sinnvoll
Webhooks (HTTP + HMAC)Externe SaaS-Integrationen, kleine bis mittlere Eventraten
NATS / RabbitMQMehrere interne Konsumenten, Pub/Sub, niedriger Aufwand
KafkaHohe 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_permission mü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.yaml im Repo — Coolify pullt den Branch, baut Image lokal, deployt rolling.

Daraus folgt für Backend (verbindlich):

  • Pro Service ein docker-compose.yaml im Repo (API, Outbox-Worker, optional Migrations-Job).
  • Container-Port via expose: / EXPOSEkein 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_on der 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 (HEALTHCHECK in Dockerfile oder healthcheck: 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.

SchichtWahlBegründungDatum
Frontend-FrameworkNext.js (App Router)Vorgabe Auftraggeber2026-04-26
Frontend-UIshadcn/ui (base-ui) + Tailwind 4Vorgabe Auftraggeber2026-04-26
DatenbankPostgreSQL latestSpec-Anforderungen (JSONB, Trigger, partial Indexes, tsvector)2026-04-26
Identity-ProviderAuthentik (self-hosted)Vorgabe Auftraggeber2026-04-26
Source-HostingGitHubVorgabe Auftraggeber2026-04-26
Deployment-PlattformCoolify (self-hosted)Vorgabe Auftraggeber2026-04-26
Image-Buildüber docker-compose.yaml durch CoolifyVorgabe Auftraggeber2026-04-26
Backend-Sprache/-Frameworkoffen — Präferenz: Java (≥ 21) + Spring Boot 4Auftraggeber-Vorzug, finale Wahl auf Anforderung
ORM/Query-Layeroffenabhängig von Backend
Migrations-Tooloffenabhängig von Backend
Outbox-DispatcheroffenDefault-Vorschlag: eigenständiger Worker
EventkonsumentoffenDefault-Vorschlag: Webhooks zuerst, Broker bei Bedarf