feat(masterdata): introduce Regulation domain with API, persistence, and metrics integration

- Added `RegulationRepository` and its `Exposed` implementation for persistence.
- Implemented REST endpoints for regulations (`/rules`) in `RegulationController`, including support for tournament classes, license matrix, guidelines, fees, and configuration retrieval.
- Integrated OpenAPI documentation for `/rules` endpoints with Swagger UI in `masterdataApiModule`.
- Enabled Micrometer-based metrics for Prometheus in the API layer.
- Updated Gradle dependencies to include OpenAPI, Swagger, and Micrometer libraries.
- Registered `RegulationRepository` and `RegulationController` in `MasterdataConfiguration`.
- Improved database access patterns and reduced repetitive validation logic across domain services.
- Added unit and application tests for `RegulationController` to verify API behavior and repository interactions.
- Updated the service's `ROADMAP.md` to mark API v1 endpoints and observability as complete.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-30 15:38:13 +02:00
parent d8c9d11adb
commit 2f17778df6
29 changed files with 591 additions and 105 deletions
@@ -4,6 +4,7 @@ import at.mocode.masterdata.api.masterdataApiModule
import at.mocode.masterdata.api.rest.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.micrometer.core.instrument.MeterRegistry
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
@@ -13,7 +14,8 @@ import org.springframework.context.annotation.Configuration
* Ktor-Server Bootstrap für den Masterdata-Bounded-Context (SCS-Architektur).
*
* - Startet einen eigenen Ktor Netty Server für diesen Kontext.
* - Hängt das masterdataApiModule mit den via Spring bereitgestellten Controllern ein.
* - Hängt das masterdataApiModul mit den via Spring bereitgestellten Controllern ein.
* - Nutzt die Spring-verwaltete MeterRegistry für gemeinsames Monitoring (Actuator + Ktor).
* - Port ist konfigurierbar über SPRING-Config/ENV (Default 8091). Für Tests kann Port 0 genutzt werden.
*/
@Configuration
@@ -21,16 +23,18 @@ class KtorServerConfiguration {
private val log = LoggerFactory.getLogger(KtorServerConfiguration::class.java)
@Bean(destroyMethod = "stop")
@Bean
fun ktorServer(
@Value("\${masterdata.http.port:8091}") port: Int,
meterRegistry: MeterRegistry,
countryController: CountryController,
bundeslandController: BundeslandController,
altersklasseController: AltersklasseController,
platzController: PlatzController,
reiterController: ReiterController,
horseController: HorseController,
vereinController: VereinController
vereinController: VereinController,
regulationController: RegulationController
): EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration> {
log.info("Starting Masterdata Ktor server on port {}", port)
val engine = embeddedServer(Netty, port = port) {
@@ -41,7 +45,9 @@ class KtorServerConfiguration {
platzController = platzController,
reiterController = reiterController,
horseController = horseController,
vereinController = vereinController
vereinController = vereinController,
regulationController = regulationController,
meterRegistry = meterRegistry
)
}
engine.start(wait = false)
@@ -58,6 +58,11 @@ class MasterdataConfiguration {
return ExposedFunktionaerRepository()
}
@Bean
fun regulationRepository(): RegulationRepository {
return ExposedRegulationRepository()
}
// Use Cases - Country/Land
@Bean
fun getCountryUseCase(landRepository: LandRepository): GetCountryUseCase {
@@ -149,6 +154,11 @@ class MasterdataConfiguration {
fun vereinController(vereinRepository: VereinRepository): VereinController {
return VereinController(vereinRepository)
}
@Bean
fun regulationController(regulationRepository: RegulationRepository): RegulationController {
return RegulationController(regulationRepository)
}
}
/**
@@ -27,7 +27,7 @@ class MasterdataDatabaseConfiguration {
log.info("Initializing database schema for Masterdata Service...")
try {
// Database connection should be initialized by Spring Boot
// Spring Boot should initialize database connection
transaction {
SchemaUtils.create(
LandTable,
@@ -0,0 +1,32 @@
spring:
application:
name: masterdata-service
main:
banner-mode: "off"
server:
port: 8081 # Spring Boot Management Port (Actuator)
masterdata:
http:
port: 8091 # Ktor API Port
management:
endpoints:
web:
exposure:
include: "health,info,metrics,prometheus"
endpoint:
health:
show-details: always
prometheus:
metrics:
export:
enabled: true
logging:
level:
root: INFO
at.mocode.masterdata: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<property name="LOG_PATTERN" value="%d{ISO8601} %-5level [%X{traceId:-}:%X{spanId:-}] %logger{36} - %msg%n"/>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${LOG_PATTERN}</pattern>
</encoder>
</appender>
<!-- Spring Loggers -->
<logger name="org.springframework" level="INFO"/>
<logger name="org.springframework.boot.actuate" level="INFO"/>
<!-- Ktor & Netty Loggers -->
<logger name="io.ktor" level="INFO"/>
<logger name="io.netty" level="WARN"/>
<!-- Masterdata Application Loggers -->
<logger name="at.mocode.masterdata" level="DEBUG"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>