5.9 KiB
Postgres Repository
This document describes the outbound read adapter under
internal/adapters/outbound/postgres. It documents repository behavior and
storage assumptions without duplicating full schema documentation.
Purpose
The Postgres repository implements internal/app.Repository against
weatherfeeder-owned tables. It reconstructs latest weather resources from SQL
rows and returns canonical weatherfeeder model values or application read
models.
Inputs and Outputs
Inputs:
- a primary
*sql.DBselected by runtime composition; - query contexts from application service calls;
- weatherfeeder-populated Postgres rows.
Outputs:
- latest observation, forecast, discussion, weather story, alert, convective outlook, and current conditions read models;
- nil data with nil error when the latest resource does not exist;
- contextual errors for query, scan, iteration, and JSON decode failures.
Boundaries
The repository may:
- own SQL text and query ordering;
- scan rows into adapter-local row structs;
- map SQL nulls to pointers or omitted zero values;
- normalize timestamps to UTC;
- reconstruct child slices in database order.
The repository must not:
- bind HTTP query parameters;
- perform unit conversion or response rounding;
- convert timestamps to requested presentation timezones;
- render templates;
- create or migrate weatherfeeder tables.
Config Fields Used
The repository does not read config directly. Runtime composition supplies the
primary *sql.DB selected from the first configured database entry.
External Adapters Used
database/sqlfor query execution and nullable scan types.github.com/lib/pqthrough runtime driver registration.weatherfeeder/modelfor canonical return values.internal/appfor the repository interface and current-conditions model.
State
The repository stores only a database handle. It owns no durable data, no migrations, no polling state, and no retry queue.
Repository Contract
Repository stores a *sql.DB and satisfies app.Repository.
Every public method first checks that the repository and database are
configured. A nil repository or nil database returns postgres repository is not configured.
Missing parent rows map to nil, nil. This is how HTTP endpoints can return
successful responses with data: null.
Route-to-Query Mapping
LatestObservation: latest row fromobservations, then present-weather rows fromobservation_present_weather.CurrentConditions: aggregates recent rows fromobservationsusing the application-provided observation window.LatestAlertRun: latest row fromalert_runs, then childalertsandalert_references. This is the latest stored alert snapshot. The repository maps bothendsandexpires; active-time filtering is performed by the application service.LatestHourlyForecast: latestforecastsrow whereproduct = 'hourly', then childforecast_periods.LatestNarrativeForecast: latestforecastsrow whereproduct = 'narrative', then childforecast_periods.LatestForecastDiscussion: latest row fromforecast_discussions, then childforecast_discussion_key_messages.LatestWeatherStoryRun: latest row fromweather_story_runs, then childweather_stories.LatestWeatherStory: latest individual row fromweather_stories.LatestConvectiveOutlookRun: latest row fromoutlook_runs, then childoutlooksandoutlook_discussions.
Latest parent rows are selected by descending weather timestamp and
event_emitted_at where that tie-breaker is available in the query.
Child Loading and Ordering
Child queries preserve stored order:
- observation present weather by
weather_index; - alerts by
alert_index; - alert references by
alert_index, thenreference_index; - forecast periods by
period_index; - discussion key messages by
message_index; - weather stories by
story_index; - outlooks by
outlook_index; - outlook discussions by
discussion_index.
Alert references are attached after both alert and reference rows are loaded. References are grouped by alert index and attached to their corresponding alert.
Null and Timestamp Policy
Row structs use sql.Null* types for nullable columns. Mapper helpers convert:
- invalid strings to empty strings;
- invalid booleans, floats, times, and WMO codes to nil pointers;
- valid times to UTC.
Required parent timestamps are normalized to UTC directly in mappers. Optional
times go through timePtr, which also normalizes to UTC.
Current conditions return nil when the aggregate sample count is zero.
JSON Decode Behavior
Observation present-weather rows store raw JSON text. Empty or null text maps to an empty present-weather value. Invalid JSON returns a contextual decode error with the weather index.
Outlook rows store geometry_json as compact GeoJSON text. The repository
validates and copies the JSON bytes into json.RawMessage without parsing or
reserializing the geometry.
Failure Behavior
Repository methods wrap failures with operation context, for example:
query latest observationquery forecast periodsscan forecast period rowiterate weather story rowsdecode observation present weather row
This context should be preserved when adding new reads so operator logs and tests identify the failing operation.
Tests to Inspect Before Changing
internal/adapters/outbound/postgres/repository_test.gointernal/app/service_test.gofor repository port expectations- endpoint tests when no-data behavior or returned model shape affects HTTP responses
Invariants
- Keep SQL and row structs in the Postgres adapter.
- Keep weatherfeeder schema ownership outside
weatherapi. - Preserve
nil, nilno-data behavior for missing latest parent rows. - Preserve UTC normalization at the repository boundary.
- Preserve child ordering from stored index columns.
- Preserve contextual error wrapping for query, scan, iteration, and decode failures.