---
title: SAIF.NetCore.DBConnectionClient architecture
summary: Architectural responsibilities, boundaries, and interaction patterns.
status: draft
owner: Framework.NCore maintainers
last_reviewed: 2026-10-02
library: SAIF.NetCore.DBConnectionClient
reference_type: package
---

# 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.

```mermaid
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.

```mermaid
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`](../../../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**.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [Security and authorization](security-authorization.md)
- [API reference](api/index.md)
- [SAIF.NetCore.ServiceContracts overview](../saif-netcore-servicecontracts/overview.md)

> **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`
