Skip to content

feat(caching): ICache port + InMemoryCache adapter (POM-512)#115

Merged
Pomdapis merged 1 commit into
mainfrom
feat/inmemory-cache
May 21, 2026
Merged

feat(caching): ICache port + InMemoryCache adapter (POM-512)#115
Pomdapis merged 1 commit into
mainfrom
feat/inmemory-cache

Conversation

@Pomdapis
Copy link
Copy Markdown
Contributor

Decision: define ICache here (Option B)

Investigation confirmed there is no existing ICache port in either Compendium.Abstractions or any sibling assembly (verified via grep across src/ and the compendium-adapter-redis@1.0.0 package contents — it ships only Idempotency/ + Projections/, no caching). So the contract is defined here, in a new per-port assembly Compendium.Abstractions.Caching, matching the project layout already used for Storage, Search, VectorStore, etc.

Summary

  • Compendium.Abstractions.Caching — new port assembly with ICache (Get/Set/Remove/Exists, optional TTL, Result<T> throughout) and CachingErrors (InvalidTtl, BackendFailure).
  • Compendium.Infrastructure/Caching/InMemoryCacheIMemoryCache-backed implementation. Optional ITenantContext injection: a non-empty TenantId scopes every key as {tenantId}:{key}. No tenant / empty tenant → keys are written verbatim. Cache misses are Result.Success(null), never failures.
  • DI: AddInMemoryCache(IServiceCollection, Action<MemoryCacheOptions>? configure = null) registers IMemoryCache (idempotent) + ICache → InMemoryCache singleton.
  • Microsoft.Extensions.Caching.{Abstractions,Memory} pinned at 9.0.16 in Directory.Packages.props.
  • No changes to the Redis adapter — that repo's README will be updated in a separate follow-up.

Files

  • src/Abstractions/Compendium.Abstractions.Caching/{ICache,CachingErrors,GlobalUsings}.cs + .csproj
  • src/Infrastructure/Compendium.Infrastructure/Caching/{InMemoryCache,ServiceCollectionExtensions}.cs
  • tests/Unit/Compendium.Abstractions.Caching.Tests/CachingErrorsTests.cs (3 tests)
  • tests/Unit/Compendium.Infrastructure.Tests/Caching/InMemoryCacheTests.cs (23 tests)
  • Directory.Packages.props, Compendium.Infrastructure.csproj, Compendium.sln

Coverage (per-class, line / branch)

Class Line Branch
Compendium.Infrastructure.Caching.InMemoryCache 100% 100%
Compendium.Infrastructure.Caching.ServiceCollectionExtensions 100% 100%
Compendium.Abstractions.Caching.CachingErrors 100% 100% (via abstraction tests)

ICache itself is an interface and contributes no IL to coverage instrumentation.

Test plan

  • dotnet build Compendium.sln -c Release0 errors, warnings unchanged from baseline (104 → 104).
  • dotnet test tests/Unit/Compendium.Infrastructure.Tests595 passed / 2 skipped (pre-existing) / 0 failed.
  • dotnet test tests/Unit/Compendium.Abstractions.Caching.Tests3 passed.
  • Architecture tests (HexagonalLayeringTests, etc.) — 37 passed, no new layering violations.
  • Full unit + architecture test pass across all 31 assemblies — 0 failures.
  • Coverage: ≥95% target met (100% line / 100% branch on the new code).

Constraints checked

  • Branch feat/inmemory-cache against main. No tag.
  • No edits to the Redis adapter repo.
  • Microsoft.Extensions.Caching.* versions match the existing 9.0.16 Microsoft.Extensions.* band.
  • Followed compendium-test-author conventions (xUnit / FluentAssertions / NSubstitute, no Thread.Sleep, TTL configuration verified via ICacheEntry substitution rather than wall-clock).

…keys (POM-512)

Introduce the framework's first cache abstraction:

- src/Abstractions/Compendium.Abstractions.Caching — new ICache port
  (Get/Set/Remove/Exists, optional TTL, Result-pattern throughout) plus
  CachingErrors (InvalidTtl, BackendFailure). Mirrors the per-port
  package layout already used for Storage, Search, etc.

- src/Infrastructure/Compendium.Infrastructure/Caching/InMemoryCache —
  IMemoryCache-backed implementation. Tenant isolation via optional
  ITenantContext: non-empty TenantId scopes every key as `{tenantId}:{key}`.
  Misses are Result.Success(null), never failures. Argument validation
  throws ArgumentException, matching the conventions of
  InMemoryIdempotencyStore.

- ServiceCollectionExtensions.AddInMemoryCache registers IMemoryCache (if
  not already) and ICache → InMemoryCache as a singleton, optionally
  configuring MemoryCacheOptions.

- Tests: 23 unit tests on InMemoryCache (100% line, 100% branch) covering
  round-trip for string/int/complex records, TTL configuration via NSubstitute
  to avoid wall-clock dependency, TTL validation, removal, existence,
  tenant prefix isolation, no-tenant pass-through, empty-tenant treated
  as no-tenant, and DI registration. 3 unit tests on CachingErrors
  (100% line, 100% branch).

Microsoft.Extensions.Caching.{Abstractions,Memory} 9.0.16 pinned in
Directory.Packages.props matching the existing 9.0.16 Microsoft.Extensions
band.

No changes to the Redis adapter — that repo's README will be updated in
a separate follow-up.
Copilot AI review requested due to automatic review settings May 21, 2026 07:50
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new caching port (ICache) under Compendium.Abstractions and provides a first-party IMemoryCache-backed adapter in Compendium.Infrastructure, along with DI registration and unit tests, to enable provider-agnostic caching across the framework.

Changes:

  • Added new Compendium.Abstractions.Caching project defining the ICache contract and standardized CachingErrors.
  • Added Compendium.Infrastructure.Caching.InMemoryCache implementation plus AddInMemoryCache(...) DI extension.
  • Updated solution/package references and added unit test projects covering the new port/errors and in-memory adapter behavior.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/Abstractions/Compendium.Abstractions.Caching/ICache.cs Defines the caching port API and behavioral contract (Result-based, TTL, miss semantics).
src/Abstractions/Compendium.Abstractions.Caching/CachingErrors.cs Adds standardized error codes/messages for caching operations.
src/Abstractions/Compendium.Abstractions.Caching/GlobalUsings.cs Adds Results global using for the new abstractions project.
src/Abstractions/Compendium.Abstractions.Caching/Compendium.Abstractions.Caching.csproj Introduces the new abstractions assembly and test visibility.
src/Infrastructure/Compendium.Infrastructure/Caching/InMemoryCache.cs Implements ICache using IMemoryCache with optional tenant key scoping.
src/Infrastructure/Compendium.Infrastructure/Caching/ServiceCollectionExtensions.cs Adds AddInMemoryCache(...) to wire up IMemoryCache + ICache via DI.
src/Infrastructure/Compendium.Infrastructure/Compendium.Infrastructure.csproj Adds project/package references needed for the caching adapter.
tests/Unit/Compendium.Infrastructure.Tests/Caching/InMemoryCacheTests.cs Adds unit tests for adapter behavior (TTL, isolation, DI registration).
tests/Unit/Compendium.Abstractions.Caching.Tests/* Adds unit tests for CachingErrors and test project scaffolding.
Directory.Packages.props Pins Microsoft.Extensions.Caching.* package versions centrally.
Compendium.sln Adds the new abstractions + test projects to the solution.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +83 to +84
_memoryCache.Set(ScopeKey(key), value, options);
return Task.FromResult(Result.Success());
Comment on lines +55 to +60
if (_memoryCache.TryGetValue(scopedKey, out var raw) && raw is T typed)
{
return Task.FromResult(Result.Success<T?>(typed));
}

return Task.FromResult(Result.Success<T?>(default));
Comment on lines +88 to +103
public Task<Result> RemoveAsync(string key, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(key);

_memoryCache.Remove(ScopeKey(key));
return Task.FromResult(Result.Success());
}

/// <inheritdoc />
public Task<Result<bool>> ExistsAsync(string key, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrWhiteSpace(key);

var exists = _memoryCache.TryGetValue(ScopeKey(key), out _);
return Task.FromResult(Result.Success(exists));
}
Comment on lines +326 to +330
var services = new ServiceCollection();
services.AddInMemoryCache(opts => opts.SizeLimit = 123);

using var sp = services.BuildServiceProvider();

@Pomdapis Pomdapis merged commit 03dfff6 into main May 21, 2026
6 checks passed
@Pomdapis Pomdapis deleted the feat/inmemory-cache branch May 21, 2026 08:01
Pomdapis added a commit that referenced this pull request May 21, 2026
… + Hugging Face (#116)

## Summary

Phase 6 shipped 4 more public adapters today. README \`## Adapters\`
catches up to 22 entries.

| New adapter | Repo |
|---|---|
| Mistral AI (EU) |
[\`compendium-adapter-mistral\`](https://github.com/sassy-solutions/compendium-adapter-mistral)
|
| DeepSeek (V3 / R1) |
[\`compendium-adapter-deepseek\`](https://github.com/sassy-solutions/compendium-adapter-deepseek)
|
| Mercury (Inception Labs) |
[\`compendium-adapter-mercury\`](https://github.com/sassy-solutions/compendium-adapter-mercury)
|
| Hugging Face Inference Endpoints |
[\`compendium-adapter-huggingface\`](https://github.com/sassy-solutions/compendium-adapter-huggingface)
|

Phase 6 also shipped a framework-internal
**\`Compendium.Abstractions.Caching\`** +
**\`Compendium.Infrastructure.Caching.InMemoryCache\`** (POM-512, see PR
#115 → \`v1.0.3\`). Not an adapter — lives in the framework. Drop-in
alternative to \`compendium-adapter-redis\` for dev / single-instance /
unit-test scenarios.

Per memory \`project_compendium_readme_adapter_index.md\`, every public
adapter ship updates this table.

## Test plan

- [ ] CI green.
- [ ] Visual render check of the updated table.

Co-authored-by: sacha <sacha@scojhconsult.com>
This was referenced May 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants