From f84a7a1a49c2d2a71881977e4764958b09e8e0c6 Mon Sep 17 00:00:00 2001 From: StefanMoCoAt Date: Fri, 15 Aug 2025 21:17:09 +0200 Subject: [PATCH] feature(guideline) --- .junie/guideline.md | 464 ------------- .junie/guidelines/master-guideline.md | 161 +++++ .junie/guidelines/trace-bullet-guideline.md | 64 ++ Guideline.md | 711 -------------------- 4 files changed, 225 insertions(+), 1175 deletions(-) delete mode 100644 .junie/guideline.md create mode 100644 .junie/guidelines/master-guideline.md create mode 100644 .junie/guidelines/trace-bullet-guideline.md delete mode 100644 Guideline.md diff --git a/.junie/guideline.md b/.junie/guideline.md deleted file mode 100644 index 21e5381e..00000000 --- a/.junie/guideline.md +++ /dev/null @@ -1,464 +0,0 @@ -# Meldestelle Development Guidelines - -**Version:** 1.0 -**Date:** 2025-08-15 -**Status:** Active - -This document outlines the development guidelines for the Meldestelle project, covering coding conventions, code organization, and testing approaches. - ---- - -## 1. Coding Conventions - -### 1.1 Language Standards - -- **Primary Language:** Kotlin (JVM/Multiplatform) -- **Java Compatibility:** Target Java 21+ -- **Kotlin Version:** Latest stable version -- **Code Style:** Official Kotlin coding conventions - -### 1.2 Naming Conventions - -#### Classes and Interfaces -```kotlin -// Use PascalCase for classes and interfaces -class MemberService -interface EventRepository -data class MemberRegistration -sealed class AuthResult - -// Use descriptive names that reflect domain concepts -class HorseRegistrationService // Good -class HRS // Avoid abbreviations -``` - -#### Functions and Variables -```kotlin -// Use camelCase for functions and variables -fun authenticateUser(): AuthResult -val memberRepository: MemberRepository -suspend fun findByEmail(email: EmailAddress): Result - -// Use descriptive test method names with "should" statements -@Test -fun `authenticate should return Success for valid credentials`() -``` - -#### Constants and Enums -```kotlin -// Use SCREAMING_SNAKE_CASE for constants -const val MAX_RETRY_ATTEMPTS = 3 -const val DEFAULT_TIMEOUT_MS = 5000L - -// Use PascalCase for enum values -enum class MemberStatus { - ACTIVE, - INACTIVE, - SUSPENDED -} -``` - -### 1.3 Code Structure Principles - -#### Result Pattern Usage -```kotlin -// Always use Result pattern for operations that can fail -interface MemberRepository { - suspend fun findById(id: MemberId): Result - suspend fun save(member: Member): Result -} - -// Result extensions for error handling -inline fun Result.mapError(transform: (E) -> R): Result = - when (this) { - is Result.Success -> Result.Success(value) - is Result.Failure -> Result.Failure(transform(error)) - } -``` - -#### Coroutines and Async Programming -```kotlin -// Use suspend functions for async operations -suspend fun processEventBatch(events: List): Result - -// Prefer structured concurrency -class EventProcessor { - private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - - suspend fun processEvents() = withContext(scope.coroutineContext) { - // Implementation - } -} -``` - -#### Documentation Standards -```kotlin -/** - * Authenticates a user with the given credentials. - * - * @param credentials The user credentials containing username and password - * @return AuthResult.Success with user data if authentication succeeds, - * AuthResult.Failure with error details if it fails - */ -suspend fun authenticate(credentials: UserCredentials): AuthResult -``` - ---- - -## 2. Code Organization and Package Structure - -### 2.1 Overall Architecture - -The project follows a **microservices architecture** with **Domain-Driven Design (DDD)** principles and **Clean Architecture** patterns. - -#### High-Level Structure -``` -Meldestelle/ -├── core/ # Shared kernel - fundamental building blocks -│ ├── core-domain/ # Common domain types and interfaces -│ └── core-utils/ # Shared utilities and extensions -├── infrastructure/ # Cross-cutting infrastructure services -│ ├── auth/ # Authentication & authorization -│ ├── messaging/ # Event messaging (Kafka) -│ ├── cache/ # Distributed caching (Redis) -│ ├── gateway/ # API Gateway -│ └── monitoring/ # Observability and monitoring -├── [domain-services]/ # Domain-specific microservices -│ ├── members/ # Member management -│ ├── events/ # Event management -│ ├── horses/ # Horse registry -│ └── masterdata/ # Master data management -├── client/ # Client applications -│ ├── common-ui/ # Shared UI components (KMP) -│ ├── desktop-app/ # Desktop application -│ └── web-app/ # Web application -└── platform/ # Build and dependency management -``` - -### 2.2 Microservice Structure (Clean Architecture) - -Each domain service follows a **4-layer architecture**: - -``` -domain-service/ -├── domain-api/ # REST controllers, DTOs, API contracts -├── domain-application/ # Use cases, application logic, orchestration -├── domain-domain/ # Domain models, business rules, interfaces -└── domain-infrastructure/ # Technical implementations (DB, external APIs) -``` - -#### Layer Responsibilities - -**`:domain-api` Layer:** -```kotlin -// REST Controllers -@RestController -@RequestMapping("/api/v1/members") -class MemberController(private val memberService: MemberService) - -// DTOs for external communication -data class MemberRegistrationRequest( - val firstName: String, - val lastName: String, - val email: String -) -``` - -**`:domain-application` Layer:** -```kotlin -// Use cases and application services -class MemberApplicationService( - private val memberRepository: MemberRepository, - private val eventPublisher: EventPublisher -) { - suspend fun registerMember(command: RegisterMemberCommand): Result -} -``` - -**`:domain-domain` Layer:** -```kotlin -// Domain models and business logic -data class Member( - val id: MemberId, - val personalInfo: PersonalInfo, - val membershipStatus: MembershipStatus -) { - fun activate(): Member = copy(membershipStatus = MembershipStatus.ACTIVE) -} - -// Repository interfaces (implemented in infrastructure) -interface MemberRepository { - suspend fun findById(id: MemberId): Result - suspend fun save(member: Member): Result -} -``` - -**`:domain-infrastructure` Layer:** -```kotlin -// Technical implementations -class ExposedMemberRepository( - private val database: Database -) : MemberRepository { - override suspend fun findById(id: MemberId): Result { - // Database implementation using Exposed ORM - } -} -``` - -### 2.3 Package Naming Conventions - -```kotlin -// Base package structure -at.mocode.[layer].[domain].[component] - -// Examples -at.mocode.members.domain.model // Domain models -at.mocode.members.application.service // Application services -at.mocode.members.infrastructure.persistence // Persistence layer -at.mocode.infrastructure.messaging.kafka // Infrastructure components -at.mocode.core.utils.result // Core utilities -``` - -### 2.4 Dependency Rules - -- **Core modules** must not depend on any other modules -- **Domain layer** must not depend on infrastructure or application layers -- **Application layer** can depend on domain layer only -- **Infrastructure layer** can depend on domain and application layers -- **API layer** orchestrates calls between application and infrastructure - ---- - -## 3. Unit and Integration Testing Approaches - -### 3.1 Testing Strategy Overview - -The project follows a **comprehensive testing strategy** with multiple testing levels: - -1. **Unit Tests** - Fast, isolated tests for individual components -2. **Integration Tests** - Tests for component interactions -3. **Performance Tests** - Load and throughput testing -4. **End-to-End Tests** - Full system workflow testing - -### 3.2 Testing Stack - -#### Core Testing Libraries -```kotlin -// Unit testing -testImplementation("org.junit.jupiter:junit-jupiter:5.10.0") -testImplementation("io.mockk:mockk:1.13.8") -testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") - -// Integration testing -testImplementation("org.testcontainers:junit-jupiter:1.19.1") -testImplementation("org.testcontainers:kafka:1.19.1") -testImplementation("org.testcontainers:postgresql:1.19.1") - -// Performance testing -testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3") -``` - -### 3.3 Unit Testing Conventions - -#### Test Structure and Naming -```kotlin -class AuthenticationServiceTest { - - @BeforeEach - fun setUp() { - // Test setup - } - - @Test - fun `authenticate should return Success for valid credentials`() = runTest { - // Given - val credentials = UserCredentials("user@example.com", "validPassword") - coEvery { userRepository.findByEmail(any()) } returns Result.Success(testUser) - - // When - val result = authenticationService.authenticate(credentials) - - // Then - assertTrue(result is AuthResult.Success) - assertEquals(testUser.id, result.user.id) - } - - @Test - fun `authenticate should return Failure for invalid credentials`() = runTest { - // Given - When - Then pattern - } -} -``` - -#### Mocking Best Practices -```kotlin -class MemberServiceTest { - private val memberRepository = mockk() - private val eventPublisher = mockk() - private val memberService = MemberService(memberRepository, eventPublisher) - - @Test - fun `should publish event when member is registered`() = runTest { - // Mock repository responses - coEvery { memberRepository.save(any()) } returns Result.Success(Unit) - coEvery { eventPublisher.publish(any()) } returns Result.Success(Unit) - - // Test implementation - val result = memberService.registerMember(validCommand) - - // Verify interactions - coVerify { eventPublisher.publish(any()) } - } -} -``` - -### 3.4 Integration Testing Approaches - -#### Database Integration Tests -```kotlin -@Testcontainers -class MemberRepositoryIntegrationTest { - - companion object { - @Container - val postgres = PostgreSQLContainer("postgres:15-alpine") - } - - @Test - fun `should persist and retrieve member correctly`() = runTest { - // Test with real database using Testcontainers - val member = createTestMember() - - val saveResult = memberRepository.save(member) - assertTrue(saveResult.isSuccess()) - - val retrievedResult = memberRepository.findById(member.id) - assertTrue(retrievedResult.isSuccess()) - assertEquals(member, retrievedResult.getOrNull()) - } -} -``` - -#### Messaging Integration Tests -```kotlin -@Testcontainers -class KafkaEventPublisherIntegrationTest { - - companion object { - @Container - val kafka = KafkaContainer(DockerImageName.parse("confluentinc/cp-kafka:latest")) - } - - @Test - fun `should publish and consume events correctly`() = runTest { - val event = MemberRegisteredEvent(memberId = MemberId.generate()) - - val publishResult = eventPublisher.publish(event) - assertTrue(publishResult.isSuccess()) - - // Verify event was consumed - val consumedEvents = eventConsumer.consumeEvents(timeout = 5.seconds) - assertTrue(consumedEvents.any { it.memberId == event.memberId }) - } -} -``` - -### 3.5 Performance Testing - -#### Batch Processing Performance Tests -```kotlin -class KafkaBatchPerformanceTest { - - @Test - fun `should process large batches within acceptable time limits`() = runTest { - val batchSize = 1000 - val events = generateTestEvents(batchSize) - val startTime = System.currentTimeMillis() - - val results = eventProcessor.processBatch(events) - val processingTime = System.currentTimeMillis() - startTime - - assertTrue(results.all { it.isSuccess() }) - assertTrue(processingTime < 5000) // Should complete within 5 seconds - - println("[DEBUG_LOG] Processed $batchSize events in ${processingTime}ms") - } -} -``` - -### 3.6 Test Organization - -#### Directory Structure -``` -src/ -├── main/kotlin/ # Production code -└── test/kotlin/ # Test code - ├── unit/ # Unit tests (optional sub-organization) - ├── integration/ # Integration tests - └── performance/ # Performance tests -``` - -#### Test Categories and Execution -```kotlin -// Use JUnit 5 tags for test categorization -@Tag("unit") -class MemberServiceTest - -@Tag("integration") -class MemberRepositoryIntegrationTest - -@Tag("performance") -class KafkaBatchPerformanceTest -``` - -### 3.7 Testing Guidelines - -#### Best Practices -1. **Test Method Naming:** Use descriptive names with "should" statements -2. **AAA Pattern:** Arrange, Act, Assert structure -3. **One Assertion Per Test:** Focus on single behavior -4. **Test Data Builders:** Use factory methods for test data creation -5. **Coroutine Testing:** Use `runTest` for suspend functions -6. **Mock Verification:** Verify important interactions, not implementation details - -#### Coverage Goals -- **Unit Tests:** 80%+ code coverage for domain and application layers -- **Integration Tests:** Cover all repository implementations and external integrations -- **Performance Tests:** Cover critical batch operations and high-load scenarios - -#### Debugging Support -```kotlin -// Always prefix debug messages with [DEBUG_LOG] -@Test -fun `should handle concurrent requests`() = runTest { - println("[DEBUG_LOG] Starting concurrent request test with ${requestCount} requests") - - // Test implementation - - println("[DEBUG_LOG] Completed test. Success rate: ${successCount}/${requestCount}") -} -``` - ---- - -## 4. Additional Development Standards - -### 4.1 Error Handling -- Use `Result` pattern consistently for operations that can fail -- Define domain-specific error types -- Avoid throwing exceptions in domain logic - -### 4.2 Logging and Monitoring -- Use structured logging with appropriate log levels -- Include correlation IDs for request tracing -- Monitor key business metrics and technical performance - -### 4.3 Security Considerations -- Validate all external inputs -- Use JWT tokens for authentication -- Implement proper authorization checks -- Secure sensitive configuration data - ---- - -This guideline is a living document and should be updated as the project evolves and new patterns emerge. diff --git a/.junie/guidelines/master-guideline.md b/.junie/guidelines/master-guideline.md new file mode 100644 index 00000000..13e70f3c --- /dev/null +++ b/.junie/guidelines/master-guideline.md @@ -0,0 +1,161 @@ +# Meldestelle_Pro: Entwicklungs-Guideline + +**Status:** Finalisiert & Verbindlich +**Version:** 1.0 +**Stand:** 15. August 2025 + +## 1. Vision & Architektonische Grundpfeiler + +Dieses Dokument definiert die verbindlichen technischen Richtlinien und Qualitätsstandards für das Projekt " +Meldestelle_Pro". Ziel ist die Schaffung einer modernen, skalierbaren und wartbaren Plattform für den Pferdesport. + +Unsere Architektur basiert auf **vier Säulen**: + +1. **Modularität & Skalierbarkeit** durch eine **Microservices-Architektur** +2. **Fachlichkeit im Code** durch **Domain-Driven Design (DDD)** +3. **Entkopplung & Resilienz** durch eine **ereignisgesteuerte Architektur (EDA)** +4. **Effizienz & Konsistenz** durch eine **Multiplattform-Client-Strategie (KMP)** + +> **Grundsatz:** Jede Code-Änderung muss diese vier Grundprinzipien respektieren. + +--- + +## 2. Coding Conventions & Code-Qualität + +### 2.1. Sprach- und Stilstandards + +* **Primärsprache:** Kotlin (JVM/Multiplatform) +* **Java-Kompatibilität:** Ziel ist Java 21+ +* **Code-Stil:** Offizielle Kotlin Coding Conventions, durch `Detekt` geprüft. + +### 2.2. Namenskonventionen + +* **Klassen & Interfaces:** `PascalCase` (z.B. `MemberService`, `EventRepository`) +* **Funktionen & Variablen:** `camelCase` (z.B. `authenticateUser`, `memberRepository`) +* **Testmethoden:** Beschreibend mit Backticks (z.B. `` `should return Success for valid credentials` ``) +* **Konstanten:** `SCREAMING_SNAKE_CASE` (z.B. `MAX_RETRY_ATTEMPTS`) +* **Enums:** `PascalCase` für Werte (z.B. `MemberStatus.ACTIVE`) + +### 2.3. Value Classes für Typsicherheit + +Primitive Typen (UUID, String, Long) für IDs oder spezifische Werte müssen in typsichere `value class`-Wrapper gekapselt +werden. + +```kotlin + @JvmInline +value class MemberId(val value: UUID) { + companion object { + fun of(value: String): Result = + runCatching { UUID.fromString(value) } + .map { MemberId(it) } + .mapError { ValidationError.INVALID_UUID } + } +} +``` + +### 2.4. Error-Handling & Logging + +* **`Result`-Pattern:** Für erwartbare Geschäftsfehler ist das `Result`-Pattern zu verwenden. Exceptions sind für + unerwartete, technische Fehler reserviert. + +* **Fehler-Hierarchie:** Wir verwenden eine `sealed class`-Hierarchie, um Fehlerarten klar zu kategorisieren ( + `DomainError`, `ValidationError`, `BusinessError`, `TechnicalError`). + +* **Structured Logging:** Logs müssen strukturiert sein und eine Korrelations-ID enthalten, um Anfragen über + Service-Grenzen hinweg zu verfolgen. + +```kotlin + logger.info { + "Creating member" with mapOf( + "memberId" to command.memberId.value, + "correlationId" to MDC.get("correlationId") + ) +} +``` + +--- + +## 3. Backend-Entwicklungsrichtlinien + +### 3.1. Microservice-Struktur (Clean Architecture) + +Jeder fachliche Microservice folgt der 4-Layer-Struktur (`api`, `application`, `domain`, `infrastructure`). + +### 3.2. Repository-Pattern + +Jede Repository-Methode muss das `Result`-Pattern verwenden. + +```kotlin + interface MemberRepository { + suspend fun findById(id: MemberId): Result + suspend fun save(member: Member): Result +} +``` + +### 3.3. Messaging & Event-Naming + +* **Event-Naming Convention:** Domänen-Events folgen dem Muster `{Domain}{Entity}{Action}Event`. + +```kotlin + data class MemberPersonalDataUpdatedEvent(...) : DomainEvent(...) +``` + +--- + +## 4. Frontend-Entwicklungsrichtlinien + +Das Frontend folgt konsequent dem **Model-View-ViewModel (MVVM)**-Muster und der **Kotlin Multiplatform (KMP)** +-Strategie. Der UI-Code wird nach **fachlichen Features** (vertikale Schnitte) strukturiert. + +--- + +## 5. Testing + +Tests sind ein integraler Bestandteil jedes Features und müssen einen hohen Standard erfüllen. + +### 5.1. Test-Pyramide & Werkzeuge + +* **Unit-Tests (80 %+ Abdeckung):** Für Domänen- und Anwendungslogik (JUnit 5, MockK). + +* **Integrationstests:** Decken alle Repository-Implementierungen und externen Integrationen ab. + +* **Testcontainers als Goldstandard:** Jede Interaktion mit externer Infrastruktur (DB, Cache, Broker) **muss** mit + **Testcontainers** getestet werden. + +### 5.2. Debugging + +Debug-Ausgaben im Test-Code müssen mit `[DEBUG_LOG]` beginnen, um sie leicht identifizieren und filtern zu können. + +--- + +## 6. Infrastruktur- & Betriebs-Spezifikationen + +### 6.1. Kafka-Konfiguration + +Die Konfiguration muss auf maximale Zuverlässigkeit ausgelegt sein: + +```yaml + # in application.yml + kafka: + producer: + acks: all + enable-idempotence: true + max-in-flight-requests-per-connection: 1 + consumer: + group-id-prefix: "meldestelle-${spring.application.name}" + auto-offset-reset: earliest + enable-auto-commit: false +``` + +### 6.2. Datenbank-Migrationen (Flyway) + +Migrations-Skripte müssen einer klaren Namenskonvention folgen. + +* **Pattern:** `V{version}__{description}.sql` (z.B., `V001__Create_member_tables.sql`) + +* **Repeatable:** `R__{description}.sql` (z.B., `R__Update_member_view.sql`) + +### 6.3. API-Dokumentation (OpenAPI) + +Alle öffentlichen REST-Endpunkte müssen mit OpenAPI-Annotationen (`@Operation`, `@ApiResponse`) dokumentiert werden, um +eine klare und interaktive API-Dokumentation zu generieren. diff --git a/.junie/guidelines/trace-bullet-guideline.md b/.junie/guidelines/trace-bullet-guideline.md new file mode 100644 index 00000000..5f4c3e23 --- /dev/null +++ b/.junie/guidelines/trace-bullet-guideline.md @@ -0,0 +1,64 @@ +# Guideline: Zyklus "Tracer Bullet" + +* **Zyklus-Start:** 15. August 2025 +* **Status:** In Arbeit +* **Basis:** Diese Guideline erweitert die [Master-Guideline](./master-guideline.md) + +## 1. Ziel des Zyklus + +Das oberste und einzige Ziel dieses Entwicklungszyklus ist die **Validierung der gesamten technischen Architektur +End-to-End**. Wir wollen beweisen, dass eine Anfrage vom Client den gesamten technischen Stack (Gateway, Service +Discovery, Backend-Service) erfolgreich durchlaufen und eine Antwort zurückliefern kann. + +Am Ende dieses Zyklus werden wir einen stabilen, qualitätsgesicherten und dokumentierten Unterbau haben, auf dem die +Entwicklung der fachlichen Features aufsetzen kann. + +## 2. Umfang (Was gehört zu diesem Zyklus?) + +Die folgenden Module und Aufgaben sind Teil dieses Zyklus: + +* **Backend-Infrastruktur (`:core` & `:infrastructure`):** + * Vollständige Überarbeitung, Optimierung und Testabdeckung aller Infrastruktur-Module (`cache`, `event-store`, + `auth`, `messaging`, `monitoring`, `gateway`). + * Implementierung einer robusten Logging- und Konfigurations-Infrastruktur. +* **Temporärer Test-Service (`:temp:ping-service`):** + * Erstellung eines minimalen Spring-Boot-Service, der nur einen `GET /ping`-Endpunkt bereitstellt. +* **Frontend-Infrastruktur (`:client`):** + * Aufbau einer sauberen, leeren Grundstruktur für die Kotlin Multiplatform App nach dem MVVM-Muster. + * Implementierung einer minimalen UI mit einem "Ping"-Button und einem Anzeigefeld für die Antwort. + +## 3. Spezifische Richtlinien für diesen Zyklus + +* **Fokus auf Technik, nicht Fachlichkeit:** Jede Zeile Code, die in diesem Zyklus geschrieben wird, dient + ausschließlich der Stabilisierung der technischen Infrastruktur. Es wird keine komplexe Geschäftslogik implementiert. +* **Qualitätsstandards gelten uneingeschränkt:** Auch für diesen technischen Zyklus gelten alle Regeln der + Master-Guideline. Insbesondere: + * **Tests sind Pflicht:** Jede neue oder geänderte Komponente muss durch Tests (insbesondere **Testcontainers** für + Infrastruktur) abgesichert werden. + * **Kein `println`:** Es wird ausschließlich der strukturierte Logger verwendet. +* **Dokumentation ist Teil der Aufgabe:** Jedes Modul, das wir überarbeiten, wird mit einer aktualisierten und präzisen + `README.md`-Datei abgeschlossen. + +## 4. Definition of Done (Wann sind wir fertig?) + +Dieser Zyklus ist abgeschlossen, wenn **alle** der folgenden Kriterien erfüllt sind: + +* [ ] Alle `:core` und `:infrastructure`-Module wurden überarbeitet, sind fehlerfrei testbar und ihre `README.md` + -Dateien sind auf dem neuesten Stand. +* [ ] Der `:temp:ping-service` ist implementiert, getestet und lauffähig. +* [ ] Die `:client:web-app` ist mit einer sauberen MVVM-Struktur aufgesetzt und startet fehlerfrei. +* [ ] **Der End-to-End "Tracer Bullet"-Test ist erfolgreich:** + * [ ] Alle Docker-Container (`docker-compose up`) starten. + * [ ] Der `gateway`-Service startet. + * [ ] Der `ping-service` startet und registriert sich erfolgreich bei Consul. + * [ ] Die `web-app` startet. + * [ ] Ein Klick auf den "Ping"-Button in der Web-App führt zu einer `GET`-Anfrage an das Gateway, wird korrekt an + den `ping-service` weitergeleitet und die Antwort `"pong"` wird erfolgreich in der UI angezeigt. +* [ ] Der gesamte `clean build` des Projekts läuft ohne Fehler und **ohne Warnungen**. +* [ ] Die `master-guideline.md` und die `trace-bullet-guideline.md` sind finalisiert. + +## 5. Lessons Learned (nach Abschluss) + +- [ ] Was hat gut funktioniert? +- [ ] Was würden wir beim nächsten Zyklus anders machen? +- [ ] Welche Standards müssen in die Master-Guideline übernommen werden? diff --git a/Guideline.md b/Guideline.md deleted file mode 100644 index 7d4467eb..00000000 --- a/Guideline.md +++ /dev/null @@ -1,711 +0,0 @@ -# Meldestelle_Pro: Entwicklungs-Guideline - -**Status:** Finalisiert & Verbindlich -**Version:** 2.0 -**Stand:** August 2025 - -## 1. Vision & Architektonische Grundpfeiler - -Dieses Dokument definiert die verbindlichen technischen Richtlinien und Qualitätsstandards für das Projekt "Meldestelle_Pro". Ziel ist die Schaffung einer modernen, skalierbaren und wartbaren Plattform für den Pferdesport. - -Unsere Architektur basiert auf **vier Säulen**: - -1. **Modularität & Skalierbarkeit** durch eine **Microservices-Architektur** -2. **Fachlichkeit im Code** durch **Domain-Driven Design (DDD)** -3. **Entkopplung & Resilienz** durch eine **ereignisgesteuerte Architektur (EDA)** -4. **Effizienz & Konsistenz** durch eine **Multiplattform-Client-Strategie (KMP)** - -> **Grundsatz:** Jede Code-Änderung muss diese vier Grundprinzipien respektieren. - ---- - -## 2. Backend-Entwicklungsrichtlinien - -#### 2.1. Microservice-Struktur (Clean Architecture) - -**Jeder fachliche Microservice (z.B. :members, :events) muss der etablierten 4-Layer-Struktur folgen:** - -* **`:*-api`**: Definiert die öffentliche Schnittstelle des Service (REST-Controller, DTOs). -* **`:*-application`**: Enthält die Anwendungslogik und Use Cases. Hier werden die Repositories orchestriert. -* **`:*-domain`**: Das Herz des Service. Enthält die reinen, von Frameworks unabhängigen Domänenmodelle, Geschäftsregeln - und Repository-Interfaces. -* **`:*-infrastructure`**: Die technische Implementierung der Interfaces aus der Domänenschicht (z.B. Datenbankzugriff - mit Exposed). - -#### 2.2. Domain-Driven Design (DDD) in der Praxis - -* **Shared Kernel (`:core`-Modul):** Das `:core`-Modul ist heilig. Es darf **ausschließlich** fundamentalen, - domänen-agnostischen Code enthalten. Fachspezifische Konzepte gehören in ihre jeweilige Domäne. -* **Repository-Pattern mit `Result`:** Jede Repository-Methode muss das `Result`-Pattern verwenden, um Erfolgs- und - Fehlerfälle explizit und typsicher zu behandeln. - - ```kotlin - // Repository mit Result-Pattern - interface MemberRepository { - suspend fun findById(id: MemberId): Result - suspend fun save(member: Member): Result - suspend fun findByEmail(email: EmailAddress): Result, RepositoryError> - } - ``` - -#### 2.3. Core-Modul Spezifikation - -Das `:core`-Modul definiert die fundamentalen Bausteine der gesamten Anwendung: - -* **Result Extensions:** Utility-Funktionen für typsichere Fehlerbehandlung -* **Common Types:** Basistypen für alle Domänen -* **Shared Utilities:** Plattformunabhängige Hilfsfunktionen - - ```kotlin - // Result Extensions im core-utils Modul - inline fun Result.mapError(transform: (E) -> R): Result = - when (this) { - is Result.Success -> Result.Success(value) - is Result.Failure -> Result.Failure(transform(error)) - } - - inline fun Result.onFailure(action: (E) -> Unit): Result = - also { if (it is Result.Failure) action(it.error) } - - // Common Domain Types - @JvmInline - value class CorrelationId(val value: UUID) { - companion object { - fun generate(): CorrelationId = CorrelationId(UUID.randomUUID()) - fun of(value: String): Result = - runCatching { UUID.fromString(value) } - .map { CorrelationId(it) } - .mapError { ValidationError.InvalidUUID("Invalid correlation ID: $value") } - } - } - - // Konkrete Error-Implementierungen - sealed class ValidationError(code: String, message: String) : DomainError(code, message) { - data class InvalidUUID(override val message: String) : - ValidationError("INVALID_UUID", message) - data class InvalidEmail(override val message: String) : - ValidationError("INVALID_EMAIL", message) - data class InvalidLength(val field: String, val min: Int, val max: Int) : - ValidationError("INVALID_LENGTH", "Field $field must be between $min and $max characters") - } - ``` - -#### 2.4. Messaging & Event-Naming - -* **Asynchrone Kommunikation:** Die bevorzugte Kommunikationsmethode ist asynchron über Kafka. -* **Event-Naming Convention:** Domänen-Events folgen dem Muster `{Domain}{Entity}{Action}Event`. - - ```kotlin - // Event-Naming Convention - sealed class DomainEvent( - val aggregateId: String, - val version: Long, - val timestamp: Instant = Instant.now() - ) { - // Pattern: {Domain}{Entity}{Action}Event - data class MemberPersonalDataUpdatedEvent( - val memberId: MemberId, - val personalData: PersonalData - ) : DomainEvent(memberId.value, version) - } - ``` - ---- - -## 3. Frontend-Entwicklungsrichtlinien - -#### 3.1. Architekturmuster: MVVM & KMP - -Das Frontend folgt konsequent dem **Model-View-ViewModel (MVVM)**-Muster und der **Kotlin Multiplatform (KMP)**-Strategie: - -* **Model & ViewModel:** Die gesamte Geschäftslogik, der Zustand und die API-Aufrufe leben im `:client:common-ui`-Modul und sind plattformunabhängig. -* **View:** Die Benutzeroberfläche wird mit **Compose Multiplatform** im `:client:common-ui`-Modul implementiert. - -#### 3.2. State Management - -**Unidirectional Data Flow mit MVI-Pattern:** - -```kotlin -// State Management Pattern -@Stable -data class MemberListUiState( - val members: List = emptyList(), - val isLoading: Boolean = false, - val error: String? = null, - val searchQuery: String = "" -) - -sealed class MemberListIntent { - object LoadMembers : MemberListIntent() - data class SearchMembers(val query: String) : MemberListIntent() - data class DeleteMember(val memberId: MemberId) : MemberListIntent() -} - -class MemberListViewModel( - private val memberRepository: MemberRepository -) : ViewModel() { - private val _uiState = MutableStateFlow(MemberListUiState()) - val uiState: StateFlow = _uiState.asStateFlow() - - fun handleIntent(intent: MemberListIntent) { - when (intent) { - is MemberListIntent.LoadMembers -> loadMembers() - is MemberListIntent.SearchMembers -> searchMembers(intent.query) - is MemberListIntent.DeleteMember -> deleteMember(intent.memberId) - } - } -} -``` - -#### 3.3. Navigation Architecture - -**Compose Navigation mit typsicheren Routes:** - -```kotlin -// Navigation Definition -@Serializable -sealed class Screen { - @Serializable - object MemberList : Screen() - - @Serializable - data class MemberDetail(val memberId: String) : Screen() - - @Serializable - data class EventRegistration(val eventId: String, val memberId: String) : Screen() -} - -// Navigation Router -class NavigationRouter { - private val _navigationEvents = MutableSharedFlow() - val navigationEvents: SharedFlow = _navigationEvents.asSharedFlow() - - fun navigateTo(screen: Screen) { - _navigationEvents.tryEmit(NavigationEvent.NavigateTo(screen)) - } - - fun navigateBack() { - _navigationEvents.tryEmit(NavigationEvent.NavigateBack) - } -} -``` - -#### 3.4. Vertikale Schnitte (Features) - -Der UI-Code wird nach **fachlichen Features** strukturiert. Ein Feature (z.B. "Nennungsabwicklung") hat sein eigenes Verzeichnis und enthält alle zugehörigen Views, ViewModels und Models: - -``` -client/common-ui/src/commonMain/kotlin/ -├── features/ -│ ├── members/ -│ │ ├── presentation/ -│ │ │ ├── MemberListViewModel.kt -│ │ │ ├── MemberDetailViewModel.kt -│ │ │ └── MemberUiState.kt -│ │ ├── ui/ -│ │ │ ├── MemberListScreen.kt -│ │ │ ├── MemberDetailScreen.kt -│ │ │ └── components/ -│ │ └── domain/ -│ │ └── MemberUseCases.kt -│ └── events/ -│ ├── presentation/ -│ ├── ui/ -│ └── domain/ -``` - -#### 3.5. Platform-spezifische Implementierungen - -**Desktop-spezifische Features:** - -```kotlin -// Desktop-specific implementations -actual class PlatformFileManager { - actual suspend fun selectFile(): Result { - return withContext(Dispatchers.IO) { - try { - val fileChooser = JFileChooser() - val result = fileChooser.showOpenDialog(null) - if (result == JFileChooser.APPROVE_OPTION) { - Result.Success(fileChooser.selectedFile) - } else { - Result.Success(null) - } - } catch (e: Exception) { - Result.Failure(FileError.SelectionFailed(e.message)) - } - } - } -} - -// Web-specific implementations -actual class PlatformFileManager { - actual suspend fun selectFile(): Result { - return try { - val input = document.createElement("input") as HTMLInputElement - input.type = "file" - input.click() - // Implementation für Web File API - Result.Success(null) // Simplified - } catch (e: Exception) { - Result.Failure(FileError.SelectionFailed(e.message)) - } - } -} -``` - ---- - -## 4. API-Versioning & Kompatibilität - -#### 4.1. Versioning-Strategie - -**Header-basierte Versionierung (Empfohlen):** - -```kotlin -// API Version Header -@RestController -@RequestMapping("/api/members") -class MemberController { - - @GetMapping - fun getMembers( - @RequestHeader(value = "API-Version", defaultValue = "1.0") version: String, - @RequestParam query: String? - ): ResponseEntity> { - return when (version) { - "1.0" -> memberService.getMembersV1(query) - "2.0" -> memberService.getMembersV2(query) - else -> ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).build() - } - } -} - -// Client-seitige Versionierung -class ApiClient { - companion object { - const val CURRENT_API_VERSION = "2.0" - const val MIN_SUPPORTED_VERSION = "1.0" - } - - private val defaultHeaders = mapOf( - "API-Version" to CURRENT_API_VERSION, - "Accept" to "application/json" - ) -} -``` - -#### 4.2. Backward Compatibility Rules - -* **Breaking Changes:** Erfordern eine neue Major-Version (1.x → 2.x) -* **Additive Changes:** Können in Minor-Versionen erfolgen (1.0 → 1.1) -* **Bug Fixes:** Patch-Versionen (1.0.0 → 1.0.1) - -```kotlin -// Compatibility Matrix -object ApiCompatibility { - val supportedVersions = mapOf( - "2.0" to ApiVersionConfig( - deprecated = false, - sunsetDate = null, - features = setOf("advanced-search", "bulk-operations") - ), - "1.0" to ApiVersionConfig( - deprecated = true, - sunsetDate = LocalDate.of(2025, 12, 31), - features = setOf("basic-search") - ) - ) -} -``` - -#### 4.3. Versioning Lifecycle Management - -* **Deprecation Notice:** Mindestens 6 Monate vor Entfernung -* **Documentation:** Alle Versionen müssen in OpenAPI dokumentiert sein -* **Migration Guide:** Für jede Major-Version erforderlich - ---- - -## 5. Allgemeine Qualitätsstandards - -#### 4.1. Code-Qualität & Kotlin-Konventionen - -* **Value Classes für Typsicherheit:** Primitive Typen (UUID, String, Long) für IDs oder spezifische Werte müssen in - typsichere `value class`-Wrapper gekapselt werden, um Fehler zu vermeiden. - - ```kotlin - // Ergänzung für Value Objects - @JvmInline - value class MemberId(val value: UUID) { - companion object { - fun of(value: String): Result = - runCatching { UUID.fromString(value) } - .map { MemberId(it) } - .mapError { ValidationError.INVALID_UUID } - } - } - ``` - -#### 4.2. Error-Handling - -* **`Result`-Pattern statt Exceptions:** Für erwartbare Geschäftsfehler ist das `Result`-Pattern zu verwenden. -* **Spezifische Fehler-Hierarchie:** Wir verwenden eine `sealed class`-Hierarchie, um Fehlerarten klar zu - kategorisieren. - - ```kotlin - // Spezifische Error-Hierarchie definieren - sealed class DomainError(val code: String, val message: String) - sealed class ValidationError(code: String, message: String) : DomainError(code, message) - sealed class BusinessError(code: String, message: String) : DomainError(code, message) - sealed class TechnicalError(code: String, message: String) : DomainError(code, message) - ``` - -#### 4.3. Testing - -* **Testcontainers als Goldstandard:** Jede Interaktion mit externer Infrastruktur (DB, Cache, Broker) **muss** mit * - *Testcontainers** getestet werden. -* **Mocking für Isolation:** Abhängigkeiten innerhalb von Tests werden mit Mocking-Frameworks (z.B. MockK) isoliert, um - den Testfokus zu schärfen. - - ```kotlin - // Testcontainers-Pattern für Infrastruktur-Tests - @TestConfiguration - class KafkaTestConfig { - @Bean - @Primary - fun kafkaEventPublisher(): KafkaEventPublisher = mockk() - } - ``` - ---- - -### 5. Infrastruktur-Spezifikationen - -#### 5.1. Kafka-Konfiguration - -Die Konfiguration für Producer und Consumer muss produktionsreife Einstellungen für Zuverlässigkeit und Datenkonsistenz -verwenden. - - ```YAML - # Ergänzung für application.yml - kafka: - producer: - acks: all - enable-idempotence: true - max-in-flight-requests-per-connection: 1 - consumer: - group-id-prefix: "meldestelle-${spring.application.name}" - auto-offset-reset: earliest - enable-auto-commit: false - ``` - -#### 5.2. Datenbank-Migrationen mit Flyway - -Migrations-Skripte müssen einer klaren Namenskonvention folgen. - -* **Pattern:**`V{version}__{description}.sql` (z.B., `V001__Create_member_tables.sql`) - -* **Repeatable:**`R__{description}.sql` (z.B., `R__Update_member_view.sql`) - ---- - -## 6. Monitoring & Observability - -#### 6.1. Structured Logging - -Logs müssen als strukturierte Daten (z.B. JSON) ausgegeben werden und immer eine Korrelations-ID enthalten, um Anfragen über Service-Grenzen hinweg verfolgen zu können. - -```kotlin -// Korrigierte Logging-Syntax -@Component -class MemberService { - private val logger = KotlinLogging.logger {} - - suspend fun createMember(command: CreateMemberCommand) { - logger.info { - mapOf( - "message" to "Creating member", - "memberId" to command.memberId.value, - "operation" to "create_member", - "correlationId" to MDC.get("correlationId") - ).toString() - } - } -} -``` - -#### 6.2. Service Level Indicators (SLIs) & Objectives (SLOs) - -**Definierte SLIs für alle Services:** - -```kotlin -// SLI/SLO Definitionen -object ServiceLevelIndicators { - - // Availability SLIs - data class AvailabilitySLI( - val serviceName: String, - val targetUptime: Double = 0.995, // 99.5% - val measurementWindow: Duration = Duration.ofDays(30) - ) - - // Latency SLIs - data class LatencySLI( - val serviceName: String, - val percentile: Double = 0.95, // P95 - val targetLatency: Duration = Duration.ofMillis(500), - val measurementWindow: Duration = Duration.ofMinutes(5) - ) - - // Error Rate SLIs - data class ErrorRateSLI( - val serviceName: String, - val maxErrorRate: Double = 0.001, // 0.1% - val measurementWindow: Duration = Duration.ofMinutes(5) - ) -} - -// SLO Monitoring -@Component -class SLOMonitor(private val meterRegistry: MeterRegistry) { - - private val requestDuration = Timer.builder("http.request.duration") - .description("HTTP request duration") - .register(meterRegistry) - - private val errorRate = Counter.builder("http.request.errors") - .description("HTTP request errors") - .register(meterRegistry) - - fun recordRequest(duration: Duration, isError: Boolean) { - requestDuration.record(duration) - if (isError) errorRate.increment() - } -} -``` - -#### 6.3. Business & Technical Metrics - -**Umfassende Metriken-Strategie:** - -```kotlin -// Business Metrics -@Component -class BusinessMetrics(meterRegistry: MeterRegistry) { - - // Fachliche Metriken - private val memberRegistrations = Counter.builder("business.member.registrations.total") - .description("Total number of member registrations") - .tag("service", "members") - .register(meterRegistry) - - private val eventParticipations = Counter.builder("business.event.participations.total") - .description("Total event participations") - .tag("service", "events") - .register(meterRegistry) - - private val paymentTransactions = Timer.builder("business.payment.transaction.duration") - .description("Payment transaction processing time") - .tag("service", "payments") - .register(meterRegistry) - - // Gauge für aktuelle Werte - private val activeSessions = Gauge.builder("business.active.sessions") - .description("Currently active user sessions") - .register(meterRegistry) { getActiveSessionCount() } -} - -// Technical Metrics -@Component -class TechnicalMetrics(meterRegistry: MeterRegistry) { - - // Database Metriken - private val dbConnectionPool = Gauge.builder("database.connection.pool.active") - .description("Active database connections") - .register(meterRegistry) { getActiveConnections() } - - // Kafka Metriken - private val kafkaLag = Gauge.builder("kafka.consumer.lag") - .description("Kafka consumer lag") - .register(meterRegistry) { getConsumerLag() } - - // Cache Metriken - private val cacheHitRate = Gauge.builder("cache.hit.rate") - .description("Cache hit rate percentage") - .register(meterRegistry) { getCacheHitRate() } -} -``` - -#### 6.4. Alerting Strategy - -**Alert-Definitionen basierend auf SLOs:** - -```yaml -# Prometheus Alert Rules -groups: - - name: slo.rules - rules: - - alert: HighErrorRate - expr: rate(http_request_errors_total[5m]) > 0.001 - for: 2m - labels: - severity: warning - annotations: - summary: "High error rate detected" - - - alert: HighLatency - expr: histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m])) > 0.5 - for: 5m - labels: - severity: critical - annotations: - summary: "High latency detected" -``` - ---- - -## 7. Zusätzliche Richtlinien - -#### 7.1. Security - -Die Autorisierung muss auf Methodenebene mit Spring Security Annotations (`@PreAuthorize`) durchgesetzt werden, um eine feingranulare Zugriffskontrolle zu gewährleisten. - -**JWT Implementation:** - -```kotlin -// JWT Configuration -@Configuration -@EnableWebSecurity -class SecurityConfig { - - @Bean - fun jwtAuthenticationFilter(): JwtAuthenticationFilter { - return JwtAuthenticationFilter() - } - - @Bean - fun securityFilterChain(http: HttpSecurity): SecurityFilterChain { - return http - .csrf { it.disable() } - .sessionManagement { it.sessionCreationPolicy(SessionCreationPolicy.STATELESS) } - .authorizeHttpRequests { auth -> - auth.requestMatchers("/api/auth/**").permitAll() - .requestMatchers(HttpMethod.GET, "/api/members/**").hasRole("USER") - .requestMatchers(HttpMethod.POST, "/api/members/**").hasRole("ADMIN") - .anyRequest().authenticated() - } - .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter::class.java) - .build() - } -} - -// Method-level Security -@RestController -@RequestMapping("/api/members") -class MemberController { - - @GetMapping("/{id}") - @PreAuthorize("hasRole('USER') or @memberService.isOwner(#id, authentication.name)") - fun getMember(@PathVariable id: String): MemberDto { - // Implementation - } - - @PostMapping - @PreAuthorize("hasRole('ADMIN') or hasPermission(#memberDto, 'CREATE')") - fun createMember(@RequestBody memberDto: MemberDto): MemberDto { - // Implementation - } -} -``` - -**OAuth2 Integration:** - -```kotlin -// OAuth2 Resource Server Configuration -@Configuration -class OAuth2Config { - - @Bean - fun jwtDecoder(): JwtDecoder { - return NimbusJwtDecoder.withJwkSetUri("https://auth-provider/.well-known/jwks.json").build() - } - - @Bean - fun jwtAuthenticationConverter(): JwtAuthenticationConverter { - val converter = JwtAuthenticationConverter() - converter.setJwtGrantedAuthoritiesConverter { jwt -> - val authorities = jwt.getClaimAsStringList("authorities") ?: emptyList() - authorities.map { SimpleGrantedAuthority("ROLE_$it") } - } - return converter - } -} - -// Custom Permission Evaluator -@Component("memberService") -class MemberPermissionEvaluator { - - fun isOwner(memberId: String, username: String): Boolean { - return memberRepository.findById(memberId) - ?.let { it.email == username } - ?: false - } - - fun hasPermission(target: Any, permission: String): Boolean { - // Custom permission logic - return when (permission) { - "CREATE" -> hasCreatePermission(target) - "UPDATE" -> hasUpdatePermission(target) - else -> false - } - } -} -``` - -**Rate Limiting:** - -```kotlin -// Rate Limiting Configuration -@Configuration -class RateLimitConfig { - - @Bean - fun rateLimitFilter(): RateLimitFilter { - return RateLimitFilter( - rateLimiters = mapOf( - "/api/auth/login" to RateLimiter.create(5.0), // 5 requests per second - "/api/members" to RateLimiter.create(100.0), // 100 requests per second - "/api/events" to RateLimiter.create(50.0) // 50 requests per second - ) - ) - } -} - -// Custom Rate Limit Annotation -@Target(AnnotationTarget.FUNCTION) -@Retention(AnnotationRetention.RUNTIME) -annotation class RateLimit( - val requestsPerSecond: Double = 10.0, - val burstCapacity: Int = 20 -) - -// Usage -@RestController -class AuthController { - - @PostMapping("/login") - @RateLimit(requestsPerSecond = 5.0, burstCapacity = 10) - fun login(@RequestBody loginRequest: LoginRequest): AuthResponse { - // Implementation - } -} -``` - -#### 7.2. Performance - -Cache-Strategien (`@Cacheable`, `@CacheEvict`) **müssen gezielt eingesetzt werden**, um die Latenz bei häufigen Lesezugriffen zu minimieren. - -#### 7.3. Dokumentation - -Alle öffentlichen REST-Endpunkte müssen mit OpenAPI-Annotationen (`@Operation`, `@ApiResponse`) dokumentiert werden, um eine klare und interaktive API-Dokumentation zu generieren.