feat(Tracer Bullet)
This commit is contained in:
@@ -1,27 +1,51 @@
|
||||
// Dieses Modul definiert die Kern-Domänenobjekte des Shared Kernels.
|
||||
// Es enthält keine Implementierungsdetails, nur reine Datenklassen und Enums.
|
||||
plugins {
|
||||
alias(libs.plugins.kotlin.jvm)
|
||||
alias(libs.plugins.kotlin.multiplatform)
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-Xopt-in=kotlin.time.ExperimentalTime")
|
||||
// Target platforms
|
||||
jvm {
|
||||
compilerOptions {
|
||||
freeCompilerArgs.add("-Xopt-in=kotlin.time.ExperimentalTime")
|
||||
}
|
||||
}
|
||||
js(IR) {
|
||||
browser()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
// Kern-Abhängigkeiten für das Domänen-Modul (common for all platforms)
|
||||
api(libs.uuid)
|
||||
api(libs.kotlinx.serialization.json)
|
||||
api(libs.kotlinx.datetime)
|
||||
}
|
||||
}
|
||||
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
// Stellt sicher, dass dieses Modul Zugriff auf die im zentralen Katalog
|
||||
// definierten Bibliotheken hat (JVM-specific)
|
||||
api(projects.platform.platformDependencies)
|
||||
}
|
||||
}
|
||||
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(libs.kotlin.test)
|
||||
}
|
||||
}
|
||||
|
||||
val jvmTest by getting {
|
||||
dependencies {
|
||||
// Stellt die Test-Bibliotheken bereit (JVM-specific)
|
||||
implementation(projects.platform.platformTesting)
|
||||
implementation(libs.bundles.testing.jvm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Stellt sicher, dass dieses Modul Zugriff auf die im zentralen Katalog
|
||||
// definierten Bibliotheken hat.
|
||||
api(projects.platform.platformDependencies)
|
||||
|
||||
// Kern-Abhängigkeiten für das Domänen-Modul.
|
||||
api(libs.uuid)
|
||||
api(libs.kotlinx.serialization.json)
|
||||
api(libs.kotlinx.datetime)
|
||||
|
||||
// Stellt die Test-Bibliotheken bereit.
|
||||
testImplementation(projects.platform.platformTesting)
|
||||
testImplementation(libs.bundles.testing.jvm)
|
||||
}
|
||||
|
||||
+17
-16
@@ -1,5 +1,6 @@
|
||||
package at.mocode.core.domain.event
|
||||
|
||||
import at.mocode.core.domain.model.*
|
||||
import at.mocode.core.domain.serialization.KotlinInstantSerializer
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
@@ -7,38 +8,38 @@ import com.benasher44.uuid.uuid4
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
|
||||
/**
|
||||
* Basis-Interface für alle Domänen-Events im System.
|
||||
* Ein Domänen-Event repräsentiert etwas fachlich Bedeutsames, das passiert ist.
|
||||
*/
|
||||
interface DomainEvent {
|
||||
val eventId: Uuid
|
||||
val aggregateId: Uuid
|
||||
val eventType: String
|
||||
val eventId: EventId
|
||||
val aggregateId: AggregateId
|
||||
val eventType: EventType
|
||||
val timestamp: Instant
|
||||
val version: Long
|
||||
val correlationId: Uuid?
|
||||
val causationId: Uuid?
|
||||
val version: EventVersion
|
||||
val correlationId: CorrelationId?
|
||||
val causationId: CausationId?
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstrakte Basisklasse für Domänen-Events, um Boilerplate-Code zu reduzieren.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
abstract class BaseDomainEvent(
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
override val aggregateId: Uuid,
|
||||
override val eventType: String,
|
||||
override val version: Long,
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
override val eventId: Uuid = uuid4(),
|
||||
override val aggregateId: AggregateId,
|
||||
override val eventType: EventType,
|
||||
override val version: EventVersion,
|
||||
override val eventId: EventId = EventId(uuid4()),
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
override val timestamp: Instant = Clock.System.now(),
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
override val correlationId: Uuid? = null,
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
override val causationId: Uuid? = null
|
||||
override val correlationId: CorrelationId? = null,
|
||||
override val causationId: CausationId? = null
|
||||
) : DomainEvent
|
||||
|
||||
/**
|
||||
+7
-2
@@ -5,6 +5,7 @@ import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.Instant
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
@@ -16,9 +17,9 @@ interface BaseDto
|
||||
* Base DTO for domain entities that have unique ID and audit timestamps.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
abstract class EntityDto : BaseDto {
|
||||
@Serializable(with = UuidSerializer::class)
|
||||
abstract val id: Uuid
|
||||
abstract val id: EntityId
|
||||
|
||||
@Serializable(with = KotlinInstantSerializer::class)
|
||||
abstract val createdAt: Instant
|
||||
@@ -41,6 +42,7 @@ data class ErrorDto(
|
||||
* A standardized and consistent wrapper for all API responses.
|
||||
*/
|
||||
@Serializable
|
||||
@OptIn(ExperimentalTime::class)
|
||||
data class ApiResponse<T>(
|
||||
val data: T?,
|
||||
val success: Boolean,
|
||||
@@ -49,10 +51,12 @@ data class ApiResponse<T>(
|
||||
val timestamp: Instant = Clock.System.now()
|
||||
) {
|
||||
companion object {
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> success(data: T): ApiResponse<T> {
|
||||
return ApiResponse(data = data, success = true)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> error(
|
||||
code: String,
|
||||
message: String,
|
||||
@@ -65,6 +69,7 @@ data class ApiResponse<T>(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
fun <T> error(errors: List<ErrorDto>): ApiResponse<T> {
|
||||
return ApiResponse(data = null, success = false, errors = errors)
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package at.mocode.core.domain.model
|
||||
|
||||
import at.mocode.core.domain.serialization.UuidSerializer
|
||||
import com.benasher44.uuid.Uuid
|
||||
import kotlin.jvm.JvmInline
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Value classes for strongly typed IDs and domain values.
|
||||
* These provide compile-time type safety without runtime overhead.
|
||||
*/
|
||||
|
||||
// === ID Value Classes ===
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for entity IDs.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EntityId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for event IDs.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EventId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for aggregate IDs.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class AggregateId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for correlation IDs used in event tracing.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class CorrelationId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for causation IDs used in event tracing.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class CausationId(@Serializable(with = UuidSerializer::class) val value: Uuid) {
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
// === Domain Value Classes ===
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for event types.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EventType(val value: String) {
|
||||
init {
|
||||
require(value.isNotBlank()) { "Event type cannot be blank" }
|
||||
require(value.matches(Regex("^[A-Za-z][A-Za-z0-9]*$"))) {
|
||||
"Event type must start with a letter and contain only alphanumeric characters"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for event version numbers.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class EventVersion(val value: Long) : Comparable<EventVersion> {
|
||||
init {
|
||||
require(value >= 0) { "Event version must be non-negative" }
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override fun compareTo(other: EventVersion): Int = value.compareTo(other.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for error codes.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class ErrorCode(val value: String) {
|
||||
init {
|
||||
require(value.isNotBlank()) { "Error code cannot be blank" }
|
||||
require(value.matches(Regex("^[A-Z][A-Z0-9_]*$"))) {
|
||||
"Error code must be uppercase and contain only letters, numbers, and underscores"
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for page numbers in pagination.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class PageNumber(val value: Int) {
|
||||
init {
|
||||
require(value >= 0) { "Page number must be non-negative" }
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* A strongly typed wrapper for page sizes in pagination.
|
||||
*/
|
||||
@Serializable
|
||||
@JvmInline
|
||||
value class PageSize(val value: Int) {
|
||||
init {
|
||||
require(value > 0) { "Page size must be positive" }
|
||||
require(value <= 1000) { "Page size cannot exceed 1000" }
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
}
|
||||
+2
@@ -3,6 +3,7 @@ package at.mocode.core.domain.serialization
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuidFrom
|
||||
import kotlin.time.Instant // KORRIGIERT: Finaler Wechsel zu kotlin.time
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlinx.datetime.LocalDate
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
import kotlinx.datetime.LocalTime
|
||||
@@ -19,6 +20,7 @@ object UuidSerializer : KSerializer<Uuid> {
|
||||
override fun deserialize(decoder: Decoder): Uuid = uuidFrom(decoder.decodeString())
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
object KotlinInstantSerializer : KSerializer<Instant> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Instant", PrimitiveKind.STRING)
|
||||
override fun serialize(encoder: Encoder, value: Instant) = encoder.encodeString(value.toString())
|
||||
@@ -1,6 +1,7 @@
|
||||
package at.mocode.core.domain
|
||||
|
||||
import at.mocode.core.domain.event.BaseDomainEvent
|
||||
import at.mocode.core.domain.model.*
|
||||
import com.benasher44.uuid.Uuid
|
||||
import com.benasher44.uuid.uuid4
|
||||
import kotlinx.serialization.Serializable
|
||||
@@ -22,21 +23,21 @@ class DomainEventTest {
|
||||
@Serializable
|
||||
data class TestEvent(
|
||||
@Transient
|
||||
override val aggregateId: Uuid = uuid4(),
|
||||
override val aggregateId: AggregateId = AggregateId(uuid4()),
|
||||
@Transient
|
||||
override val version: Long = 1L,
|
||||
override val version: EventVersion = EventVersion(1L),
|
||||
val testPayload: String = "Test"
|
||||
) : BaseDomainEvent(
|
||||
aggregateId = aggregateId,
|
||||
eventType = "TestEventOccurred", // Ein klar definierter Event-Typ
|
||||
eventType = EventType("TestEventOccurred"), // Ein klar definierter Event-Typ
|
||||
version = version
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `BaseDomainEvent should auto-generate eventId and timestamp upon creation`() {
|
||||
// Arrange
|
||||
val aggregateId = uuid4()
|
||||
val version = 1L
|
||||
val aggregateId = AggregateId(uuid4())
|
||||
val version = EventVersion(1L)
|
||||
|
||||
// Act
|
||||
val event = TestEvent(aggregateId, version)
|
||||
@@ -46,6 +47,6 @@ class DomainEventTest {
|
||||
assertNotNull(event.timestamp, "timestamp should be automatically generated and not null")
|
||||
assertEquals(aggregateId, event.aggregateId, "aggregateId should be set correctly")
|
||||
assertEquals(version, event.version, "version should be set correctly")
|
||||
assertEquals("TestEventOccurred", event.eventType, "eventType should be set correctly")
|
||||
assertEquals(EventType("TestEventOccurred"), event.eventType, "eventType should be set correctly")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user