All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cultureamp.eventsourcing.BlockingAsyncEventProcessorWaiter.kt Maven / Gradle / Ivy

package com.cultureamp.eventsourcing

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout

class BlockingAsyncEventProcessorWaiter(
    private val eventProcessors: List>,
    private val maxWaitMs: Long = 5000,
    private val pollWaitMs: Long = 100,
    private val logger: (String) -> Unit = System.out::println,
) {
    fun waitUntilProcessed(events: List>) {
        val projectorToMaxRelevantSequence: Map, Long> = eventProcessors.associateWith { eventProcessor ->
            val eventProcessorEventTypes = eventProcessor.sequencedEventProcessor.domainEventClasses().toSet()
            val relevantSequencedEvents = events.filter { eventProcessorEventTypes.contains(it.event.domainEvent::class) }
            val maxRelevantSequence = relevantSequencedEvents.lastOrNull()?.let { it.sequence }
            maxRelevantSequence
        }.filterNotNullValues()
        if (projectorToMaxRelevantSequence.isNotEmpty()) {
            runBlocking {
                withTimeout(maxWaitMs) {
                    while (anyLaggingFrom(projectorToMaxRelevantSequence)) {
                        delay(pollWaitMs)
                    }
                }
            }
        }
    }

    private fun anyLaggingFrom(projectorToMaxRelevantSequence: Map, Long>): Boolean {
        val bookmarkStoreToBookmarkNames = projectorToMaxRelevantSequence.keys.map { it.bookmarkStore to it.bookmarkName }.groupBy { it.first }.mapValues { it.value.map { it.second }.toSet() }
        val bookmarks = bookmarkStoreToBookmarkNames.flatMap { it.key.bookmarksFor(it.value) }.toSet()
        val desired = projectorToMaxRelevantSequence.mapKeys { it.key.bookmarkName }
        val actual = bookmarks.associate { it.name to it.sequence }
        val diff = desired.map { it.key to it.value - (actual[it.key] ?: 0) }.toMap()
        val lagging = diff.filter { it.value > 0 }
        if (lagging.isNotEmpty()) {
            logger("Waiting for eventProcessors to catch up. ${lagging.map { "${it.key}=${(actual[it.key] ?: 0)}/${desired[it.key]}" }}")
        }
        return lagging.isNotEmpty()
    }
}

@Suppress("UNCHECKED_CAST")
fun  Map.filterNotNullValues(): Map =
    filterValues { it != null } as Map




© 2015 - 2025 Weber Informatics LLC | Privacy Policy