Class: ConvertSdk::Stores::RedisStore

Inherits:
Object
  • Object
show all
Defined in:
lib/convert_sdk/stores/redis_store.rb

Overview

A first-party, in-tree store adapter backed by Redis — the cross-process answer to MemoryStore's per-process limitation.

Why Redis (FR49)

MemoryStore keeps state in a single process. Puma clusters, Sidekiq worker fleets, and Lambda invocations each run in separate processes, so sticky bucketing (Story 2.11) and goal deduplication (Story 4.3) that round-trip through a MemoryStore are inconsistent across the fleet. RedisStore shares that state through a Redis instance, giving every process the same view.

Zero gemspec footprint

The redis gem is the user's dependency, never the SDK's: it is NOT a gemspec runtime dependency and is +require+-d lazily inside #initialize — and only when a client is built from connection options. Requiring this file (which lib/convert_sdk.rb does unconditionally) therefore never pulls in redis, so require "convert_sdk" stays green for users who do not install it. If a caller asks RedisStore to build its own client without redis installed, instantiation raises an actionable error naming the gem to add — a wiring-time programmer error, sanctioned in the same class as +ConvertSdk.create+'s argument validation, NOT a business path.

Construction

# Preferred: inject an existing client (connection reuse / pooling).
# No `require "redis"`, no `Redis.new` — works even where the adapter
# file is loaded without the gem present.
store = ConvertSdk::Stores::RedisStore.new(redis: Redis.new(url: ...))

# Or pass connection options; the adapter lazily requires `redis` and
# constructs the client itself.
store = ConvertSdk::Stores::RedisStore.new(url: "redis://localhost:6379/0")

An optional key_prefix namespaces every key (default "convert:") so the SDK's keys do not collide with other tenants of the same Redis database.

Thin adapter — resilience lives upstream

This adapter is serialization + connection only. It does NOT rescue Redis client exceptions: DataStoreManager (Story 2.1) already wraps every +get+/+set+ in a rescue-log passthrough, degrading a raising store to +nil+/no-op instead of crashing the host. Duplicating that rescue here would swallow errors the manager is responsible for logging.

Cross-process consistency caveat

Visitor-data merges are a read-modify-write. In-process that sequence is atomic under DataStoreManager's mutex, but across processes sharing one Redis there is no such lock: concurrent writers race and the last write wins. This matches the JS SDK contract. The SDK does NOT use Lua scripts or +WATCH+/+MULTI+ to close that race — that is deliberately out of scope.

Sidekiq / Lambda deployment guidance: see the Epic 5 documentation.

Constant Summary collapse

DEFAULT_KEY_PREFIX =

Default namespace prepended to every key written to Redis.

"convert:"

Instance Method Summary collapse

Constructor Details

#initialize(redis: nil, key_prefix: DEFAULT_KEY_PREFIX, **options) ⇒ RedisStore

Returns a new instance of RedisStore.

Parameters:

  • redis (Object, nil) (defaults to: nil)

    an existing redis-rb-compatible client responding to +#get+/+#set+. When supplied, redis is NOT required and no new client is constructed (preferred — enables connection reuse).

  • key_prefix (String) (defaults to: DEFAULT_KEY_PREFIX)

    namespace prepended to every key (default "convert:").

  • options (Hash)

    connection options (e.g. url:) forwarded to Redis.new when no redis: client is injected. Triggers the lazy require "redis".

Raises:

  • (LoadError)

    re-raised as an actionable error when redis: is omitted and the redis gem is not installed.



76
77
78
79
# File 'lib/convert_sdk/stores/redis_store.rb', line 76

def initialize(redis: nil, key_prefix: DEFAULT_KEY_PREFIX, **options)
  @key_prefix = key_prefix
  @client = redis || build_client(options)
end

Instance Method Details

#get(key) ⇒ Object?

Read and deserialize the value stored under key.

Parameters:

  • key (String)

    the (unprefixed) lookup key.

Returns:

  • (Object, nil)

    the JSON-parsed value (string-keyed hashes, numbers, arrays, booleans), or nil when the key is absent.



86
87
88
89
# File 'lib/convert_sdk/stores/redis_store.rb', line 86

def get(key)
  raw = @client.get(namespaced(key))
  raw.nil? ? nil : JSON.parse(raw)
end

#set(key, value) ⇒ Object

Serialize value to JSON and store it under key, overwriting any existing value.

Parameters:

  • key (String)

    the (unprefixed) storage key.

  • value (Object)

    a JSON-serializable value (StoreData shape).

Returns:

  • (Object)

    the client's set return value.



97
98
99
# File 'lib/convert_sdk/stores/redis_store.rb', line 97

def set(key, value)
  @client.set(namespaced(key), JSON.generate(value))
end