Skip to content

ADR-002: Singleton Service Clients

Status: Accepted
Date: 2025-06-01
Authors: SpanForge Core Team

Context

The SDK needs to provide globally accessible service clients (sf_pii, sf_audit, etc.) that share configuration and connection state. Requiring users to instantiate each client explicitly adds boilerplate and increases the risk of misconfiguration.

Decision

Use module-level singleton instances in spanforge.sdk.__init__. Each client is instantiated lazily from SFClientConfig.from_env():

# spanforge/sdk/__init__.py
sf_pii: SFPIIClient = SFPIIClient(SFClientConfig.from_env())
sf_audit: SFAuditClient = SFAuditClient(SFClientConfig.from_env())
# …

Configuration resolution order: constructor kwargs → environment variables → defaults.

Consequences

  • Positive: Simple ergonomics — from spanforge.sdk import sf_pii.
  • Positive: Configuration is loaded once at import time.
  • Negative: Singletons are harder to test — mitigated by spanforge.testing_mocks.mock_all_services().
  • Negative: Thread safety relies on each client being internally synchronized.

Alternatives Considered

AlternativeReason Rejected
Explicit client instantiation per callToo verbose; every user adds boilerplate
Dependency injection containerAdds a framework dependency; overkill for SDK use
Thread-local clientsBreaks shared state expectations; complex lifecycle