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:
@@ -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"
|
||||
)
|
||||
}
|
||||
}
|
||||
+78
@@ -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
|
||||
}
|
||||
+101
@@ -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)
|
||||
Reference in New Issue
Block a user