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:
26
.devcontainer/devcontainer.json
Normal file
26
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "Go",
|
||||
"image": "mcr.microsoft.com/devcontainers/go:2-1.25-trixie",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-extra/features/claude-code:1": {}
|
||||
},
|
||||
"forwardPorts": [],
|
||||
"postCreateCommand": "go version",
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"files.autoSave": "afterDelay",
|
||||
"files.autoSaveDelay": 1000,
|
||||
"explorer.compactFolders": false,
|
||||
"explorer.showEmptyFolders": true
|
||||
},
|
||||
"extensions": [
|
||||
"golang.go",
|
||||
"eamodio.golang-postfix-completion",
|
||||
"quicktype.quicktype",
|
||||
"usernamehw.errorlens"
|
||||
]
|
||||
}
|
||||
},
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
38
.gitignore
vendored
Normal file
38
.gitignore
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
# Binaries
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with go test -c
|
||||
*.test
|
||||
|
||||
# Output of go build
|
||||
*.out
|
||||
|
||||
# Dependency directory
|
||||
vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# Editor / IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# VCS files
|
||||
COMMIT.md
|
||||
RELEASE.md
|
||||
29
CHANGELOG.md
Normal file
29
CHANGELOG.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this module will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this module adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.9.0] - 2026-03-18
|
||||
|
||||
### Added
|
||||
|
||||
- `Config` struct — Valkey connection settings loaded from environment variables: `VK_ADDRS` (required, comma-separated server addresses), `VK_PASSWORD` (authentication password), `VK_DB` (database index, default `0`), `VK_CLIENT_CACHE_MB` (per-connection client-side cache size in MB, default `0` disables caching)
|
||||
- `Provider` interface — `Client() valkey.Client`; for consumers that only need access to the native `valkey-go` client
|
||||
- `Component` interface — embeds `launcher.Component`, `health.Checkable`, and `Provider`; the full lifecycle-managed surface registered with the launcher
|
||||
- `New(logger logz.Logger, cfg Config) Component` — constructor; returns a `Component` ready for `lc.Append`
|
||||
- `OnInit` — constructs the `valkey-go` client from `Config`; enables client-side caching when `CacheSizeEachConn > 0`
|
||||
- `OnStart` — verifies connectivity by issuing a `PING` command; returns an error if the ping fails
|
||||
- `OnStop` — calls `client.Close()` for graceful shutdown
|
||||
- `HealthCheck(ctx context.Context) error` — issues `PING`; returns an error if the client is nil or the command fails
|
||||
- `Name() string` — returns `"valkey"`
|
||||
- `Priority() health.Level` — returns `health.LevelDegraded`; a cache outage degrades service rather than halting it
|
||||
|
||||
### Design Notes
|
||||
|
||||
- The native `valkey-go` client is exposed directly via `Client()` with no wrapping or re-exported command subset; callers use the full command-builder API and own all serialisation and key namespacing.
|
||||
- `Provider` / `Component` split follows the framework pattern: inject `Provider` into repositories and services, inject `Component` only at the lifecycle registration site.
|
||||
- Health priority is `LevelDegraded` by design — callers must handle cache misses by falling back to the primary datastore rather than treating a Valkey outage as fatal.
|
||||
|
||||
[0.9.0]: https://code.nochebuena.dev/go/valkey/releases/tag/v0.9.0
|
||||
92
CLAUDE.md
Normal file
92
CLAUDE.md
Normal file
@@ -0,0 +1,92 @@
|
||||
# valkey
|
||||
|
||||
Valkey (Redis-compatible) client with launcher lifecycle and health check integration.
|
||||
|
||||
## Purpose
|
||||
|
||||
Manages the lifecycle of a `valkey-go` client: constructs it from config, verifies
|
||||
connectivity at startup, exposes the native client to consumers, and closes it on shutdown.
|
||||
Provides a health check via `PING`. Does not add serialisation, key namespacing, or any
|
||||
caching policy on top of the native client.
|
||||
|
||||
## Tier & Dependencies
|
||||
|
||||
**Tier 3 (infrastructure)** — depends on:
|
||||
- `code.nochebuena.dev/go/health` (Tier 2)
|
||||
- `code.nochebuena.dev/go/launcher` (Tier 2)
|
||||
- `code.nochebuena.dev/go/logz` (Tier 1)
|
||||
- `github.com/valkey-io/valkey-go` (native Valkey client)
|
||||
|
||||
Does **not** depend on `xerrors` — errors from the valkey-go client are returned as-is.
|
||||
|
||||
## Key Design Decisions
|
||||
|
||||
- **Native client exposure**: `Client() vk.Client` returns the native `valkey-go` client
|
||||
directly. No wrapper, no re-exported command subset. Callers use the full valkey-go command
|
||||
builder API. See ADR-001.
|
||||
- **No serialisation helpers**: The module has no `SetJSON`, `GetJSON`, or similar helpers.
|
||||
Callers marshal and unmarshal their own data. See ADR-002.
|
||||
- **Health priority is Degraded**: `Priority()` returns `health.LevelDegraded`, not
|
||||
`LevelCritical`. A Valkey outage degrades service but should not always halt it,
|
||||
depending on whether the caller falls back to the primary datastore.
|
||||
- **Optional client-side caching**: `Config.CacheSizeEachConn` (in MB) enables valkey-go's
|
||||
built-in client-side cache. Setting it to 0 (the default) disables the cache entirely.
|
||||
- **Duck-typed Logger**: The internal `logger` field is typed as `logz.Logger`, the shared
|
||||
interface from the `logz` module (ADR-001 global pattern).
|
||||
|
||||
## Patterns
|
||||
|
||||
**Lifecycle registration:**
|
||||
```go
|
||||
vk := valkey.New(logger, cfg)
|
||||
lc.Append(vk) // registers OnInit / OnStart / OnStop
|
||||
```
|
||||
|
||||
**Accessing the client in a repository:**
|
||||
```go
|
||||
type cacheRepo struct {
|
||||
provider valkey.Provider
|
||||
}
|
||||
|
||||
func (r *cacheRepo) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
client := r.provider.Client()
|
||||
result := client.Do(ctx, client.B().Get().Key(key).Build())
|
||||
return result.AsBytes()
|
||||
}
|
||||
```
|
||||
|
||||
**Writing with TTL:**
|
||||
```go
|
||||
client := provider.Client()
|
||||
cmd := client.B().Set().Key(key).Value(val).Ex(300).Build()
|
||||
if err := client.Do(ctx, cmd).Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
**Health check registration:**
|
||||
```go
|
||||
health.Register(vkComponent) // satisfies health.Checkable via Name()/Priority()/HealthCheck()
|
||||
```
|
||||
|
||||
## What to Avoid
|
||||
|
||||
- Do not add serialisation helpers to this module. Keep marshal/unmarshal in the caller or
|
||||
in a separate cache repository layer.
|
||||
- Do not define custom interfaces that re-export a subset of `vk.Client` methods here.
|
||||
If a consumer needs a minimal testable interface, define it in the consumer package.
|
||||
- Do not call `Client()` before `OnInit` has run — it will return `nil`.
|
||||
- Do not treat `health.LevelDegraded` as if it were `LevelCritical`. Design callers to
|
||||
handle cache misses gracefully rather than depending on Valkey for correctness.
|
||||
|
||||
## Testing Notes
|
||||
|
||||
- Unit tests (`valkey_test.go`) do not require a running Valkey server. They test lifecycle
|
||||
behaviour (nil client safety, name, priority) without real network calls.
|
||||
- `TestComponent_OnInit_InvalidAddr` verifies that an empty address slice does not panic.
|
||||
Whether `OnInit` returns an error depends on the valkey-go implementation; the test
|
||||
documents the accepted behaviour.
|
||||
- Integration tests requiring a live Valkey instance are outside this module and belong in
|
||||
a higher-tier test suite.
|
||||
- `compliance_test.go` (package `valkey_test`) asserts `New(...)` satisfies `Component`
|
||||
at compile time.
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 NOCHEBUENADEV
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
8
compliance_test.go
Normal file
8
compliance_test.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package valkey_test
|
||||
|
||||
import (
|
||||
"code.nochebuena.dev/go/logz"
|
||||
"code.nochebuena.dev/go/valkey"
|
||||
)
|
||||
|
||||
var _ valkey.Component = valkey.New(logz.New(logz.Options{}), valkey.Config{})
|
||||
3
doc.go
Normal file
3
doc.go
Normal file
@@ -0,0 +1,3 @@
|
||||
// Package valkey provides a Valkey (Redis-compatible) client with launcher lifecycle
|
||||
// and health check integration.
|
||||
package valkey
|
||||
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).
|
||||
12
go.mod
Normal file
12
go.mod
Normal file
@@ -0,0 +1,12 @@
|
||||
module code.nochebuena.dev/go/valkey
|
||||
|
||||
go 1.25
|
||||
|
||||
require (
|
||||
code.nochebuena.dev/go/health v0.9.0
|
||||
code.nochebuena.dev/go/launcher v0.9.0
|
||||
code.nochebuena.dev/go/logz v0.9.0
|
||||
github.com/valkey-io/valkey-go v1.0.54
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.29.0 // indirect
|
||||
20
go.sum
Normal file
20
go.sum
Normal file
@@ -0,0 +1,20 @@
|
||||
code.nochebuena.dev/go/health v0.9.0 h1:x0UKjC7CHAE3AgwyFzCyjmGJIjoLBBxeOHxXuqpbKwI=
|
||||
code.nochebuena.dev/go/health v0.9.0/go.mod h1:f3IsNtU60JSn5yXmBBh9XOvr5pRyEah5+wS4tjDQZso=
|
||||
code.nochebuena.dev/go/launcher v0.9.0 h1:dJHonA9Xm03AQKK0919FJaQn9ZKHZ+RZfB9yxjnx3TA=
|
||||
code.nochebuena.dev/go/launcher v0.9.0/go.mod h1:IBtntmbnyddukjEhxlc7Ysdzz9nZsnd9+8FzAIHt77g=
|
||||
code.nochebuena.dev/go/logz v0.9.0 h1:wfV7vtI4V/8ED7Hm31Fbql7Y5iOGrlHN4X8Z5ajTZZE=
|
||||
code.nochebuena.dev/go/logz v0.9.0/go.mod h1:qODhSbKb+tWE7rdhHLcKweiP5CgwIaWoZxadCT3bQV8=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
|
||||
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
|
||||
github.com/valkey-io/valkey-go v1.0.54 h1:pmFRGcMRJW8mHvsWLd/2MSgY6i3WNygpUl904KUaxao=
|
||||
github.com/valkey-io/valkey-go v1.0.54/go.mod h1:NE+C8cjb3+XvLazNhiorcLJGhJa9MBAkFNoAW/48/fk=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
92
valkey.go
Normal file
92
valkey.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package valkey
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
vk "github.com/valkey-io/valkey-go"
|
||||
|
||||
"code.nochebuena.dev/go/health"
|
||||
"code.nochebuena.dev/go/launcher"
|
||||
"code.nochebuena.dev/go/logz"
|
||||
)
|
||||
|
||||
// Provider is the minimal interface for consumers that only need the valkey client.
|
||||
type Provider interface {
|
||||
Client() vk.Client
|
||||
}
|
||||
|
||||
// Component adds lifecycle management and health check to Provider.
|
||||
type Component interface {
|
||||
launcher.Component
|
||||
health.Checkable
|
||||
Provider
|
||||
}
|
||||
|
||||
// Config holds Valkey connection settings.
|
||||
type Config struct {
|
||||
Addrs []string `env:"VK_ADDRS,required" envSeparator:","`
|
||||
Password string `env:"VK_PASSWORD"`
|
||||
SelectDB int `env:"VK_DB" envDefault:"0"`
|
||||
CacheSizeEachConn int `env:"VK_CLIENT_CACHE_MB" envDefault:"0"` // MB; 0 = disable
|
||||
}
|
||||
|
||||
type vkComponent struct {
|
||||
cfg Config
|
||||
logger logz.Logger
|
||||
client vk.Client
|
||||
}
|
||||
|
||||
// New returns a valkey Component. Call lc.Append(vk) to manage its lifecycle.
|
||||
func New(logger logz.Logger, cfg Config) Component {
|
||||
return &vkComponent{cfg: cfg, logger: logger}
|
||||
}
|
||||
|
||||
func (v *vkComponent) OnInit() error {
|
||||
opts := vk.ClientOption{
|
||||
InitAddress: v.cfg.Addrs,
|
||||
Password: v.cfg.Password,
|
||||
SelectDB: v.cfg.SelectDB,
|
||||
}
|
||||
if v.cfg.CacheSizeEachConn > 0 {
|
||||
opts.CacheSizeEachConn = v.cfg.CacheSizeEachConn * 1024 * 1024
|
||||
}
|
||||
client, err := vk.NewClient(opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("valkey: failed to create client: %w", err)
|
||||
}
|
||||
v.client = client
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *vkComponent) OnStart() error {
|
||||
v.logger.Info("valkey: verifying connection")
|
||||
if v.client == nil {
|
||||
return fmt.Errorf("valkey: client not initialized")
|
||||
}
|
||||
if err := v.client.Do(context.Background(), v.client.B().Ping().Build()).Error(); err != nil {
|
||||
return fmt.Errorf("valkey: ping failed: %w", err)
|
||||
}
|
||||
v.logger.Info("valkey: connected")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *vkComponent) OnStop() error {
|
||||
v.logger.Info("valkey: closing client")
|
||||
if v.client != nil {
|
||||
v.client.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *vkComponent) Client() vk.Client { return v.client }
|
||||
|
||||
func (v *vkComponent) HealthCheck(ctx context.Context) error {
|
||||
if v.client == nil {
|
||||
return fmt.Errorf("valkey: client not initialized")
|
||||
}
|
||||
return v.client.Do(ctx, v.client.B().Ping().Build()).Error()
|
||||
}
|
||||
|
||||
func (v *vkComponent) Name() string { return "valkey" }
|
||||
func (v *vkComponent) Priority() health.Level { return health.LevelDegraded }
|
||||
46
valkey_test.go
Normal file
46
valkey_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package valkey
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.nochebuena.dev/go/health"
|
||||
"code.nochebuena.dev/go/logz"
|
||||
)
|
||||
|
||||
func newLogger() logz.Logger { return logz.New(logz.Options{}) }
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
if New(newLogger(), Config{}) == nil {
|
||||
t.Fatal("New returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponent_Name(t *testing.T) {
|
||||
c := New(newLogger(), Config{}).(health.Checkable)
|
||||
if c.Name() != "valkey" {
|
||||
t.Errorf("want valkey, got %s", c.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponent_Priority(t *testing.T) {
|
||||
c := New(newLogger(), Config{}).(health.Checkable)
|
||||
if c.Priority() != health.LevelDegraded {
|
||||
t.Error("Priority() != LevelDegraded")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponent_OnStop_NilClient(t *testing.T) {
|
||||
c := &vkComponent{logger: newLogger()}
|
||||
if err := c.OnStop(); err != nil {
|
||||
t.Errorf("OnStop with nil client: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponent_OnInit_InvalidAddr(t *testing.T) {
|
||||
// An empty Addrs slice should cause NewClient to fail or produce an error.
|
||||
c := New(newLogger(), Config{Addrs: []string{}})
|
||||
err := c.OnInit()
|
||||
// valkey-go may not error on empty addr until first command;
|
||||
// just verify it doesn't panic and returns a component.
|
||||
_ = err
|
||||
}
|
||||
Reference in New Issue
Block a user