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:
@@ -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
|
||||
/**
|
||||
* A standardized wrapper for paginated API responses.
|
||||
* @param T The type of the content in the page.
|
||||
*/
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
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)))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user