IRateCache Interface
Definition
- Namespace
- Bodu.Financial.ExchangeRates.Caching
- Assembly
- Bodu.Financial.ExchangeRates.Caching.dll
- Package
- Bodu.Financial.ExchangeRates.Caching 1.0.0
- Source
- IRateCache.cs
Persists fetched exchange rates for a single provider in a pair-keyed store so they need not be re-fetched while fresh.
public interface IRateCache
- Extension Methods
Remarks
A cache instance is bound to exactly one provider, exposed through Provider and fixed at construction rather than supplied on each call. Group several providers by composing one cache (and one caching provider) per provider behind an aggregating provider.
The cache owns expiry: callers supply the caching duration on each call, and the cache returns only fresh
rows from GetRates(CurrencyPair, TimeSpan, DateTimeOffset) and prunes stale rows when it merges on Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset), so the backing store
self-cleans over time.
Alongside rate rows, the cache persists coverage: the date ranges that were actually fetched, recorded via RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset) and read back as a DateRangeCoverage via GetCoverage(CurrencyPair, TimeSpan, DateTimeOffset). Coverage is what makes a range lookup correct - a sparse set of rows can span a window without every interior day having been fetched, so a range is served from the cache only when its coverage contains the whole window.
A fetched range writes both halves at once through StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset), which merges the fetched rows and records the covered window as one atomic unit. Splitting that into a separate Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset) and RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset) risks persisting coverage without its rows on a backend that silently swallows a write failure, which would let a later range lookup report a false hit and return incomplete data as if complete. Treat StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset) as the primary range-fetch contract every backend must satisfy; Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset) (the single-date miss path) and RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset) are the lower-level halves, intended for the cache's own composition rather than a provider fetch path.
Implementations are expected to be resilient: a cache failure should manifest as an empty result or a no-op rather than an exception that breaks rate retrieval.
Properties
Provider
Gets the name of the provider this cache stores rates for.
string Provider { get; }
Property Value
- string
The provider identifier the cache is bound to.
Methods
GetCoverage(CurrencyPair, TimeSpan, DateTimeOffset)
Returns the union of the still-fresh coverage windows recorded for the supplied pair, evaluated against
asOf.
DateRangeCoverage GetCoverage(CurrencyPair pair, TimeSpan duration, DateTimeOffset asOf)
Parameters
pairCurrencyPairThe currency pair.
durationTimeSpanThe duration a recorded coverage window remains fresh after it was fetched.
asOfDateTimeOffsetThe instant against which coverage freshness is evaluated.
Returns
- DateRangeCoverage
A DateRangeCoverage describing the days known to have been fetched and still fresh; empty when no fresh coverage exists.
Remarks
Coverage answers which days were actually fetched, not merely which days have a cached rate. A range lookup should be served from the cache only when this coverage Contains(DateOnly, DateOnly) the whole requested window, so an interior day that was never fetched forces a refetch rather than being served from a sparse set of rows.
GetRates(CurrencyPair, TimeSpan, DateTimeOffset)
Returns the fresh cached rates for the supplied pair, evaluated against asOf.
IReadOnlyList<CachedRate> GetRates(CurrencyPair pair, TimeSpan duration, DateTimeOffset asOf)
Parameters
pairCurrencyPairThe currency pair.
durationTimeSpanThe duration a cached rate remains fresh after it was cached.
asOfDateTimeOffsetThe instant against which freshness is evaluated.
Returns
- IReadOnlyList<CachedRate>
The fresh cached rates ordered by date, or an empty list when none are fresh or available.
RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset)
Records that the inclusive range start..end was fetched for the supplied
pair, stamping the window at asOf and pruning windows that are no longer fresh under
duration.
void RecordCoverage(CurrencyPair pair, DateOnly start, DateOnly end, TimeSpan duration, DateTimeOffset asOf)
Parameters
pairCurrencyPairThe currency pair.
startDateOnlyThe inclusive first date of the fetched range.
endDateOnlyThe inclusive last date of the fetched range.
durationTimeSpanThe duration a recorded coverage window remains fresh after it was fetched.
asOfDateTimeOffsetThe instant the fetched window is stamped with and against which stale windows are pruned.
Remarks
This is a low-level building block that records the coverage half only, with no rows. It exists for the cache's own composition and is not the path a provider fetch should use: recording coverage here and writing rows through a separate Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset) risks persisting coverage a backend later serves as a false hit. A range fetch must instead use StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset), which writes both halves atomically.
Exceptions
- ArgumentOutOfRangeException
Thrown when
startis later thanend.
Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset)
Stores rates for the supplied pair, merging with any existing entry so the most recently cached rate wins per
date, and pruning rows that are no longer fresh under duration.
void Store(CurrencyPair pair, IReadOnlyList<CachedRate> rates, TimeSpan duration, DateTimeOffset asOf)
Parameters
pairCurrencyPairThe currency pair.
ratesIReadOnlyList<CachedRate>The rates to store.
durationTimeSpanThe duration a cached rate remains fresh after it was cached.
asOfDateTimeOffsetThe instant against which stale rows are pruned.
Remarks
This stores rate rows only and records no coverage window, so it is the path a single-date miss caches its resolved row through. Because no coverage is recorded, a later range lookup that spans those dates still refetches rather than serving from these rows - by design, since only a range fetch (through StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset)) establishes the contiguous coverage a range serve requires.
StoreFetchedRange(CurrencyPair, IReadOnlyList<CachedRate>, DateOnly, DateOnly, TimeSpan, DateTimeOffset)
Atomically merges rows into the pair's cached rows and records the inclusive range
start..end as covered, persisting both halves together or neither.
RateCacheWriteStatus StoreFetchedRange(CurrencyPair pair, IReadOnlyList<CachedRate> rows, DateOnly start, DateOnly end, TimeSpan duration, DateTimeOffset asOf)
Parameters
pairCurrencyPairThe currency pair.
rowsIReadOnlyList<CachedRate>The rows fetched for the range, which may be empty when the fetch returned no observation.
startDateOnlyThe inclusive first date of the fetched range.
endDateOnlyThe inclusive last date of the fetched range.
durationTimeSpanThe duration a cached row and a recorded coverage window remain fresh after they were cached or fetched.
asOfDateTimeOffsetThe instant newly cached rows and the fetched window are stamped with and against which stale rows and windows are pruned.
Returns
- RateCacheWriteStatus
Stored when both halves were persisted; Failed when a storage error was swallowed and nothing was persisted; Skipped for a cache that intentionally stores nothing.
Remarks
The coverage window is recorded even when rows is empty: a successful fetch that returned no
observation (a weekend, a holiday, a true gap) must still mark the window covered so a later lookup of the same
window is served rather than refetched.
The merge follows the same most-recent-per-date rule as Store(CurrencyPair, IReadOnlyList<CachedRate>, TimeSpan, DateTimeOffset) and the same window pruning as RecordCoverage(CurrencyPair, DateOnly, DateOnly, TimeSpan, DateTimeOffset). The write is atomic per pair - under the per-pair lock for the in-memory and file caches, in one transaction for SQLite, and as one read-modify-write of the per-pair blob for a distributed cache - so a reader never observes coverage without its rows. As with the other write paths, a swallowed storage error degrades to Failed rather than throwing; argument validation still throws.
Exceptions
- ArgumentNullException
Thrown when
rowsis null.- ArgumentOutOfRangeException
Thrown when
startis later thanend.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |