> ## Documentation Index
> Fetch the complete documentation index at: https://docs.westyx.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Spring Boot SDK - Caching Behaviour

> When does the Spring Boot SDK hit the network, snapshot atomicity, Spring Actuator integration.

The SDK keeps every config / secret / flag in an **in-memory snapshot** that is replaced atomically as a whole on each sync. Reads never block on the network; only the periodic background refresh does.

## When does the SDK hit the network

| Event                                       | Result                                                                                              |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Spring context startup (auto-configuration) | **Blocking** full sync. The application context only completes startup once the cache is populated. |
| Read within the TTL window                  | **No network** - served from the in-memory cache                                                    |
| Read after TTL has elapsed                  | Stale cache returned **immediately**; one background refresh fires in parallel                      |
| Server returns `304 Not Modified`           | TTL reset, no payload downloaded                                                                    |
| Multiple concurrent expirations             | Only **one** background sync runs at a time (atomic CAS guard)                                      |
| SSE event received                          | Immediate full sync, regardless of TTL                                                              |
| SSE stream connected                        | Effective TTL bumped to **60 s** as a safety net (the stream handles fresh updates)                 |

## The atomic snapshot guarantee

The snapshot is replaced as one immutable record reference, behind an `AtomicReference`. Two threads reading concurrently either see the same old state or the same new state - never a mix. This means you can iterate `getAllConfigs()` or `getAllFlags()` without worrying about an entry being added or removed mid-loop.

## ETag / 304

Every successful sync stores the server's `ETag`. The next sync sends `If-None-Match: <etag>`. If nothing has changed:

```
HTTP/1.1 304 Not Modified
```

...the SDK resets its `syncedAt` timestamp and serves the existing snapshot. **No payload transferred.**

## Background sync errors

Background syncs **never throw** to the application. If the network call fails:

1. The error is logged via `NexusLogger.error(...)` (SLF4J at `error` level by default)
2. The existing cache continues to be served
3. The next read after the TTL elapses retries automatically

## Cache lifetime

The cache lives as long as the `NexusClient` bean - typically the entire application lifetime. The auto-configuration registers the bean with `destroyMethod = "close"`, so the SSE stream is stopped automatically on context shutdown.

## Diagnostics

| Method                       | What it tells you                                                             |
| ---------------------------- | ----------------------------------------------------------------------------- |
| `client.getSyncedAt()`       | When was the last successful sync - useful for `/actuator/health` integration |
| `client.isStreamConnected()` | Is the SSE stream currently up                                                |
| `client.getKeyType()`        | `"sk"` or `"pk"` - confirms the SDK saw the right key prefix                  |

If you set the SDK logger to `DEBUG` level via SLF4J:

```yaml theme={null}
logging:
  level:
    dev.westyx.nexus.NexusClient: DEBUG
```

...you'll see one line per sync:

```
nexus: sync OK - configs=12 secrets=4 flags=7 key_type=sk
nexus: sync 304 Not Modified - cache retained
```

## Spring Actuator integration

Exposing a custom health indicator that reports the SDK's freshness:

```java theme={null}
@Component
class NexusHealthIndicator implements HealthIndicator {

    private final NexusClient nexus;
    NexusHealthIndicator(NexusClient nexus) { this.nexus = nexus; }

    @Override
    public Health health() {
        Duration sinceSync = Duration.between(nexus.getSyncedAt(), Instant.now());
        Health.Builder health = sinceSync.toMinutes() < 5 ? Health.up() : Health.outOfService();
        return health
                .withDetail("syncedAt", nexus.getSyncedAt())
                .withDetail("streamConnected", nexus.isStreamConnected())
                .withDetail("keyType", nexus.getKeyType())
                .build();
    }
}
```

## Concurrency model

* **All reads are thread-safe** - guarded by `AtomicReference` for snapshot reads.
* **Background sync runs in a daemon thread** - at most one at a time, guarded by a CAS counter.
* **SSE runs in a separate daemon thread** - started by `connectStream()` (called by the auto-configuration).

The SDK does not pin a thread; reads from any `@Async` method, `@RestController`, or `@Scheduled` task are equally safe.
