# How CacheEvictedEventBus Enables Distributed Cache Invalidation in Cocache

> Discover how CacheEvictedEventBus in Cocache synchronizes distributed caches by broadcasting eviction events across JVMs, ensuring local entries are invalidated when remote modifications occur.

- Repository: [Ahoo Wang/cocache](https://github.com/ahoo-wang/cocache)
- Tags: internals
- Published: 2026-02-23

---

**The CacheEvictedEventBus is a lightweight publish/subscribe mechanism that broadcasts cache eviction events across JVMs, ensuring coherent caches stay synchronized by invalidating stale entries locally when remote modifications occur.**

In the `ahoo-wang/cocache` repository, the `CacheEvictedEventBus` serves as the backbone for maintaining consistency across distributed cache instances. When one node modifies a cache entry, this event bus propagates `CacheEvictedEvent` messages to all registered subscribers, triggering local invalidation without requiring direct remote calls per operation.

## Core Architecture

The distributed invalidation system revolves around three primary components defined in the `cocache-core` module.

### CacheEvictedEventBus Interface

The contract is defined in [`cocache-core/src/main/kotlin/me/ahoo/cache/consistency/CacheEvictedEventBus.kt`](https://github.com/ahoo-wang/cocache/blob/main/cocache-core/src/main/kotlin/me/ahoo/cache/consistency/CacheEvictedEventBus.kt) and specifies three primitive operations:

- **`publish(event: CacheEvictedEvent)`** – Broadcasts an eviction event to all subscribers
- **`register(subscriber: CacheEvictedSubscriber)`** – Adds a subscriber to receive future events  
- **`unregister(subscriber: CacheEvictedSubscriber)`** – Removes a subscriber from the bus

### CacheEvictedEvent Payload

The event structure in [`CacheEvictedEvent.kt`](https://github.com/ahoo-wang/cocache/blob/main/CacheEvictedEvent.kt) carries the minimal data required for invalidation:

- **`cacheName`** – Identifies which cache instance should evict
- **`key`** – The specific cache key to invalidate
- **`publisherId`** – Unique identifier of the originating node (prevents self-eviction loops)

### CacheEvictedSubscriber Interface

Implementations of `CacheEvictedSubscriber` (such as `DefaultCoherentCache`) provide the `onEvicted(event: CacheEvictedEvent)` method to handle incoming invalidation requests.

## Distributed Invalidation Flow

When a cache entry is modified, the `CacheEvictedEventBus` orchestrates consistency through the following sequence:

1. **Local Mutation** – `DefaultCoherentCache.setCache()` or `evict()` updates the client-side and distributed cache layers, then calls `cacheEvictedEventBus.publish()`.

2. **Event Propagation** – 
   - **Guava implementation**: `eventBus.post(event)` notifies in-process subscribers instantly
   - **Redis implementation**: `redisTemplate.convertAndSend(channel, message)` publishes to a Redis Pub/Sub channel named after the cache

3. **Remote Reception** – Each `CacheEvictedSubscriber` receives the event via:
   - Guava's `@Subscribe` annotation adapter
   - Redis `MessageListenerAdapter` that deserializes the message back to `CacheEvictedEvent`

4. **Local Eviction** – `DefaultCoherentCache.onEvicted()` validates the event by checking:
   - The `cacheName` matches the local cache
   - The `publisherId` differs from the local node ID (avoiding self-eviction)
   - If valid, calls `clientSideCache.evict(key)` to remove the stale entry

## Implementation Strategies

The repository provides two concrete implementations of the `CacheEvictedEventBus` interface to support different deployment topologies.

### GuavaCacheEvictedEventBus

Located in [`cocache-core/src/main/kotlin/me/ahoo/cache/consistency/GuavaCacheEvictedEventBus.kt`](https://github.com/ahoo-wang/cocache/blob/main/cocache-core/src/main/kotlin/me/ahoo/cache/consistency/GuavaCacheEvictedEventBus.kt), this implementation wraps Google's `EventBus` for single-process or testing environments. It provides synchronous, in-memory event dispatch without external dependencies.

### RedisCacheEvictedEventBus

Found in [`cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/RedisCacheEvictedEventBus.kt`](https://github.com/ahoo-wang/cocache/blob/main/cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/RedisCacheEvictedEventBus.kt), this production-ready implementation leverages Redis Pub/Sub for cross-process communication. Each subscriber creates a `MessageListenerAdapter` that converts Redis messages back into `CacheEvictedEvent` objects, enabling cache coherence across distributed microservices.

## Wiring Components Together

The `DefaultCoherentCacheFactory` in [`cocache-core/src/main/kotlin/me/ahoo/cache/consistency/DefaultCoherentCacheFactory.kt`](https://github.com/ahoo-wang/cocache/blob/main/cocache-core/src/main/kotlin/me/ahoo/cache/consistency/DefaultCoherentCacheFactory.kt) injects the chosen `CacheEvictedEventBus` implementation into `DefaultCoherentCache` instances during creation. This factory pattern ensures that every coherent cache automatically participates in the distributed invalidation protocol without manual wiring.

## Practical Examples

### Guava-Based Event Bus for Unit Testing

```kotlin
import me.ahoo.cache.consistency.*

// Create the in-process bus
val evictedBus = GuavaCacheEvictedEventBus()

// Register a subscriber
evictedBus.register(object : CacheEvictedSubscriber {
    override fun onEvicted(event: CacheEvictedEvent) {
        println("Received eviction: ${event.cacheName}:${event.key}")
    }
})

// Publish an event from producer node "app-01"
evictedBus.publish(CacheEvictedEvent("userCache", "user:123", "app-01"))

```

### Redis-Backed Production Configuration

```kotlin
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.data.redis.listener.RedisMessageListenerContainer
import me.ahoo.cache.spring.redis.RedisCacheEvictedEventBus

@Bean
fun cacheEvictedEventBus(
    redisTemplate: StringRedisTemplate,
    listenerContainer: RedisMessageListenerContainer
): CacheEvictedEventBus {
    return RedisCacheEvictedEventBus(redisTemplate, listenerContainer)
}

// Automatic invalidation occurs when:
// myCoherentCache.setCache("product:456", cacheValue) 
// This triggers publish() behind the scenes

```

### Factory-Based Creation

```kotlin
val bus: CacheEvictedEventBus = RedisCacheEvictedEventBus(redisTemplate, listenerContainer)
val factory = DefaultCoherentCacheFactory(bus)

val coherentCache = factory.create(
    cacheName = "orderCache",
    clientSideCache = caffeineCache,
    distributedCache = redisCache,
    cacheSource = orderCacheSource
)

```

## Summary

- **CacheEvictedEventBus** provides the publish/subscribe contract for distributed invalidation through `publish()`, `register()`, and `unregister()` methods
- **CacheEvictedEvent** carries `cacheName`, `key`, and `publisherId` to target specific entries while avoiding self-eviction loops
- **DefaultCoherentCache** automatically publishes events on write operations and handles remote invalidations via `onEvicted()`
- **GuavaCacheEvictedEventBus** supports single-process deployments using Google's EventBus
- **RedisCacheEvictedEventBus** enables cross-JVM coherence using Redis Pub/Sub channels
- The **DefaultCoherentCacheFactory** wires the event bus into cache instances automatically

## Frequently Asked Questions

### How does CacheEvictedEventBus prevent a node from evicting its own cache entries?

Each `CacheEvictedEvent` includes a `publisherId` field containing the unique identifier of the originating node. When `DefaultCoherentCache.onEvicted()` receives an event, it compares the event's `publisherId` against the local node ID. If they match, the event is ignored, preventing self-eviction while still processing events from other nodes.

### What is the difference between GuavaCacheEvictedEventBus and RedisCacheEvictedEventBus?

**GuavaCacheEvictedEventBus** uses Google's in-memory `EventBus` and only works within a single JVM process, making it suitable for testing or monolithic deployments. **RedisCacheEvictedEventBus** publishes events to Redis Pub/Sub channels, allowing multiple JVM processes or microservices to subscribe to invalidation events and maintain cache coherence across distributed systems.

### Which source files define the core CacheEvictedEventBus contract?

The interface is defined in [`cocache-core/src/main/kotlin/me/ahoo/cache/consistency/CacheEvictedEventBus.kt`](https://github.com/ahoo-wang/cocache/blob/main/cocache-core/src/main/kotlin/me/ahoo/cache/consistency/CacheEvictedEventBus.kt). Supporting classes include [`CacheEvictedEvent.kt`](https://github.com/ahoo-wang/cocache/blob/main/CacheEvictedEvent.kt) (payload), [`CacheEvictedSubscriber.kt`](https://github.com/ahoo-wang/cocache/blob/main/CacheEvictedSubscriber.kt) (consumer interface), and [`DefaultCoherentCache.kt`](https://github.com/ahoo-wang/cocache/blob/main/DefaultCoherentCache.kt) (publisher/subscriber implementation). Redis-specific implementation resides in [`cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/RedisCacheEvictedEventBus.kt`](https://github.com/ahoo-wang/cocache/blob/main/cocache-spring-redis/src/main/kotlin/me/ahoo/cache/spring/redis/RedisCacheEvictedEventBus.kt).

### How is CacheEvictedEventBus integrated with Spring Boot applications?

The `CoCacheAutoConfiguration` class in the Spring Boot starter automatically configures a `RedisCacheEvictedEventBus` when Spring Data Redis is detected on the classpath. The `DefaultCoherentCacheFactory` then injects this bean into all coherent cache instances, enabling distributed invalidation without explicit configuration.