ADR 0007: Organize service adapters behind stable facades
Status
Accepted
Context
A service facade can remain stable while its implementation grows for unrelated upstream collections. Keeping every Water Data getter in one module coupled changes to time series, monitoring metadata, field measurements, reference catalogs, Samples, statistics, and generalized CQL queries. Active service modules also relied on Python’s implicit wildcard-export behavior, making their intended public surfaces difficult to distinguish from imported helpers.
Decision
dataretrieval.waterdata.api is a compatibility facade with no collection
logic. Implementation functions are grouped by collection family in
time_series, metadata, measurements, reference, samples, and
cql. Existing focused modules continue to own ratings, nearest-value
selection, Statistics API execution, shared Water Data policy, and type
vocabularies.
The facade re-exports the established functions and preserves their signatures,
their identity at dataretrieval.waterdata, and the private Samples constants
compatibility tests rely on. It does not rewrite their __module__: each
function reports the family module that defines it, so a traceback names a file
that contains code. Collection-family modules do not
import one another; shared behavior belongs in Water Data policy, OGC, or
transport modules.
Active service and focused implementation modules declare explicit __all__
exports. Deprecated NWIS remains outside this modernization. Service adapters do
not import another adapter’s implementation to obtain transport behavior.
Return contracts remain service-specific. Tabular services generally return a
(DataFrame, metadata) pair, while NLDI returns geospatial values directly,
StreamStats exposes response/domain objects, and ratings return parsed tables or
raw catalog features. Uniformity is not a reason to break these established
contracts.
Consequences
Collection changes have a smaller implementation and test blast radius.
Existing package and
waterdata.apiimport paths remain stable.Explicit exports make accidental public-surface growth reviewable.
More modules require a maintained facade and executable signature/export snapshots.
Tests are described as public-contract, adapter-contract, component, or cross-component layers without forcing a disruptive move of established files.
Compliance
tests/contracts/public_api_test.py freezes Water Data imports, signatures,
facade identity, and compatibility names. tests/architecture_test.py
requires a logic-free facade, exact active-service exports, and separate OGC
request construction and schema execution. .importlinter keeps the
collection families independent of each other, holds NGWMN to the OGC facade,
and prevents lateral adapter reach-through.