chore(ci): Align GH Workflows with Docker SSoT, new paths; minimal SSoT guard; staticAnalysis (#23)

* chore(MP-21): snapshot pre-refactor state (Epic 1)

* chore(MP-22): scaffold new repo structure, relocate Docker Compose, move frontend/backend modules, update Makefile; add docs mapping and env template

* MP-22 Epic 2: Erfolgreich umgesetzt und verifiziert

* MP-23 Epic 3: Gradle/Build Governance zentralisieren

* MP-23 Epic 3: Gradle/Build Governance zentralisieren

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(devops)!: Docker-SSoT (.env) konsolidiert, Compose-Mounts ergänzt, Makefile entfernt

- ENV Single Source of Truth
  - docker/.env.example neu (inkl. REDIS_PASSWORD, Ports, Build-Overrides)
  - config/.env(.example) als DEPRECATED markiert (Verweis auf docker/.env[.example])

- Docker Compose vereinheitlicht (docker/docker-compose.yaml)
  - Postgres: zentralen postgresql.conf mounten (../config/postgres/postgresql.conf)
    und Start mit -c config_file=/etc/postgresql/postgresql.conf
  - Redis: zentralen redis.conf mounten (../config/redis/redis.conf)
    und Start via "redis-server … ${REDIS_PASSWORD:+--requirepass $REDIS_PASSWORD}"
  - Web-Nginx: ../config/nginx/nginx.prod.conf → /etc/nginx/nginx.conf (ro)
  - Monitoring: Prometheus/Grafana nutzen ../config/monitoring/* als SSoT

- Frontend/DI/Network (MP-23 Grundlage)
  - :frontend:core:network Modul mit Koin `apiClient` (Ktor + JSON/Retry/Timeout/Logging)
  - Plattform-Basis-URL-Auflösung (JVM: ENV API_BASE_URL; JS: globalThis.API_BASE_URL / Same-Origin)
  - Web index.html setzt API_BASE_URL (Query `?apiBaseUrl=…` > Same-Origin > Fallback)

- Build/Gradle & Module-Refs
  - settings.gradle.kts: neue Frontend-/Backend-Pfade bereits inkludiert
  - Features/Shell: Abhängigkeiten auf :frontend:shared / :frontend:core:* angepasst
  - Ping-API-Refs auf :backend:services:ping:ping-api vereinheitlicht

- Dockerfiles angepasst
  - backend/infrastructure/gateway/Dockerfile → Tasks/Pfade auf :backend:gateway
  - backend/services/ping/Dockerfile → Tasks/Pfade auf :backend:services:ping:ping-service

- Static Analysis / Guards
  - config/detekt/detekt.yml hinzugefügt
  - Leichter Arch-Guard (Frontend) gegen manuelle Authorization-Header vorbereitet

- Doku
  - docs/ARCHITECTURE.md (Struktur, Mapping, Next Steps) ergänzt
  - docs/adr/README.md angelegt

BREAKING CHANGES:
- Makefile komplett entfernt (bitte direkt `docker compose` verwenden)
- ENV-Quelle ist jetzt docker/.env (statt config/.env oder Root)
- Compose-Datei unter docker/docker-compose.yaml (nicht mehr compose.yaml im Repo-Root)

Verifikation (lokal):
- ENV anlegen: `cp docker/.env.example docker/.env` (Werte anpassen)
- Compose prüfen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml config`
- Infrastruktur: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle up -d postgres redis keycloak web-app`
- Services bauen: `docker compose --env-file docker/.env -f docker/docker-compose.yaml -p meldestelle build api-gateway ping-service --no-cache --progress=plain`

Refs: MP-22 (Epic 2), MP-23 (Epic 3)

* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard

- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)

Refs: MP-22, MP-23

* chore(ci): Workflows an Docker-SSoT & neue Struktur angepasst, minimaler SSoT-Guard

- ssot-guard.yml: Option B (minimal) → `docker compose -f docker/docker-compose.yaml config` als Lint
- integration-tests.yml: `./gradlew staticAnalysis` vor Integrationstests
- docs-kdoc-sync.yml: Dokka-Task Fallback (dokkaGfmAll || dokkaGfm), YouTrack-Sync nur wenn Script vorhanden
- deploy-proxmox.yml: Compose-Pfade auf docker/docker-compose.yaml + `--env-file docker/.env`; Build/Test Schritte vereinheitlicht
- ci-main.yml: SSoT-Skripte per `if: hashFiles(...)` guarded, Compose-Lint Fallback; OpenAPI‑Pfad → backend/gateway; ADR‑Pfade → docs/adr/**; `staticAnalysis` in Build integriert
- youtrack-sync.yml: unverändert (funktional)

Refs: MP-22, MP-23

* fix(ci): create .env from example before validating compose config

* fix(ci): update ssot-guard filename (.yaml) and sync workflow state

* fixing

* fix(webpack): correct sql.js fallback configuration for webpack 5
This commit is contained in:
StefanMo
2025-12-03 12:03:40 +01:00
committed by GitHub
parent 034892e890
commit 95fe3e0573
365 changed files with 2283 additions and 15142 deletions
@@ -0,0 +1,59 @@
// Dieses Modul definiert die provider-agnostische API für den Event Store.
// Es enthält die Interfaces (z.B. `EventStore`, `EventSerializer`) und die
// Domänen-Events aus `core-domain`, die gespeichert und publiziert werden.
plugins {
alias(libs.plugins.kotlinJvm)
// Für bessere IDE-Unterstützung und Dokumentation
`java-library`
}
kotlin {
compilerOptions {
// Optimierungen für API-Module
freeCompilerArgs.addAll(
"-opt-in=kotlin.time.ExperimentalTime",
"-Xjvm-default=all"
)
}
}
java {
// Aktiviert die Erstellung von Source- und Javadoc-JARs für bessere API-Dokumentation
withSourcesJar()
withJavadocJar()
}
dependencies {
// === Core Dependencies ===
// Stellt sicher, dass alle Versionen aus der zentralen BOM kommen
implementation(platform(projects.platform.platformBom))
// Abhängigkeit zu den Core-Modulen, um auf Domänenobjekte (Events)
// und technische Hilfsklassen zugreifen zu können
api(projects.core.coreDomain)
implementation(projects.core.coreUtils)
// === Test Dependencies ===
// Stellt alle Test-Abhängigkeiten gebündelt bereit
testImplementation(projects.platform.platformTesting)
testImplementation(libs.bundles.testing.jvm)
// Für erweiterte Test-Unterstützung bei API-Tests
testImplementation(libs.kotlinx.coroutines.test)
}
// === Task Configuration ===
// Optimiert die Test-Ausführung
tasks.test {
useJUnitPlatform()
// Parallelisierung für bessere Performance
maxParallelForks = (Runtime.getRuntime().availableProcessors() / 2).coerceAtLeast(1)
}
// Konfiguration für bessere JAR-Erstellung bei API-Modulen
tasks.jar {
manifest {
attributes(
"Implementation-Title" to "Event Store API",
"Implementation-Version" to project.version,
"Automatic-Module-Name" to "at.mocode.infrastructure.eventstore.api"
)
}
}
@@ -0,0 +1,78 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.infrastructure.eventstore.api
import at.mocode.core.domain.event.DomainEvent
import kotlin.uuid.Uuid
/**
* Schnittstelle für die Serialisierung und Deserialisierung von Domain-Events.
*/
interface EventSerializer {
/**
* Serialisiert ein Domain-Event zu einer Map von Strings zu Strings.
* Dieses Format ist für die Speicherung in Redis Streams geeignet.
*
* @param event Das zu serialisierende Event
* @return Eine Map von Strings zu Strings, die das Event repräsentiert
*/
fun serialize(event: DomainEvent): Map<String, String>
/**
* Deserialisiert eine Map von Strings zu einem Domain-Event.
*
* @param data Die zu deserialisierende Map von Strings
* @return Das deserialisierte Domain-Event
*/
fun deserialize(data: Map<String, String>): DomainEvent
/**
* Ermittelt den Typ des Domain-Events.
* Dies wird verwendet, um den Typ des Events bei der Deserialisierung zu bestimmen.
*
* @param event Das Event, dessen Typ ermittelt werden soll
* @return Der Typ des Events als String
*/
fun getEventType(event: DomainEvent): String
/**
* Ermittelt den Typ des Domain-Events aus einer serialisierten Map.
*
* @param data Die serialisierten Event-Daten
* @return Der Typ des Events als String
*/
fun getEventType(data: Map<String, String>): String
/**
* Registriert eine Domain-Event-Klasse beim Serializer.
* Dies wird verwendet, um Event-Typen auf ihre entsprechenden Klassen abzubilden.
*
* @param eventClass Die Klasse des zu registrierenden Events
* @param eventType Der Typ des Events als String
*/
fun registerEventType(eventClass: Class<out DomainEvent>, eventType: String)
/**
* Ermittelt die Aggregat-ID aus einem serialisierten Event.
*
* @param data Die serialisierten Event-Daten
* @return Die Aggregat-ID
*/
fun getAggregateId(data: Map<String, String>): Uuid
/**
* Ermittelt die Event-ID aus einem serialisierten Event.
*
* @param data Die serialisierten Event-Daten
* @return Die Event-ID
*/
fun getEventId(data: Map<String, String>): Uuid
/**
* Ermittelt die Version aus einem serialisierten Event.
*
* @param data Die serialisierten Event-Daten
* @return Die Version
*/
fun getVersion(data: Map<String, String>): Long
}
@@ -0,0 +1,101 @@
@file:OptIn(kotlin.uuid.ExperimentalUuidApi::class)
package at.mocode.infrastructure.eventstore.api
import at.mocode.core.domain.event.DomainEvent
import kotlin.uuid.Uuid
/**
* Schnittstelle für einen Event Store, der Domain-Events persistiert.
*/
interface EventStore {
/**
* Fügt ein Event zum Event Store hinzu.
*
* @param event Das hinzuzufügende Event
* @param streamId Die ID des Event-Streams (normalerweise die Aggregat-ID)
* @param expectedVersion Die erwartete Version des Streams (für optimistische Nebenläufigkeitskontrolle)
* @return Die neue Version des Streams
* @throws ConcurrencyException wenn die erwartete Version nicht mit der tatsächlichen Version übereinstimmt
*/
fun appendToStream(event: DomainEvent, streamId: Uuid, expectedVersion: Long): Long
/**
* Fügt mehrere Events zum Event Store hinzu.
*
* @param events Die hinzuzufügenden Events
* @param streamId Die ID des Event-Streams (normalerweise die Aggregat-ID)
* @param expectedVersion Die erwartete Version des Streams (für optimistische Nebenläufigkeitskontrolle)
* @return Die neue Version des Streams
* @throws ConcurrencyException wenn die erwartete Version nicht mit der tatsächlichen Version übereinstimmt
*/
fun appendToStream(events: List<DomainEvent>, streamId: Uuid, expectedVersion: Long): Long
/**
* Liest Events aus einem Stream.
*
* @param streamId Die ID des Event-Streams, aus dem gelesen werden soll
* @param fromVersion Die Version, ab der gelesen werden soll (inklusive)
* @param toVersion Die Version, bis zu der gelesen werden soll (inklusive), oder null um alle Events zu lesen
* @return Die Events im Stream
*/
fun readFromStream(streamId: Uuid, fromVersion: Long = 0, toVersion: Long? = null): List<DomainEvent>
/**
* Liest alle Events aus allen Streams.
*
* @param fromPosition Die Position, ab der gelesen werden soll (inklusive)
* @param maxCount Die maximale Anzahl der zu lesenden Events, oder null um alle Events zu lesen
* @return Die Events in allen Streams
*/
fun readAllEvents(fromPosition: Long = 0, maxCount: Int? = null): List<DomainEvent>
/**
* Ermittelt die aktuelle Version eines Streams.
*
* @param streamId Die ID des Event-Streams
* @return Die aktuelle Version des Streams, oder -1 wenn der Stream nicht existiert
*/
fun getStreamVersion(streamId: Uuid): Long
/**
* Abonniert Events von einem spezifischen Stream.
*
* @param streamId Die ID des Event-Streams, der abonniert werden soll
* @param fromVersion Die Version, ab der abonniert werden soll (inklusive)
* @param handler Der Handler, der für jedes Event aufgerufen wird
* @return Ein Abonnement, das zum Abbestellen verwendet werden kann
*/
fun subscribeToStream(streamId: Uuid, fromVersion: Long = 0, handler: (DomainEvent) -> Unit): Subscription
/**
* Abonniert alle Events von allen Streams.
*
* @param fromPosition Die Position, ab der abonniert werden soll (inklusive)
* @param handler Der Handler, der für jedes Event aufgerufen wird
* @return Ein Abonnement, das zum Abbestellen verwendet werden kann
*/
fun subscribeToAll(fromPosition: Long = 0, handler: (DomainEvent) -> Unit): Subscription
}
/**
* Schnittstelle für ein Abonnement eines Event-Streams.
*/
interface Subscription {
/**
* Beendet das Abonnement des Event-Streams.
*/
fun unsubscribe()
/**
* Überprüft, ob das Abonnement aktiv ist.
*
* @return true wenn das Abonnement aktiv ist, false andernfalls
*/
fun isActive(): Boolean
}
/**
* Exception, die bei einem Nebenläufigkeitskonflikt im Event Store ausgelöst wird.
*/
class ConcurrencyException(message: String) : RuntimeException(message)