Skip to content

SAIF.NetCore.DBConnectionClient architecture

SAIF.NetCore.DBConnectionClient is an implementation-layer library that provides ConnectionClient, a single behavioral type implementing the IDBConnectionClient contract from SAIF.NetCore.ServiceContracts; it does not connect to any database itself, but instead calls a remote HTTP service — the DBConnectionAPI — through an injected IHttpJsonClient and returns fully formatted SQL Server, PostgreSQL, or Oracle connection strings.

Responsibilities

  • Implement IDBConnectionClient and expose GetSqlServerConnection, GetPostgreSqlServerConnection, and GetOracleConnection for callers that need a ready-to-use connection string.
  • Resolve the DBConnectionAPI base URL and the three endpoint path segments (SqlConnectionEndpoint, PostgreSqlConnectionEndpoint, OracleConnectionEndpoint) from the DBConnectionClient configuration section via IEnvironmentClient.GetSettingValue.
  • Configure the injected IHttpJsonClient with client identity headers (SetClientInfo) and, optionally, explicit NTLM service-account credentials (SetupClientCredentials).
  • Retrieve connection strings over HTTP with a bounded retry policy (MaxRetrieveAttempts, fixed backoff delays) and cache successful results per connection name for the lifetime of the client instance.
  • Translate failures into DBClientException, a SAIFException (ServiceContracts) subtype.

Component boundaries

  • ConnectionClient: non-sealed, template-method-style class that owns configuration resolution, HTTP client setup, retrieval-with-retry, and caching; every public operation and most extension points (Setup, RetrieveConnectionString, MaxRetrieveAttempts, RetrieveBackoffDelays, Delay) are virtual/protected virtual so subclasses can override endpoints, retry policy, or wait behavior*.
  • ConnectionClientKeys: public static class holding the configuration section name and the three endpoint-key constants consumed by Setup.
  • DBClientException: thin exception type that wraps failures as a SAIFException (ServiceContracts).
  • IHttpJsonClient / IHttpRequestResult / IEnvironmentClient (ServiceContracts interfaces): consumed, not implemented, by this library; concrete behavior is supplied by whatever the host application registers (in the standard stack, SAIF.NetCore.HttpClients and SAIF.NetCore.EnvironmentClient respectively)*.

Interaction model

  • A consuming application (typically via SAIF.NetCore.ServiceExtensions) constructs ConnectionClient with an IHttpJsonClient and an IEnvironmentClient; the constructor itself calls Setup() to resolve the service URL, endpoint segments, and client-identity headers before the constructor returns. The consumer separately calls SetupClientCredentials(username, password) after construction to switch from ambient NTLM identity to explicit service-account credentials.
  • Callers then invoke GetSqlServerConnection, GetPostgreSqlServerConnection, or GetOracleConnection, each of which builds a database-specific cache key and URL path and delegates to the shared cached-lookup logic.
  • The cached lookup acquires an instance-wide lock, checks the in-memory cache keyed by connection name, and returns immediately on a cache hit. On a cache miss it calls RetrieveConnectionString, which issues HttpClient.GetAsync(urlPath) through IHttpJsonClient and classifies the per-attempt IHttpRequestResult read from the completed task (deliberately avoiding the shared IHttpJsonClient.RequestResult property to prevent a cross-call race*): a successful, non-empty response returns immediately; HTTP 401/403/404 fail fast with no retry at this (outer) level; a 503 or network-level error is transient and retries up to MaxRetrieveAttempts (4) times with fixed backoff delays (2s, 3s, 5s); any other non-success or an empty body fails immediately. Each single outer GetAsync call is not necessarily a single HTTP request: when the standard HttpJsonClient implementation is registered, that call applies its own independent inner retry policy (default on 401/502/503, up to 4 attempts with a 1-second delay) before returning a result to this outer step, so the outer and inner retry/backoff schedules are separate and additive, not the same policy described twice.
  • On success the connection string is decoded, cached, and returned; on a permanent failure or exhaustion of transient retries the failure is surfaced as a DBClientException.

Architecture diagram

