ข้ามไปยังเนื้อหา

Cephalon.Data.Redis

เนื้อหานี้ยังไม่ได้แปลเป็นภาษาไทย แสดงเป็นภาษาอังกฤษแทน

Maturity: M1 · Ownership: provider-managed · Family: data-and-cdc · See audit, matrix.

Cephalon.Data.Redis is the Redis key-value store companion pack for Cephalon, proving that the companion-pack pattern established by Cephalon.Data.MongoDB extends cleanly to key-value stores without any changes to Cephalon.Engine or Cephalon.Abstractions.

  • registers a singleton IConnectionMultiplexer from a StackExchange.Redis connection string, using TryAdd semantics so a host-owned multiplexer is never displaced
  • registers a scoped IOutbox backed by Redis Hash and Sorted Set structures when RegisterOutbox is enabled; key names honour the configurable KeyPrefix
  • registers a scoped IEventDispatchStore over those same Redis structures when RegisterOutbox is enabled, so staged Redis events can be read and durable dispatch outcomes can be written back through the runtime-neutral eventing contract
  • registers a scoped IInbox backed by a Redis Set when RegisterInbox is enabled; key names honour the configurable KeyPrefix
  • ensures that outbox staging is idempotent by using a Redis transaction conditioned on the Hash key not existing (Condition.KeyNotExists) — if the hash already exists, the transaction silently no-ops
  • exposes operator-facing outbox and inbox descriptors through /engine/outboxes, /engine/inboxes, and /engine/snapshot when the respective path is enabled
  • projects the outbox descriptor through the event-driven-integration technology surface as outbox-producers with provider: "redis" and mode: "sorted-set" when that technology is active
  • projects the inbox descriptor through the same technology surface as inbox-stores when the technology is active
  • publishes capability metadata data.redis, data.key-value-store, and optionally data.outbox.redis and data.inbox.redis introspectable at runtime through the manifest
  • Configuration/RedisDataOptions.cs
  • Modules/RedisDataModule.cs
  • Registration/RedisDataEngineBuilderExtensions.cs
  • Services/RedisOutbox.cs
  • Services/RedisEventDispatchStore.cs
  • Services/RedisInbox.cs
  • Services/RedisOutboxRuntimeSurfaceContributor.cs
  • Services/RedisInboxRuntimeSurfaceContributor.cs

This pack sits on top of Cephalon.Data, not in place of it. Cephalon.Data still owns the runtime-neutral IReadStore / IWriteStore dispatching surface. Cephalon.Data.Redis adds the Redis-backed outbox and inbox persistence paths that let event-driven workloads stage and track messages without a relational store, and it now also exposes the same staged outbox through IEventDispatchStore so consumer-managed or adapter-owned dispatch loops can read pending items and persist durable dispatch outcomes without Redis-specific host glue.

The slice is intentionally narrow and honest: it proves the companion-pack pattern works for key-value stores, ships an idempotent outbox using Hash + Sorted Set structures, an idempotency-guarded inbox using a Redis Set, and exposes the same runtime introspection surfaces as the MongoDB and Entity Framework providers. IReadStore and IWriteStore are not backed directly by Redis in this slice — query and command handlers should depend on Redis directly through IConnectionMultiplexer. Full CQRS query/command dispatch on top of Redis remains a later slice.

engine.AddRedisData("localhost:6379");

To enable the outbox and inbox paths:

engine.AddRedisData(
connectionString: "localhost:6379",
configure: options =>
{
options.RegisterOutbox = true;
options.RegisterInbox = true;
options.KeyPrefix = "myapp:"; // optional — prefix all Cephalon keys
});

The connectionString parameter accepts any StackExchange.Redis connection string, including options such as abortConnect=false, password=..., and ssl=true.

For configuration-driven hosts, prefer the options overload and let the pack resolve either ConnectionStringName from the root ConnectionStrings section or ConnectionString directly:

