feat(cache): AddNamedCaching, a complete second caching stack on its own Redis connection - #182
feat(cache): AddNamedCaching, a complete second caching stack on its own Redis connection#182cosmin-staicu wants to merge 1 commit into
Conversation
|
🔎 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
Other signals
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.
|
There was a problem hiding this comment.
🟡 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
configureServicesand the builder chain run. Either callback can register a laterConfigure/PostConfigure<CacheOptions>that changesKeyCasing; the child options validator will then seedCacheKey.DefaultCasingwith 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.
4019a9a to
9d416bf
Compare
There was a problem hiding this comment.
🟢 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
9d416bf to
6fc5db0
Compare
There was a problem hiding this comment.
🟡 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
6fc5db0 to
4befd13
Compare
There was a problem hiding this comment.
🟡 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
4befd13 to
aa24230
Compare
There was a problem hiding this comment.
🟡 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
aa24230 to
033689a
Compare
There was a problem hiding this comment.
🟡 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
033689a to
0040c19
Compare
There was a problem hiding this comment.
🟡 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
0040c19 to
49dbc22
Compare
… 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>
49dbc22 to
e75af3d
Compare
There was a problem hiding this comment.
🟢 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
|



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 undername:The library wires one Redis connection per container, and the classes behind
IDistributedLock, connection warm-up andIDistributedCacheare internal, so until now a consumer could only get a second stack by hand-building a child container.Cachingsection as the primary, so every cache option is shared. Its connection isConnections:{name}bound overConnections:Redis, so a connection key it leaves unset is inherited. A separate section is available throughsectionName.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()addsISetCache,ISetCache<T>,IQueueCacheFactory.JsonSerializerOptionsare forwarded from the application container.configureServicesadds registrations to the stack (the sample passes its OpenTelemetry multiplexer factory);configureConnectioncovers connection settings that need code.Redisand a duplicate name are refused at registration; a chain that callsAddRedisConnection, aConnectionStringequal to the application's resolved primary one, and aKeyCasingthat 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, keyedNamedCache<T>/NamedHashCache<T>).src/UiPath.Caching.Queue/Config/NamedQueueCachingExtensions.cs:ExposeQueueCaches()and keyedNamedSetCache<T>.PublicAPI.Unshipped.txtin both packages.SecondaryCaching.csis the one call plus the OpenTelemetry hook; aSecondary*controller twin for every surface; newSetCacheandDistributedLockcontrollers on both stacks; the Aspire host provisions a second Redis container and injects its connection string.recipes/second-redis-connection.md, theConnections:{name}subsection ofreference/settings.md,appsettings.all.json, README, concepts, sample-app, changelog.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
🤖 Generated with Claude Code
https://claude.ai/code/session_01K2d7HhcRTtYYHgCKAD1hzm