The following diagram shows how ConnectionClient sits between a consuming application and the remote DBConnectionAPI, mediated by the ServiceContracts interfaces it depends on.

flowchart TD
    APP["Consuming application<br>(via SAIF.NetCore.ServiceExtensions)"] -->|constructs + calls| CC["ConnectionClient<br>(SAIF.NetCore.DBConnectionClient)"]
    CC -->|IEnvironmentClient.GetSettingValue| ENV["IEnvironmentClient<br>(ServiceContracts contract)"]
    CC -->|SetClientInfo / SetNtlmAuthentication / GetAsync| HTTP["IHttpJsonClient<br>(ServiceContracts contract)"]
    HTTP -->|GET connection-string endpoints| API["DBConnectionAPI<br>(external HTTP service)"]
    CC -->|wraps failures as| EX["DBClientException<br>(extends SAIFException)"]

Sequence diagram

The following diagram shows the primary request/response sequence for retrieving a SQL Server connection string, including both ConnectionClient's own outer retry loop and the standard IHttpJsonClient implementation's separate, independent inner retry policy that it wraps.

sequenceDiagram
    participant Consumer
    participant ConnectionClient
    participant IHttpJsonClient
    participant DBConnectionAPI
    Consumer->>ConnectionClient: GetSqlServerConnection(server, instance, userName)
    ConnectionClient->>ConnectionClient: Acquire lock, check in-memory cache
    alt cache hit
        ConnectionClient-->>Consumer: cached connection string
    else cache miss
        loop outer: up to MaxRetrieveAttempts (4)
            ConnectionClient->>IHttpJsonClient: GetAsync("/{SqlEndpoint}/{server}/{instance}/{userName}")
            loop inner (standard HttpJsonClient only): up to 4 HTTP requests
                IHttpJsonClient->>DBConnectionAPI: GET connection-string endpoint
                DBConnectionAPI-->>IHttpJsonClient: HTTP response
                Note over IHttpJsonClient: Retries on 401/502/503<br>(default RetryOnStatusCodes), 1s delay
            end
            IHttpJsonClient-->>ConnectionClient: IHttpRequestResult (converted from the final inner HTTP response)
            break success, non-empty body
                ConnectionClient-->>Consumer: decode and cache, return connection string
            end
            break 401/403/404 (permanent at the outer level)
                ConnectionClient-->>Consumer: throw DBClientException (no outer retry)
            end
            alt 503 or network error (transient) and attempts remain
                ConnectionClient->>ConnectionClient: Delay(outer backoff) and retry
            else other non-success, empty body, or transient retries exhausted
                break terminal failure
                    ConnectionClient-->>Consumer: throw DBClientException
                end
            end
        end
    end

Design constraints

  • No project references between libraries in this repository; the only cross-library dependency is the SAIF.NetCore.ServiceContracts NuGet package, consistent with the repository-wide layering in Architecture.md.
  • The library ships no IServiceCollection extension of its own; DI wiring, singleton lifetime, and credential retrieval are composed externally by SAIF.NetCore.ServiceExtensions*.
  • IHttpJsonClient is documented as intended for transient use, but a singleton ConnectionClient captures a single instance for the process lifetime, which is a potential lifetime mismatch combined with stored NTLM credential state*.
  • Actual values for the service URL and the three endpoint path segments are environment configuration, not present in this repository**.

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.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ConnectionClient.cs
  • src/SAIF.NetCore.DbConnectionClient/SAIF.NetCore.DBConnectionClient/SAIF.NetCore.DBConnectionClient/ClientException.cs
  • src/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/SAIF.NetCore.ServiceContracts/IDBConnectionClient.cs, IHttpJsonClient.cs, IHttpRequestResult.cs, IEnvironmentClient.cs, SAIFException.cs, HttpClientOptions.cs
  • src/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/HttpJsonClient/HttpJsonClient.cs (inner retry policy detail reached when the standard IHttpJsonClient implementation is registered)
  • src/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/SAIF.NetCore.ServiceExtensions/IServiceCollectionExtensions.cs
  • Architecture.md