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.Uuid
|
||||||
import com.benasher44.uuid.uuid4
|
import com.benasher44.uuid.uuid4
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for all domain events in the system.
|
* Base interface for all domain events in the system.
|
||||||
* Domain events represent something that happened in the domain that domain experts care about.
|
* A domain event represents something significant that has happened in a specific domain.
|
||||||
*/
|
*/
|
||||||
interface DomainEvent {
|
interface DomainEvent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique identifier for this event instance.
|
* Unique identifier for this event instance.
|
||||||
*/
|
*/
|
||||||
val eventId: Uuid
|
val eventId: Uuid
|
||||||
|
|
||||||
/**
|
|
||||||
* Timestamp when the event occurred.
|
|
||||||
*/
|
|
||||||
val timestamp: java.time.Instant
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifier of the aggregate that the event belongs to.
|
* Identifier of the aggregate that the event belongs to.
|
||||||
*/
|
*/
|
||||||
val aggregateId: Uuid
|
val aggregateId: Uuid
|
||||||
|
|
||||||
|
val eventType: String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Timestamp when the event occurred.
|
||||||
|
*/
|
||||||
|
val timestamp: Instant
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Version of the aggregate after the event was applied.
|
* Version of the aggregate after the event was applied.
|
||||||
*/
|
*/
|
||||||
@@ -30,12 +35,34 @@ interface DomainEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base implementation of the DomainEvent interface.
|
* Abstract base class for domain events to reduce boilerplate code.
|
||||||
* Provides default implementations for common properties.
|
|
||||||
*/
|
*/
|
||||||
abstract class BaseDomainEvent(
|
abstract class BaseDomainEvent(
|
||||||
override val eventId: Uuid = uuid4(),
|
|
||||||
override val timestamp: java.time.Instant = java.time.Instant.now(),
|
|
||||||
override val aggregateId: Uuid,
|
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
|
) : 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.KotlinInstantSerializer
|
||||||
import at.mocode.core.domain.serialization.UuidSerializer
|
import at.mocode.core.domain.serialization.UuidSerializer
|
||||||
import com.benasher44.uuid.Uuid
|
import com.benasher44.uuid.Uuid
|
||||||
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.serialization.Serializable
|
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
|
@Serializable
|
||||||
data class ApiResponse<T>(
|
data class ApiResponse<T>(
|
||||||
val success: Boolean,
|
|
||||||
val data: T? = null,
|
val data: T? = null,
|
||||||
val error: ErrorDto? = null,
|
val success: Boolean = true,
|
||||||
val message: String? = null
|
val message: String? = null,
|
||||||
) : BaseDto {
|
val errors: List<String> = emptyList(),
|
||||||
companion object {
|
val timestamp: Instant = Clock.System.now()
|
||||||
/**
|
)
|
||||||
* 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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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(
|
data class PagedResponse<T>(
|
||||||
success = false,
|
val content: List<T>,
|
||||||
error = ErrorDto(
|
val page: Int,
|
||||||
code = code,
|
val size: Int,
|
||||||
message = message,
|
val totalElements: Long,
|
||||||
details = details
|
val totalPages: Int,
|
||||||
)
|
val hasNext: Boolean,
|
||||||
)
|
val hasPrevious: Boolean
|
||||||
}
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Error information DTO
|
* Error information DTO
|
||||||
@@ -85,12 +76,5 @@ data class PaginationDto(
|
|||||||
val totalPages: Int
|
val totalPages: Int
|
||||||
) : BaseDto
|
) : 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
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data source enumeration - indicates where data originated from
|
* Defines the source of a data record.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@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
|
@Serializable
|
||||||
enum class PferdeGeschlechtE {
|
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 }
|
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
|
@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
|
* 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