feat(valkey): initial stable release v0.9.0
Valkey (Redis-compatible) client component with launcher lifecycle and health check integration. What's included: - Config with Addrs, Password, SelectDB, CacheSizeEachConn (env-driven) - Provider interface exposing native valkey-go Client() directly (no wrapper) - Component interface: launcher.Component + health.Checkable + Provider - New(logger, cfg) constructor for lifecycle registration via lc.Append - Health check via PING at LevelDegraded priority - Graceful shutdown calling client.Close() in OnStop Tested-via: todo-api POC integration Reviewed-against: docs/adr/
This commit is contained in:
54
docs/adr/ADR-001-native-valkey-client.md
Normal file
54
docs/adr/ADR-001-native-valkey-client.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# ADR-001: Expose Native valkey.Client Without a Wrapper Layer
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
Some infrastructure modules wrap their underlying client behind a custom interface that
|
||||
re-exports only the operations they anticipate callers will need. This approach has two
|
||||
failure modes:
|
||||
|
||||
1. The wrapper becomes a bottleneck: every new operation requires a new method on the
|
||||
wrapper interface, creating churn.
|
||||
2. The wrapper diverges from the upstream API surface, forcing callers to learn two APIs.
|
||||
|
||||
Valkey (and the compatible Redis protocol) has a rich, evolving command set. A thin wrapper
|
||||
that re-exports commands one at a time would either be incomplete or grow unboundedly.
|
||||
|
||||
## Decision
|
||||
|
||||
The `Component` interface exposes the native `vk.Client` directly via `Client() vk.Client`.
|
||||
Callers receive a `vk.Client` value and use the valkey-go command builder API directly:
|
||||
|
||||
```go
|
||||
cmd := vkClient.B().Set().Key(key).Value(val).Ex(ttl).Build()
|
||||
err = vkClient.Do(ctx, cmd).Error()
|
||||
```
|
||||
|
||||
The `Provider` interface is the minimal consumer-facing surface:
|
||||
|
||||
```go
|
||||
type Provider interface {
|
||||
Client() vk.Client
|
||||
}
|
||||
```
|
||||
|
||||
This module's only responsibilities are: constructing the client from `Config`, verifying
|
||||
connectivity on `OnStart`, issuing a `PING` for health checks, and closing the client on
|
||||
`OnStop`. All command execution is delegated entirely to the caller via the native client.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Callers have access to the full valkey-go API with no intermediary layer.
|
||||
- No wrapper code to maintain as the valkey-go API evolves.
|
||||
- The module stays small and focused on lifecycle management.
|
||||
- Optional client-side caching (`CacheSizeEachConn`) is supported by passing the config
|
||||
option through to `vk.NewClient` — no wrapper changes needed.
|
||||
|
||||
**Negative:**
|
||||
- Callers are coupled to the `valkey-go` library's API directly. Switching to a different
|
||||
Valkey/Redis client would require changes at every call site.
|
||||
- Mocking in tests requires either an `httptest`-style server or a mock that satisfies the
|
||||
`vk.Client` interface, which is more complex than mocking a minimal custom interface.
|
||||
49
docs/adr/ADR-002-no-serialization-helpers.md
Normal file
49
docs/adr/ADR-002-no-serialization-helpers.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# ADR-002: No Serialisation Helpers — Callers Marshal/Unmarshal Themselves
|
||||
|
||||
**Status:** Accepted
|
||||
**Date:** 2026-03-18
|
||||
|
||||
## Context
|
||||
|
||||
Cache and key-value store modules often provide convenience methods such as
|
||||
`SetJSON(ctx, key, value, ttl)` or `GetJSON(ctx, key, &target)` that handle JSON
|
||||
marshalling and unmarshalling internally. While convenient, this approach has drawbacks:
|
||||
|
||||
- It encodes a single serialisation format (typically JSON) into the module's API, making
|
||||
it hard to use binary formats like protobuf or MessagePack for performance-sensitive paths.
|
||||
- It obscures marshalling errors, which can become hard to distinguish from network errors.
|
||||
- It requires the module to understand the caller's data types, coupling them together.
|
||||
- It adds dependencies (e.g. `encoding/json`) that are not needed for all callers.
|
||||
|
||||
## Decision
|
||||
|
||||
The `valkey` module provides no serialisation helpers. It exposes only `Client() vk.Client`,
|
||||
and all marshal/unmarshal logic lives in the caller:
|
||||
|
||||
```go
|
||||
// caller marshals before writing
|
||||
b, err := json.Marshal(myValue)
|
||||
cmd := client.B().Set().Key(key).Value(string(b)).Ex(ttl).Build()
|
||||
client.Do(ctx, cmd)
|
||||
|
||||
// caller unmarshals after reading
|
||||
result := client.Do(ctx, client.B().Get().Key(key).Build())
|
||||
b, err := result.AsBytes()
|
||||
json.Unmarshal(b, &myValue)
|
||||
```
|
||||
|
||||
This keeps the module at zero opinion on serialisation format, zero added dependencies
|
||||
beyond `valkey-go`, and zero abstraction cost.
|
||||
|
||||
## Consequences
|
||||
|
||||
**Positive:**
|
||||
- Callers choose their own serialisation format with no module-level constraints.
|
||||
- The module has no encoding/decoding logic that needs testing or maintenance.
|
||||
- Binary formats, compressed payloads, and plain strings all work identically.
|
||||
|
||||
**Negative:**
|
||||
- Every caller that stores structured data must implement its own marshal/unmarshal
|
||||
boilerplate, typically in a repository or cache layer.
|
||||
- There is no built-in protection against storing data with an incompatible format
|
||||
(e.g. writing JSON and reading with a protobuf decoder).
|
||||
Reference in New Issue
Block a user