engine.AddRedisData(options =>
{
configuration.GetSection(RedisDataOptions.SectionPath).Bind(options);
options.ConnectionStringName ??= "Redis";
});
{
"ConnectionStrings": {
"Redis": "localhost:6379"
},
"Engine": {
"Data": {
"Redis": {
"ConnectionStringName": "Redis",
"KeyPrefix": "myapp:"
}
}
}
}

ConnectionStringName and ConnectionString are mutually exclusive. If both are set, the pack fails fast during service resolution. If neither is set, Redis falls back to localhost:6379.

OptionTypeDefaultDescription
ConnectionStringNamestring?nullRoot ConnectionStrings key to resolve for Redis
ConnectionStringstring?nullInline StackExchange.Redis connection string or configuration
KeyPrefixstring"cephalon:"Prefix applied to all Cephalon-managed Redis keys
RegisterOutboxboolfalseRegister IOutbox backed by a Redis Hash and Sorted Set
RegisterInboxboolfalseRegister IInbox backed by a Redis Set

The outbox uses two Redis data structures per staged message.

Message Hash — {KeyPrefix}outbox:msg:{messageId}

Section titled “Message Hash — {KeyPrefix}outbox:msg:{messageId}”
FieldTypeNotes
MessageIdstringStable logical message identifier; Hash existence is the idempotency guard
ChannelIdstringLogical channel or destination identifier
MessageTypestringLogical message type identifier
PayloadstringSerialized payload that should be delivered later
ContentTypestringPayload content type; empty string when not provided
CorrelationIdstringCorrelation identifier; empty string when not provided
TenantIdstringTenant identifier; empty string when not provided
OccurredAtUtcstringISO 8601 UTC timestamp of the domain event
CreatedAtUtcstringISO 8601 UTC timestamp when the message was staged
DispatchAttemptCountstringDispatch attempt counter; starts at 0
HeadersJsonstringSystem.Text.Json-serialized message headers dictionary
MetadataJsonstringSystem.Text.Json-serialized message metadata dictionary

Pending Sorted Set — {KeyPrefix}outbox:pending

Section titled “Pending Sorted Set — {KeyPrefix}outbox:pending”

Members are messageId values. The score is DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() at the time of staging, which provides a natural dispatch-ordering window for an external relay.

Idempotency: EnqueueAsync runs a Redis transaction conditioned on KeyNotExists(hashKey). If the hash already exists, the transaction does not execute — no error is raised. Calling EnqueueAsync twice with the same MessageId is safe.

Receipts Set — {KeyPrefix}inbox:receipts

Section titled “Receipts Set — {KeyPrefix}inbox:receipts”

The set members are raw messageId strings.

Idempotency: SADD (Redis set add) is naturally idempotent — adding an existing member is a no-op. MarkProcessedAsync always calls SADD; calling it twice for the same id is safe.

When RedisDataModule is active, the following capability keys appear in the runtime manifest:

Capability keyWhen registered
data.redisAlways
data.key-value-storeAlways
data.outbox.redisRegisterOutbox = true
data.inbox.redisRegisterInbox = true

When the event-driven-integration technology is active, the following entries appear under /engine/snapshot:

SurfaceEntry idprovider metadata
outbox-producersredis-outboxredis
inbox-storesredis-inboxredis

This pack intentionally does not claim:

  • IReadStore / IWriteStore dispatch backed by Redis — query and command handlers should use IConnectionMultiplexer directly
  • transaction-scoped outbox staging with Lua script atomic test-and-set (the current implementation uses a Redis transaction with KeyNotExists condition, which is safe against the single-entry idempotency concern but is not a WATCH-based optimistic lock)
  • pack-owned dispatch loops or broker-specific retry scheduling beyond the runtime-neutral IEventDispatchStore bridge
  • TTL or key-expiry management for outbox or inbox entries
  • Pub/Sub integration
  • Redis Cluster sharding guidance

These remain explicit later slices to keep the initial provider claim honest.