refactor: Migrate from monolithic to modular architecture

1. **Dokumentation der Architektur:**
    - Vervollständigen Sie die C4-Diagramme im docs-Verzeichnis
    - Dokumentieren Sie die wichtigsten Architekturentscheidungen in ADRs

2. **Redis-Integration finalisieren:**
    - Implementieren Sie die verteilte Cache-Lösung für die Offline-Fähigkeit
    - Nutzen Sie Redis Streams für das Event-Sourcing
This commit is contained in:
stefan
2025-07-23 14:29:40 +02:00
parent a256622f37
commit 9282dd0eb4
52 changed files with 5648 additions and 3 deletions
@@ -0,0 +1,69 @@
package at.mocode.infrastructure.cache.api
import java.time.Duration
/**
* Configuration for the distributed cache.
*/
interface CacheConfiguration {
/**
* Default time-to-live for cache entries.
* If null, entries do not expire by default.
*/
val defaultTtl: Duration?
/**
* Maximum number of entries to store in the local cache.
* If null, there is no limit.
*/
val localCacheMaxSize: Int?
/**
* Whether to enable offline mode.
* If true, the cache will store entries locally when offline
* and synchronize them when online.
*/
val offlineModeEnabled: Boolean
/**
* How often to attempt synchronization in offline mode.
*/
val synchronizationInterval: Duration
/**
* Maximum age of entries to keep in the local cache when offline.
* If null, entries do not expire when offline.
*/
val offlineEntryMaxAge: Duration?
/**
* Prefix to add to all cache keys.
* This can be used to namespace cache entries.
*/
val keyPrefix: String
/**
* Whether to compress cache entries.
*/
val compressionEnabled: Boolean
/**
* Threshold in bytes above which to compress cache entries.
* Only used if compressionEnabled is true.
*/
val compressionThreshold: Int
}
/**
* Default implementation of CacheConfiguration.
*/
data class DefaultCacheConfiguration(
override val defaultTtl: Duration? = Duration.ofHours(1),
override val localCacheMaxSize: Int? = 10000,
override val offlineModeEnabled: Boolean = true,
override val synchronizationInterval: Duration = Duration.ofMinutes(5),
override val offlineEntryMaxAge: Duration? = Duration.ofDays(7),
override val keyPrefix: String = "",
override val compressionEnabled: Boolean = true,
override val compressionThreshold: Int = 1024
) : CacheConfiguration
@@ -0,0 +1,96 @@
package at.mocode.infrastructure.cache.api
import java.time.Instant
/**
* Represents an entry in the cache with metadata for offline capability.
*
* @param key The key of the cache entry
* @param value The value stored in the cache
* @param createdAt When the entry was created
* @param expiresAt When the entry expires, or null if it doesn't expire
* @param lastModifiedAt When the entry was last modified
* @param isDirty Whether the entry has been modified locally and needs to be synchronized
* @param isLocal Whether the entry is only stored locally (not yet synchronized)
*/
data class CacheEntry<T : Any>(
val key: String,
val value: T,
val createdAt: Instant = Instant.now(),
val expiresAt: Instant? = null,
val lastModifiedAt: Instant = Instant.now(),
val isDirty: Boolean = false,
val isLocal: Boolean = false
) {
/**
* Checks if the entry is expired.
*
* @return true if the entry is expired, false otherwise
*/
fun isExpired(): Boolean {
return expiresAt?.isBefore(Instant.now()) ?: false
}
/**
* Creates a new entry with the isDirty flag set to true.
*
* @return A new CacheEntry with isDirty set to true
*/
fun markDirty(): CacheEntry<T> {
return copy(
isDirty = true,
lastModifiedAt = Instant.now()
)
}
/**
* Creates a new entry with the isDirty flag set to false.
*
* @return A new CacheEntry with isDirty set to false
*/
fun markClean(): CacheEntry<T> {
return copy(
isDirty = false,
isLocal = false,
lastModifiedAt = Instant.now()
)
}
/**
* Creates a new entry with the isLocal flag set to true.
*
* @return A new CacheEntry with isLocal set to true
*/
fun markLocal(): CacheEntry<T> {
return copy(
isLocal = true,
lastModifiedAt = Instant.now()
)
}
/**
* Creates a new entry with an updated value.
*
* @param newValue The new value
* @return A new CacheEntry with the updated value
*/
fun updateValue(newValue: T): CacheEntry<T> {
return copy(
value = newValue,
lastModifiedAt = Instant.now()
)
}
/**
* Creates a new entry with an updated expiration time.
*
* @param newExpiresAt The new expiration time
* @return A new CacheEntry with the updated expiration time
*/
fun updateExpiration(newExpiresAt: Instant?): CacheEntry<T> {
return copy(
expiresAt = newExpiresAt,
lastModifiedAt = Instant.now()
)
}
}
@@ -0,0 +1,56 @@
package at.mocode.infrastructure.cache.api
/**
* Interface for serializing and deserializing cache entries.
*/
interface CacheSerializer {
/**
* Serializes a value to a byte array.
*
* @param value The value to serialize
* @return The serialized value as a byte array
*/
fun <T : Any> serialize(value: T): ByteArray
/**
* Deserializes a byte array to a value.
*
* @param bytes The byte array to deserialize
* @param clazz The class of the value to deserialize to
* @return The deserialized value
*/
fun <T : Any> deserialize(bytes: ByteArray, clazz: Class<T>): T
/**
* Serializes a cache entry to a byte array.
*
* @param entry The cache entry to serialize
* @return The serialized cache entry as a byte array
*/
fun <T : Any> serializeEntry(entry: CacheEntry<T>): ByteArray
/**
* Deserializes a byte array to a cache entry.
*
* @param bytes The byte array to deserialize
* @param valueClass The class of the value in the cache entry
* @return The deserialized cache entry
*/
fun <T : Any> deserializeEntry(bytes: ByteArray, valueClass: Class<T>): CacheEntry<T>
/**
* Compresses a byte array.
*
* @param bytes The byte array to compress
* @return The compressed byte array
*/
fun compress(bytes: ByteArray): ByteArray
/**
* Decompresses a byte array.
*
* @param bytes The byte array to decompress
* @return The decompressed byte array
*/
fun decompress(bytes: ByteArray): ByteArray
}
@@ -0,0 +1,76 @@
package at.mocode.infrastructure.cache.api
import java.time.Instant
/**
* Represents the connection status of the cache.
*/
enum class ConnectionState {
/**
* The cache is connected to the remote server.
*/
CONNECTED,
/**
* The cache is disconnected from the remote server.
*/
DISCONNECTED,
/**
* The cache is attempting to reconnect to the remote server.
*/
RECONNECTING
}
/**
* Interface for tracking the connection status of the cache.
*/
interface ConnectionStatusTracker {
/**
* Gets the current connection state.
*
* @return The current connection state
*/
fun getConnectionState(): ConnectionState
/**
* Gets the time when the connection state last changed.
*
* @return The time when the connection state last changed
*/
fun getLastStateChangeTime(): Instant
/**
* Registers a listener to be notified when the connection state changes.
*
* @param listener The listener to register
*/
fun registerConnectionListener(listener: ConnectionStateListener)
/**
* Unregisters a connection state listener.
*
* @param listener The listener to unregister
*/
fun unregisterConnectionListener(listener: ConnectionStateListener)
/**
* Checks if the cache is currently connected.
*
* @return true if the cache is connected, false otherwise
*/
fun isConnected(): Boolean = getConnectionState() == ConnectionState.CONNECTED
}
/**
* Listener for connection state changes.
*/
interface ConnectionStateListener {
/**
* Called when the connection state changes.
*
* @param newState The new connection state
* @param timestamp The time when the state changed
*/
fun onConnectionStateChanged(newState: ConnectionState, timestamp: Instant)
}
@@ -0,0 +1,94 @@
package at.mocode.infrastructure.cache.api
import java.time.Duration
/**
* Interface for a distributed cache that supports offline capability.
* This cache can be used to store and retrieve data across multiple instances
* and provides mechanisms for offline operation.
*/
interface DistributedCache {
/**
* Retrieves a value from the cache.
*
* @param key The key to retrieve
* @return The value associated with the key, or null if not found
*/
fun <T : Any> get(key: String, clazz: Class<T>): T?
/**
* Stores a value in the cache with an optional time-to-live.
*
* @param key The key to store the value under
* @param value The value to store
* @param ttl Optional time-to-live for the cache entry
*/
fun <T : Any> set(key: String, value: T, ttl: Duration? = null)
/**
* Removes a value from the cache.
*
* @param key The key to remove
*/
fun delete(key: String)
/**
* Checks if a key exists in the cache.
*
* @param key The key to check
* @return true if the key exists, false otherwise
*/
fun exists(key: String): Boolean
/**
* Retrieves multiple values from the cache.
*
* @param keys The keys to retrieve
* @return A map of keys to values, with missing keys omitted
*/
fun <T : Any> multiGet(keys: Collection<String>, clazz: Class<T>): Map<String, T>
/**
* Stores multiple values in the cache with an optional time-to-live.
*
* @param entries The key-value pairs to store
* @param ttl Optional time-to-live for the cache entries
*/
fun <T : Any> multiSet(entries: Map<String, T>, ttl: Duration? = null)
/**
* Removes multiple values from the cache.
*
* @param keys The keys to remove
*/
fun multiDelete(keys: Collection<String>)
/**
* Synchronizes the local cache with the distributed cache.
* This is used to ensure that the local cache is up-to-date with the distributed cache
* after being offline.
*
* @param keys Optional collection of keys to synchronize. If null, all keys are synchronized.
*/
fun synchronize(keys: Collection<String>? = null)
/**
* Marks a key as dirty, indicating that it has been modified locally
* and needs to be synchronized with the distributed cache.
*
* @param key The key to mark as dirty
*/
fun markDirty(key: String)
/**
* Gets all keys that have been marked as dirty.
*
* @return A collection of dirty keys
*/
fun getDirtyKeys(): Collection<String>
/**
* Clears all entries from the cache.
*/
fun clear()
}