feat(core): Implement initial Shared Kernel foundation

Initializes the foundational `core`-module, which will act as the "Shared Kernel" for the entire Meldestelle_Pro ecosystem. This commit establishes the central building blocks required by all other domains.

- **Ubiquitous Language:** Defines core enums (`SparteE`, `PferdeGeschlechtE`, `DatenQuelleE`, `RolleE`, `BerechtigungE`) to ensure a consistent language across all services.
- **API Consistency:** Implements standardized DTOs (`ApiResponse`, `PagedResponse`, `EntityDto`) to guarantee that all APIs have a uniform structure.
- **Domain Events:** Creates the base interfaces (`DomainEvent`, `DomainEventPublisher`) for our event-driven architecture.
- **Error Handling & Validation:** Introduces a functional `Result` type for robust error handling and `ValidationResult` for business rule validation within domain entities.
- **Serialization:** Provides common serializers for standard data types like `UUID` and `Instant` to ensure data consistency.

This foundational module is a critical prerequisite for the development of all subsequent domain services, starting with the `masterdata-service`.
This commit is contained in:
2025-07-27 23:28:39 +02:00
parent 73b5e19db4
commit f9d9d01b21
5 changed files with 147 additions and 57 deletions
@@ -2,27 +2,32 @@ package at.mocode.core.domain.event
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuid4
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
/**
* Interface for all domain events in the system.
* Domain events represent something that happened in the domain that domain experts care about.
* Base interface for all domain events in the system.
* A domain event represents something significant that has happened in a specific domain.
*/
interface DomainEvent {
/**
* Unique identifier for this event instance.
*/
val eventId: Uuid
/**
* Timestamp when the event occurred.
*/
val timestamp: java.time.Instant
/**
* Identifier of the aggregate that the event belongs to.
*/
val aggregateId: Uuid
val eventType: String
/**
* Timestamp when the event occurred.
*/
val timestamp: Instant
/**
* Version of the aggregate after the event was applied.
*/
@@ -30,12 +35,34 @@ interface DomainEvent {
}
/**
* Base implementation of the DomainEvent interface.
* Provides default implementations for common properties.
* Abstract base class for domain events to reduce boilerplate code.
*/
abstract class BaseDomainEvent(
override val eventId: Uuid = uuid4(),
override val timestamp: java.time.Instant = java.time.Instant.now(),
override val aggregateId: Uuid,
override val version: Long
override val eventType: String,
override val version: Long,
override val eventId: Uuid = uuid4(),
override val timestamp: Instant = Clock.System.now()
) : DomainEvent
/**
* Interface for a component that can publish domain events, typically to a message bus like Kafka.
*/
interface DomainEventPublisher {
suspend fun publish(event: DomainEvent)
suspend fun publishAll(events: List<DomainEvent>)
}
/**
* Interface for a component that can handle (react to) a specific type of domain event.
*/
interface DomainEventHandler<T : DomainEvent> {
suspend fun handle(event: T)
fun canHandle(eventType: String): Boolean
}
@@ -3,6 +3,7 @@ package at.mocode.core.domain.model
import at.mocode.core.domain.serialization.KotlinInstantSerializer
import at.mocode.core.domain.serialization.UuidSerializer
import com.benasher44.uuid.Uuid
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.serialization.Serializable
@@ -27,42 +28,32 @@ abstract class EntityDto : BaseDto {
}
/**
* Standard API response wrapper
* A standardized wrapper for all API responses.
* Provides a consistent structure for data, success status, and errors.
* @param T The type of the data payload.
*/
@Serializable
data class ApiResponse<T>(
val success: Boolean,
val data: T? = null,
val error: ErrorDto? = null,
val message: String? = null
) : BaseDto {
companion object {
/**
* Creates a successful API response with data
*/
fun <T> success(data: T, message: String? = null): ApiResponse<T> {
return ApiResponse(
success = true,
data = data,
message = message
)
}
val success: Boolean = true,
val message: String? = null,
val errors: List<String> = emptyList(),
val timestamp: Instant = Clock.System.now()
)
/**
* Creates an error API response
*/
fun <T> error(message: String, code: String = "ERROR", details: Map<String, String>? = null): ApiResponse<T> {
return ApiResponse(
success = false,
error = ErrorDto(
code = code,
message = message,
details = details
)
)
}
}
}
/**
* A standardized wrapper for paginated API responses.
* @param T The type of the content in the page.
*/
data class PagedResponse<T>(
val content: List<T>,
val page: Int,
val size: Int,
val totalElements: Long,
val totalPages: Int,
val hasNext: Boolean,
val hasPrevious: Boolean
)
/**
* Error information DTO
@@ -85,12 +76,5 @@ data class PaginationDto(
val totalPages: Int
) : BaseDto
/**
* Paginated response wrapper
*/
@Serializable
data class PagedResponse<T>(
val data: List<T>,
val pagination: PaginationDto
) : BaseDto
@@ -3,17 +3,23 @@ package at.mocode.core.domain.model
import kotlinx.serialization.Serializable
/**
* Data source enumeration - indicates where data originated from
* Defines the source of a data record.
*/
@Serializable
enum class DatenQuelleE { OEPS_ZNS, MANUELL }
enum class DatenQuelleE {
MANUELL, // Manually entered
IMPORT_ZNS, // Imported from OEPS ZNS data
SYSTEM_GENERATED // Generated by the system itself
}
/**
* Horse gender enumeration
* Defines the gender of a horse.
*/
@Serializable
enum class PferdeGeschlechtE {
HENGST, STUTE, WALLACH, UNBEKANNT
HENGST, // Male, not castrated
STUTE, // Female
WALLACH // Male, castrated
}
/**
@@ -23,10 +29,21 @@ enum class PferdeGeschlechtE {
enum class GeschlechtE { M, W, D, UNBEKANNT }
/**
* Sport discipline enumeration
* Defines the different equestrian disciplines (Sparten).
* This enum is a central part of the Ubiquitous Language.
*/
@Serializable
enum class SparteE { DRESSUR, SPRINGEN, VIELSEITIGKEIT, FAHREN, VOLTIGIEREN, WESTERN, DISTANZ, ISLAND, PFERDESPORT_SPIEL, BASIS, KOMBINIERT, SONSTIGES }
enum class SparteE {
DRESSUR, // Dressurreiten
SPRINGEN, // Springreiten
VIELSEITIGKEIT, // Vielseitigkeitsreiten
FAHREN, // Fahrsport
VOLTIGIEREN, // Voltigieren
WESTERN, // Westernreiten
DISTANZ, // Distanzreiten
PARA_DRESSUR, // Para-Dressur
ISLAND; // Islandpferde
}
/**
* Venue/place type enumeration
@@ -0,0 +1,37 @@
package at.mocode.core.utils.error
/**
* A functional approach to error handling, avoiding exceptions for predictable errors.
* Represents a value that can either be a Success (containing the result) or a Failure (containing an error).
*
* @param T The type of the success value.
* @param E The type of the error value.
*/
sealed class Result<out T, out E> {
data class Success<out T>(val value: T) : Result<T, Nothing>()
data class Failure<out E>(val error: E) : Result<Nothing, E>()
val isSuccess: Boolean get() = this is Success
val isFailure: Boolean get() = this is Failure
fun getOrNull(): T? = when (this) {
is Success -> value
is Failure -> null
}
fun getOrElse(defaultValue: @UnsafeVariance T): T = when (this) {
is Success -> value
is Failure -> defaultValue
}
}
// Extension functions for convenient usage
inline fun <T, E> Result<T, E>.onSuccess(action: (T) -> Unit): Result<T, E> {
if (this is Result.Success) action(value)
return this
}
inline fun <T, E> Result<T, E>.onFailure(action: (E) -> Unit): Result<T, E> {
if (this is Result.Failure) action(error)
return this
}
@@ -0,0 +1,25 @@
package at.mocode.core.utils.validation
/**
* Represents a single validation error.
* @param field The name of the field that failed validation.
* @param message A user-friendly error message.
*/
data class ValidationError(
val field: String,
val message: String
)
/**
* Represents the result of a validation process.
*/
data class ValidationResult(
val isValid: Boolean,
val errors: List<ValidationError> = emptyList()
) {
companion object {
fun valid() = ValidationResult(true)
fun invalid(errors: List<ValidationError>) = ValidationResult(false, errors)
fun invalid(field: String, message: String) = ValidationResult(false, listOf(ValidationError(field, message)))
}
}