CachingRateOptions Class
Definition
- Namespace
- Bodu.Financial.ExchangeRates.Caching
- Assembly
- Bodu.Financial.ExchangeRates.Caching.dll
- Package
- Bodu.Financial.ExchangeRates.Caching 1.0.0
- Source
- CachingRateOptions.cs
Configures a CachingRateProvider: the on-disk cache location, the default time a cached rate stays fresh, and per-provider overrides of that default.
public sealed class CachingRateOptions
- Inheritance
-
CachingRateOptions
- Inherited Members
- Extension Methods
Examples
var options = new CachingRateOptions
{
CacheDirectory = "/var/cache/fx", // null/blank -> a bodu-exchange-rates temp folder
DefaultExpiry = TimeSpan.FromHours(24), // applies to any source without an override
};
// Override the default for specific sources, keyed by the name the source is cached under.
options.ProviderExpiry["RBA"] = TimeSpan.FromDays(7);
options.ProviderExpiry["Yahoo"] = TimeSpan.FromHours(1);
TimeSpan rbaExpiry = options.GetExpiry("RBA"); // 7 days
TimeSpan ecbExpiry = options.GetExpiry("ECB"); // 24 hours (the default)
Remarks
Every member carries a working default, so the options bind cleanly through Microsoft.Extensions.Options and
require no configuration for the common case.
The Cache*LogLevel members set the LogLevel at which each cache diagnostic is logged, so
consumers can re-tune verbosity per concern without category-wide log filters. The per-lookup hit and miss events
default to Information because they run on the read hot path; the range events default to
Debug. The per-serve RateProvenanceLogLevel records the lineage of every
served rate - live versus cache hit, the backend identity, and the served data's age - and also defaults to
Debug. Set any member to None to suppress that event entirely.
Constructors
CachingRateOptions()
public CachingRateOptions()
Properties
CacheDirectory
Gets or sets the directory used by the on-disk cache.
public string? CacheDirectory { get; set; }
Property Value
CacheHitLogLevel
Gets or sets the level at which a single-date lookup served from the cache is logged.
public LogLevel CacheHitLogLevel { get; set; }
Property Value
- LogLevel
The log level; defaults to Information.
CacheMissLogLevel
Gets or sets the level at which a single-date cache miss resolved from a source and then cached is logged.
public LogLevel CacheMissLogLevel { get; set; }
Property Value
- LogLevel
The log level; defaults to Information.
CacheRangeHitLogLevel
Gets or sets the level at which a range lookup served entirely from the cache is logged.
public LogLevel CacheRangeHitLogLevel { get; set; }
Property Value
CacheRangeRefetchLogLevel
Gets or sets the level at which a range lookup refetched from a source and re-cached is logged.
public LogLevel CacheRangeRefetchLogLevel { get; set; }
Property Value
DefaultExpiry
Gets or sets the default duration a cached rate stays fresh, applied to any provider without a specific override in ProviderExpiry.
public TimeSpan DefaultExpiry { get; set; }
Property Value
- TimeSpan
The default caching duration; defaults to 24 hours.
Remarks
Validation requires only a strictly positive duration; there is no upper bound. This property and the ProviderExpiry overrides accept an arbitrarily large value, so an extreme duration leaves cached rows fresh indefinitely and effectively disables expiry for the affected provider.
DefaultLookupOptions
Gets or sets the lookup options applied by the timeless GetRate(string, string) surface, which resolves the rate for the current UTC date.
public RateLookupOptions DefaultLookupOptions { get; set; }
Property Value
- RateLookupOptions
The lookup options used for timeless lookups; defaults to Exact.
ExpiryJitter
Gets or sets the maximum fraction of a pair's caching duration that is deterministically shaved off per pair, so entries warmed together do not all expire - and refetch - at the same instant.
public double ExpiryJitter { get; set; }
Property Value
- double
A fraction in
[0, 1); defaults to0, which disables jitter and preserves the exact configured expiry.
Remarks
The reduction is derived from a stable hash of the provider and pair (not from randomness), so a given pair's effective expiry is identical across instances and processes and deterministic under test. Jitter only ever shortens the duration, never extends it, so no rate is served longer than the configured expiry allows; the trade-off is that a pair may refetch up to this fraction of its expiry early. Inverse-pair probes jitter by the inverse pair's own key, matching the data actually being read.
HistoryClampLogLevel
Gets or sets the level at which a fetch skipped or clamped because of the inner source's advertised history (see RespectHistoryAvailability) is logged.
public LogLevel HistoryClampLogLevel { get; set; }
Property Value
ProviderExpiry
Gets the per-provider expiry overrides, keyed by the provider name supplied to the caching provider.
public IDictionary<string, TimeSpan> ProviderExpiry { get; }
Property Value
- IDictionary<string, TimeSpan>
A map from provider name to the duration its cached rates stay fresh. Providers absent from the map use DefaultExpiry.
RateProvenanceLogLevel
Gets or sets the level at which the provenance of every served rate is logged.
public LogLevel RateProvenanceLogLevel { get; set; }
Property Value
Remarks
This per-serve diagnostic records the lineage of each result - whether it was resolved live or from the cache, the cache backend that served it, and the served data's age - and so is richer than the per-concern hit and miss events on the read hot path. Set it to None to suppress provenance entirely.
RefreshAheadFraction
Gets or sets the fraction of a pair's effective caching duration after which a cache hit, while still served immediately, additionally schedules a single background refresh of the served data, so a hot pair is refetched before it expires and no caller ever absorbs the refetch latency.
public double RefreshAheadFraction { get; set; }
Property Value
- double
A fraction in
[0, 1); defaults to0, which disables refresh-ahead entirely and preserves the plain serve-until-expiry behaviour.
Remarks
Refresh-ahead is access-triggered: no timers run, and only a lookup that is actually served from the cache can
schedule a refresh. When a hit's served data is older than this fraction of the pair's effective (post-jitter)
expiry, at most one background refresh per pair and window is started; concurrent aged hits for the same key
join the pending refresh rather than duplicating it. Because the fraction is below 1, the refresh always
begins before the entry expires, so a continuously hot pair never surfaces a miss.
A background refresh that fails is swallowed after logging - the hit it piggybacked on was already served - and the next aged hit schedules a fresh attempt. Single-date hits served from the inverse pair's rows evaluate the threshold against the requested pair's effective expiry, a deliberate approximation that keeps the trigger cheap. Disposing the provider prevents new refreshes from being scheduled and abandons pending ones without draining them.
RespectHistoryAvailability
Gets or sets a value indicating whether the provider consults the inner source's advertised RateHistoryAvailability before delegating a miss, skipping or clamping fetches for dates the source has declared it cannot serve.
public bool RespectHistoryAvailability { get; set; }
Property Value
Remarks
The clamp applies only when the inner provider implements IHistoricalRateProvider; a non-aware inner is treated as unbounded and never skipped. A skipped single-date lookup surfaces as an ordinary miss, and a range window that starts before the advertised earliest date is fetched from that earliest date while the whole requested window is still recorded as covered, so the unavailable prefix is not refetched until normal expiry. Disable this to forward every request to the inner source unchanged.
SkipInverseRangeProbeWhenDirectCovered
Gets or sets a value indicating whether a range lookup that misses the direct pair's coverage skips the inverse-pair probe when the direct pair holds any fresh coverage at all, probing the inverse only when the direct pair's coverage is completely empty.
public bool SkipInverseRangeProbeWhenDirectCovered { get; set; }
Property Value
- bool
true to skip the inverse probe when the direct pair carries fresh coverage; defaults to false, matching the historical behaviour of always probing the inverse on a direct miss.
Remarks
On a direct-coverage miss the provider normally issues a second backend read for the inverse pair. For a workload that only ever fetches one orientation - the common case - that probe is a guaranteed-empty read doubling backend I/O on every refetch. Enabling this option treats any fresh direct coverage as evidence the pair is actively fetched in the direct orientation and goes straight to the refetch, at the cost of a redundant refetch in the rare configuration where the inverse pair alone holds complete coverage for the window. The option has no effect when DefaultLookupOptions disallows inverse serves.
Methods
GetExpiry(string)
Resolves the caching duration for a provider, returning its specific override when present and the default otherwise.
public TimeSpan GetExpiry(string provider)
Parameters
providerstringThe provider name.
Returns
- TimeSpan
The duration cached rates for
providerstay fresh.
TryValidate(out string?)
Attempts to validate the options without throwing, reporting the first invariant that is violated.
public bool TryValidate(out string? error)
Parameters
errorstringWhen this method returns false, a message describing the first violated invariant; otherwise null.
Returns
Remarks
The throwing Validate() method is expressed in terms of this method, and the dependency-injection
registration wires it into ValidateOnStart so misconfiguration fails fast at application startup.
Validate()
Validates the option values, throwing when a rule is violated.
public void Validate()
Remarks
This throwing form preserves the ParamName of the offending option, which callers rely on. The
dependency-injection registration instead wires TryValidate(out string?) into ValidateOnStart, which
reports the same invariants without throwing.
Exceptions
- ArgumentException
Thrown when DefaultExpiry or any ProviderExpiry entry is not strictly positive, when ProviderExpiry or DefaultLookupOptions is null, or when any
Cache*LogLevel, HistoryClampLogLevel, or RateProvenanceLogLevel is not a defined LogLevel.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |