Skip to content

feat(cache): AddNamedCaching, a complete second caching stack on its own Redis connection - #182

Open
cosmin-staicu wants to merge 1 commit into
mainfrom
feat/named-caching
Open

feat(cache): AddNamedCaching, a complete second caching stack on its own Redis connection#182
cosmin-staicu wants to merge 1 commit into
mainfrom
feat/named-caching

Conversation

@cosmin-staicu

@cosmin-staicu cosmin-staicu commented Sep 12, 2026

Copy link
Copy Markdown
Member

Supersedes #181, which stays open for reference as the same capability done without touching the library. This PR is the one to merge; it carries the sample and docs from that branch plus the library API.

What

services.AddNamedCaching(name, configuration, chain) registers a complete second caching stack on its own Redis server, reached through keyed services under name:

builder.Services
    .AddNamedCaching("SecondaryRedis", builder.Configuration, b => b
        .AddBroadcast().AddRedis().AddInMemoryRedis().AddMemory().AddQueueInMemoryRedis())   // no AddRedisConnection: the stack adds its own
    .ExposeQueueCaches();                                                                     // UiPath.Caching.Queue

public class OrdersService([FromKeyedServices("SecondaryRedis")] ICacheFactory cold,
                           [FromKeyedServices("SecondaryRedis")] IDistributedLock coldLock,
                           [FromKeyedServices("SecondaryRedis")] ICache<Order> orders) { ... }
"Caching": {
  "Connections": {
    "Redis":          { "ConnectionString": "redis-a:6379", "WarmUpOnStart": true },
    "SecondaryRedis": { "ConnectionString": "redis-b:6379" }
  }
}

The library wires one Redis connection per container, and the classes behind IDistributedLock, connection warm-up and IDistributedCache are internal, so until now a consumer could only get a second stack by hand-building a child container.

  • Same configuration, other connection. The stack reads the same Caching section as the primary, so every cache option is shared. Its connection is Connections:{name} bound over Connections:Redis, so a connection key it leaves unset is inherited. A separate section is available through sectionName.
  • Exposed by default: ICacheFactory, ICache, IHashCache, ICache<T>, IHashCache<T>, ICachePolicyFactory, IDistributedLock, ILocalLock, ITopicFactory, IRedisConnector, IRedisPlannedMaintenance — each absent when the chain does not register it, as on the unkeyed side. INamedCaching.Expose<T>() reaches anything else (IDistributedCache, …); ExposeQueueCaches() adds ISetCache, ISetCache<T>, IQueueCacheFactory.
  • Own container. The stack has its own connector, caches, L1 memory caches, locks, topics and hosted services (started with the host, disposed with the application container). Logging, telemetry, clock, key masking and JsonSerializerOptions are forwarded from the application container. configureServices adds registrations to the stack (the sample passes its OpenTelemetry multiplexer factory); configureConnection covers connection settings that need code.
  • Guard rails, each naming the stack: an empty connection section, the name Redis and a duplicate name are refused at registration; a chain that calls AddRedisConnection, a ConnectionString equal to the application's resolved primary one, and a KeyCasing that differs from the application's are refused when the stack is first resolved. The connection guard compares resolved options on both sides and never names the value, which can carry credentials.

Changes

  • src/UiPath.Caching/Config/: INamedCaching.cs, NamedCachingCollectionExtensions.cs, NamedCaching.cs (stack container, hosted-service forwarder, keyed NamedCache<T>/NamedHashCache<T>).
  • src/UiPath.Caching.Queue/Config/NamedQueueCachingExtensions.cs: ExposeQueueCaches() and keyed NamedSetCache<T>.
  • PublicAPI.Unshipped.txt in both packages.
  • Sample: SecondaryCaching.cs is the one call plus the OpenTelemetry hook; a Secondary* controller twin for every surface; new SetCache and DistributedLock controllers on both stacks; the Aspire host provisions a second Redis container and injects its connection string.
  • Docs: recipes/second-redis-connection.md, the Connections:{name} subsection of reference/settings.md, appsettings.all.json, README, concepts, sample-app, changelog.
  • Tests: Config/NamedCachingTests.cs (21) — separation of every Redis-facing service and of local memory, shared configuration and connection inheritance, configureConnection, a separate section, every guard rail, forwarding, Expose, optional services, a disabled stack, the hosted-service bridge, and disposal.

Verified

  • Full suite on both targets: net8.0 1721 passed, net10.0 1742 passed, 0 failed.
  • Live with the Aspire AppHost (two Redis containers, two sample machines): sets, strings and hashes on both stacks; a value written through the secondary on machine 1 is read on machine 2 through the secondary and is absent through the primary; on one machine a primary L1 hit is a secondary miss; a lock held on machine 1's secondary is refused on machine 2's; keys land only on their own container.
  • Copilot review: approval recommended, 17/17 files, 0 comments, after a first round of 5 findings (4 fixed, 1 answered).

🤖 Generated with Claude Code

https://claude.ai/code/session_01K2d7HhcRTtYYHgCKAD1hzm

@github-actions github-actions Bot added the needs-cla-review A maintainer should assess whether a signed CLA is required (see CONTRIBUTING.md) label Sep 12, 2026
@github-actions

Copy link
Copy Markdown

🔎 Maintainer heads-up: automated triage flagged this PR as potentially material, so it may need a signed CLA in addition to the DCO sign-off.

Strong signals

  • adds public API surface (PublicAPI.Unshipped.txt in src/UiPath.Caching.Queue, src/UiPath.Caching)

Other signals

  • large production change (+283 lines under src/)

This is advisory only — the bot does not decide. Please judge against the CLA criteria (material, product-critical, patent-sensitive, corporate contributor, broad commercial use). Note that thresholds can be gamed by splitting PRs, so use your judgement.

  • If a CLA is needed → add the cla-required label (a contributor comment with signing steps is posted automatically).
  • If it is not needed → replace needs-cla-review with cla-not-required so later pushes don't re-flag it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Child-service ownership and validation gaps can cause double disposal and permit invalid named-stack configurations.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds keyed named caching stacks backed by independent Redis connections.

Changes:

  • Introduces AddNamedCaching, service exposure, lifecycle forwarding, and queue integration.
  • Adds guardrails and comprehensive named-stack tests.
  • Migrates the sample and documentation to the new API.
File summaries
File Description
tests/UiPath.Caching.Tests/Config/SecondCachingContainerTests.cs Removes prototype bridge tests.
tests/UiPath.Caching.Tests/Config/NamedCachingTests.cs Tests named-stack behavior and validation.
src/UiPath.Caching/PublicAPI.Unshipped.txt Records the new public API.
src/UiPath.Caching/Config/NamedCachingCollectionExtensions.cs Registers named caching stacks.
src/UiPath.Caching/Config/NamedCaching.cs Implements containers, exposure, and lifecycle.
src/UiPath.Caching/Config/INamedCaching.cs Defines the fluent exposure API.
src/UiPath.Caching.Queue/PublicAPI.Unshipped.txt Records queue exposure API.
src/UiPath.Caching.Queue/Config/NamedQueueCachingExtensions.cs Exposes keyed queue caches.
samples/UiPath.Caching.Sample/SecondaryCaching.cs Adopts AddNamedCaching.
samples/UiPath.Caching.Sample/appsettings.all.json Documents named connection settings.
README.md Adds a named-stack example.
docs/sample-app.md Updates sample guidance.
docs/reference/settings.md Documents Connections:{name}.
docs/recipes/second-redis-connection.md Rewrites the second-connection recipe.
docs/index.md Updates the recipe index.
docs/concepts.md Introduces named stacks conceptually.
CHANGELOG.md Announces the feature.
Review details

Suppressed comments (1)

src/UiPath.Caching/Config/NamedCaching.cs:118

  • This guard checks only a freshly bound section before configureServices and the builder chain run. Either callback can register a later Configure/PostConfigure<CacheOptions> that changes KeyCasing; the child options validator will then seed CacheKey.DefaultCasing with that final value and silently alter the primary stack despite this guard. Validate the finalized child options against the root casing before allowing the child provider to be used.
        var options = new CacheOptions();
        section.Bind(options);
        if (root.GetService<IOptions<CacheOptions>>()?.Value.KeyCasing is { } casing && casing != options.KeyCasing)
  • Files reviewed: 17/17 changed files
  • Comments generated: 5
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/UiPath.Caching/Config/NamedCaching.cs
Comment thread src/UiPath.Caching/Config/NamedCaching.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCaching.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCaching.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCachingCollectionExtensions.cs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The API, lifecycle management, guard rails, tests, sample, and documentation are consistent and no unresolved correctness issues were found.

Review details
  • Files reviewed: 17/17 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@cosmin-staicu
cosmin-staicu changed the base branch from sample/second-redis-connection to main September 12, 2026 14:52
@cosmin-staicu cosmin-staicu reopened this Sep 12, 2026
@cosmin-staicu
cosmin-staicu requested a balanced review from Copilot September 12, 2026 14:58

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Source layout violations will fail the warning-as-error CI build, and the README example selects the wrong configuration path.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 29/29 changed files
  • Comments generated: 5
  • Review effort level: Balanced

Comment thread src/UiPath.Caching.Queue/Config/NamedQueueCachingExtensions.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCaching.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCaching.cs Outdated
Comment thread README.md Outdated
Comment thread samples/UiPath.Caching.Sample/Controllers/SecondaryCacheController.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Configuration validation can be bypassed, invalid connections are accepted, and hosted-service shutdown may leave services running.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 29/29 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread src/UiPath.Caching/Config/NamedCachingContainer.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCachingCollectionExtensions.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCachingHostedService.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Typed wrappers discard custom key strategies, and rejected key-casing configuration can transiently alter process-wide behavior.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 30/30 changed files
  • Comments generated: 4
  • Review effort level: Balanced

Comment thread src/UiPath.Caching.Queue/Config/NamedSetCacheOfT.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCacheOfT.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedCachingContainer.cs Outdated
Comment thread src/UiPath.Caching/Config/NamedHashCacheOfT.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation is internally consistent, comprehensively tested, and aligned with its documented behavior.

Review details
  • Files reviewed: 30/30 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Standalone named stacks can overwrite the process-wide key casing with incompatible values.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 30/30 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread src/UiPath.Caching/Config/NamedCachingContainer.cs Outdated
Comment thread tests/UiPath.Caching.Tests/Config/NamedCachingTests.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The lock sample permits unsupported delay durations that can acquire a lock and then fail with a server error.

Get a fresh assessment by requesting another Copilot review.

Review details
  • Files reviewed: 30/30 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation, public API, tests, sample, and documentation are consistent, and prior review findings are addressed.

Review details
  • Files reviewed: 30/30 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

… connection

The library wires one Redis connection per container, and the classes behind
IDistributedLock, warm-up and IDistributedCache are internal, so a consumer
could not assemble a second stack by hand.

services.AddNamedCaching(name, configuration, chain) builds one in its own
container from the same "Caching" section, and exposes its caches, typed
caches, locks, topics and connector as keyed services under the name;
INamedCaching.Expose<T>() reaches the rest, and the Queue package's
ExposeQueueCaches() adds the set caches. Its connection is Connections:{name}
laid over Connections:Redis, so unset keys are inherited. Logging, telemetry,
the clock and key masking come from the application; nothing Redis-facing and
no memory cache does, so the two stacks keep separate L1s.

Refused: no ConnectionString of its own, the name "Redis", a duplicate name, a
chain adding its own connection, a connection resolving to nothing or to the
application's, a KeyCasing differing from it, and no application caching at all.

The sample runs two stacks against two Redis containers from the Aspire host.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K2d7HhcRTtYYHgCKAD1hzm
Signed-off-by: Cosmin Staicu <cosmin.staicu@uipath.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The implementation, lifecycle handling, validation, API baselines, tests, samples, and documentation are consistent, with no unresolved defects identified.

Review details
  • Files reviewed: 30/30 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-cla-review A maintainer should assess whether a signed CLA is required (see CONTRIBUTING.md)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants