SAIF.NetCore.EnvironmentClient architecture¶
SAIF.NetCore.EnvironmentClient is the implementation-layer library that provides BaseEnvironmentClient and SecureEnvironmentClient, the concrete implementations of the IEnvironmentClient contract from SAIF.NetCore.ServiceContracts; it resolves which environment "track" the running application belongs to, then lazily downloads, caches, and refreshes named configuration settings — and, for the secure variant, crypto-protected settings — from a central Environment API over HTTP.
Responsibilities¶
- Implement
IEnvironmentClientand expose configuration-value accessors (GetSettingValue/GetSettingValueAsyncwith multiple overloads,GetSectionSettings,HasSetting) and secure-setting accessors (GetSecureSetting/GetSecureSettingAsync). - Resolve the application's environment track and host identity at startup (
Setup) viaGetHostsByTrackandIHostResolver.ResolveHost, and support a developer-machine configuration override file. - Lazily load and cache configuration sections and settings from the Environment API, with a time-based refresh check (
TestForRefreshSettings, throttled to roughly 60 seconds). - Gate access to secure (crypto) settings behind a system hash (
ISystemHashService), implemented inSecureEnvironmentClient, which throws in the base class. - Resolve the current host's Active Directory group membership (
HostResolver) to decide trust/override behavior.
Component boundaries¶
BaseEnvironmentClient: the primaryIEnvironmentClientimplementation; owns configuration/section caching (ConcurrentDictionary,HashSetof loaded sections,SemaphoreSlim), host/track resolution, the developer override file, and all non-secure setting access.RetrieveSecureSetting<T>/RetrieveSecureSettingAsync<T>arevirtualand throw "Operation not allowed!" by default*.SecureEnvironmentClient: inheritsBaseEnvironmentClientand overrides the secure-setting retrieval methods, gated by a constructor check (COREAPIENABLED=1or developer machine) and backed bySecureSettingCache.SecureSettingCache: an in-memoryGet/GetAsynccache keyed by setting key and last-refreshed timestamp, with aClearCachemethod that is never invoked by this library*.HostResolver/IHostResolver: resolves the current host from a candidate list using Active Directory group membership (System.DirectoryServices.AccountManagement).IEnvironmentClient,IHttpJsonClient,IHttpRequestResult,ISystemHashService(ServiceContracts interfaces): consumed, not implemented beyondIEnvironmentClient; the HTTP transport and system-hash computation are supplied by whatever the host application registers (in the standard stack,SAIF.NetCore.HttpClientsandSAIF.NetCore.SystemHash/SAIF.NetCore.HashUtilrespectively)*.
Interaction model¶
- A consuming application (typically via
SAIF.NetCore.ServiceExtensions) constructsBaseEnvironmentClientorSecureEnvironmentClientwithIConfiguration,IHostEnvironment,IHttpJsonClient,IHostResolver, andISystemHashService. The constructor itself callsSetup()— setup is not a separate step the caller invokes; it runs synchronously during construction and resolves the application host and environment track against the Environment API before the constructor returns. - Callers then invoke
GetSettingValue/GetSettingValueAsync(optionally scoped to a section) or, onSecureEnvironmentClient,GetSecureSetting/GetSecureSettingAsync; both paths unconditionally run the refresh check (CheckForUpdates) first, then lazily load the target section from the Environment API if it has not already been loaded in the current cache generation. - All outbound calls are issued through the injected
IHttpJsonClientagainst the base URL supplied by theEnvironmentApiUrlOS environment variable; secure-setting calls additionally append asystemHashValuequery parameter computed viaISystemHashService.Get(SystemType). - A local developer-settings override file (resolved by
HostResolver's Active Directory check) is consulted only after the non-secure setting path has already triggered the refresh-check/load sequence described above — it changes which value is returned once loading has happened, it does not let a developer machine skip the Environment API bootstrap or load/refresh network dependency. The override file is never consulted for secure settings,HasSetting, orGetSectionSettings.
Architecture diagram¶
The following diagram shows how BaseEnvironmentClient/SecureEnvironmentClient sit between a consuming application and the remote Environment API, mediated by the ServiceContracts interfaces they depend on.
flowchart TD
APP["Consuming application<br>(via SAIF.NetCore.ServiceExtensions)"] -->|constructs + calls| BEC["BaseEnvironmentClient"]
BEC --> SEC["SecureEnvironmentClient<br>(inherits BaseEnvironmentClient)"]
SEC --> CACHE["SecureSettingCache"]
BEC -->|SetClientInfo / Get / GetAsync| HTTP["IHttpJsonClient<br>(ServiceContracts contract)"]
SEC -->|"Get(SystemType)"| HASH["ISystemHashService<br>(ServiceContracts contract)"]
HTTP -->|GET tracks/settings/secureSetting endpoints| API["Environment API<br>(external HTTP service)"]
BEC -->|AD group lookup| RES["IHostResolver / HostResolver"]
Sequence diagram¶
The following diagram shows construction-time setup followed by the primary request/response sequence for resolving a non-secure configuration setting, including the server-timestamp refresh probe, the empty-cache reload path, and section loading.
sequenceDiagram
participant Consumer
participant BaseEnvironmentClient
participant IHttpJsonClient
participant EnvironmentAPI
Consumer->>BaseEnvironmentClient: new BaseEnvironmentClient(...)
activate BaseEnvironmentClient
BaseEnvironmentClient->>BaseEnvironmentClient: Setup() — resolve host/track, GET tracks endpoint
BaseEnvironmentClient->>IHttpJsonClient: Get("/v2/tracks/{app}/value")
IHttpJsonClient->>EnvironmentAPI: GET tracks
EnvironmentAPI-->>IHttpJsonClient: wire response
IHttpJsonClient-->>BaseEnvironmentClient: deserialized result
deactivate BaseEnvironmentClient
Consumer->>BaseEnvironmentClient: GetSettingValue(key, section)
BaseEnvironmentClient->>BaseEnvironmentClient: CheckForUpdates (always runs first)
alt Settings dictionary empty
BaseEnvironmentClient->>BaseEnvironmentClient: NeedsRefresh = true (no probe call)
else at least 60s since last probe
BaseEnvironmentClient->>IHttpJsonClient: Get("/settings/lastRefreshed")
IHttpJsonClient->>EnvironmentAPI: GET lastRefreshed
EnvironmentAPI-->>IHttpJsonClient: wire response
IHttpJsonClient-->>BaseEnvironmentClient: server timestamp
BaseEnvironmentClient->>BaseEnvironmentClient: NeedsRefresh = server timestamp > LastRefreshed
else probe not yet due
BaseEnvironmentClient->>BaseEnvironmentClient: NeedsRefresh = false
end
alt NeedsRefresh = true
BaseEnvironmentClient->>BaseEnvironmentClient: LastRefreshed = now, Settings.Clear(), reload root + known sections
end
alt section not yet loaded this cache generation
BaseEnvironmentClient->>IHttpJsonClient: Get("/v2/settings/{app}/{host}/{section}/value")
IHttpJsonClient->>EnvironmentAPI: GET settings endpoint
EnvironmentAPI-->>IHttpJsonClient: wire response
IHttpJsonClient-->>BaseEnvironmentClient: deserialized ConfigurationSettings
BaseEnvironmentClient->>BaseEnvironmentClient: AddOrUpdate each setting into global Settings dictionary
end
BaseEnvironmentClient->>BaseEnvironmentClient: Developer override lookup (checked after the load above, not instead of it)
BaseEnvironmentClient-->>Consumer: setting value
Design constraints¶
- No project references between libraries in this repository; the only cross-library dependency is the
SAIF.NetCore.ServiceContractsNuGet package, consistent with the repository-wide layering inArchitecture.md. SecureEnvironmentClientgates secure-setting access behindCOREAPIENABLED=1or developer-machine detection; the base class unconditionally throws for secure-setting retrieval, so secure access requires the derived type to be registered*.LastRefreshedis tracked using client local time but compared against the server's reportedlastRefreshed, so clock skew between client and server could cause missed or extra refreshes*.- The
SectionsHashSetis read outside its guarding semaphore in at least one path while also being mutated during reloads, which is a potential thread-safety gap*. - Server-side Environment API semantics (hash validation,
lastRefreshedmeaning, response casing) are outside this repository and cannot be verified from source alone**.
Related documentation¶
- Index
- Overview
- Dependencies
- Configuration
- Operations and processes
- Business rules
- Security and authorization
- API reference
- SAIF.NetCore.ServiceContracts overview
Note: Statements marked with
*were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with**could not be confirmed; treat as unknown until verified.
Sources¶
src/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/BaseEnvironmentClient.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureEnvironmentClient.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SecureSettingCache.cssrc/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/SAIF.NetCore.EnvironmentClient/HostResolver.cssrc/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IEnvironmentClient.cs,ISystemHashService.cs,IHttpJsonClient.cs,IHttpRequestResult.csArchitecture.md