Add internal architecture documentation
This commit is contained in:
151
docs/internal/http-adapter.md
Normal file
151
docs/internal/http-adapter.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# HTTP Adapter
|
||||||
|
|
||||||
|
This document describes the internal HTTP adapter under
|
||||||
|
`internal/adapters/inbound/httpapi`. The public endpoint contract belongs in
|
||||||
|
[`docs/api.md`](../api.md).
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The HTTP adapter turns feedapi route definitions into calls on the application
|
||||||
|
service boundary. It owns route registration, query binding, request validation,
|
||||||
|
forecast day-slice filtering, response envelopes, and template names.
|
||||||
|
|
||||||
|
## Inputs and Outputs
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
|
||||||
|
- feedapi HTTP requests;
|
||||||
|
- query parameters;
|
||||||
|
- an implementation of the adapter-local `Service` interface.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
- `endpoint.Definition` values registered by runtime composition;
|
||||||
|
- `response.Envelope{Data: ...}` values for successful requests;
|
||||||
|
- typed feedapi errors for invalid query parameters;
|
||||||
|
- endpoint-specific template names for text rendering.
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
The adapter may:
|
||||||
|
|
||||||
|
- define routes and supported response formats;
|
||||||
|
- bind and validate query parameters;
|
||||||
|
- call the `Service` interface;
|
||||||
|
- choose the presenter function for an endpoint;
|
||||||
|
- filter forecast copies for `/today` and `/tomorrow`.
|
||||||
|
|
||||||
|
The adapter must not:
|
||||||
|
|
||||||
|
- execute SQL;
|
||||||
|
- open databases;
|
||||||
|
- mutate repository-returned models in place;
|
||||||
|
- duplicate public API reference text;
|
||||||
|
- move unit conversion or timezone presentation out of presenters.
|
||||||
|
|
||||||
|
## Config Fields Used
|
||||||
|
|
||||||
|
The HTTP adapter does not read config directly. Feedapi applies server and
|
||||||
|
renderer configuration before requests reach these handlers. Template names are
|
||||||
|
declared in endpoint definitions, but `templates.base_dir` is loaded by feedapi.
|
||||||
|
|
||||||
|
## External Adapters Used
|
||||||
|
|
||||||
|
- `feedapi/endpoint` for route definitions.
|
||||||
|
- `feedapi/render` for declared output formats.
|
||||||
|
- `feedapi/response` for success envelopes.
|
||||||
|
- `feedapi/bind` and `feedapi/errors` for query validation failures.
|
||||||
|
- `presenter` package for payload shaping.
|
||||||
|
|
||||||
|
## State
|
||||||
|
|
||||||
|
The adapter has no durable state. `forecastNow` is package-level state only to
|
||||||
|
make forecast day filtering testable. Do not add request caches or cross-request
|
||||||
|
mutable state here.
|
||||||
|
|
||||||
|
## Route Registry
|
||||||
|
|
||||||
|
`Definitions(svc)` returns all implemented route definitions. It includes:
|
||||||
|
|
||||||
|
- observations;
|
||||||
|
- active alerts;
|
||||||
|
- current conditions;
|
||||||
|
- weather stories;
|
||||||
|
- forecast discussions;
|
||||||
|
- hourly and narrative forecasts.
|
||||||
|
|
||||||
|
Each route uses `endpoint.GET`, declares JSON/XML/text production, and names a
|
||||||
|
text template. Feedapi owns routing, format negotiation, response rendering,
|
||||||
|
middleware, and error normalization after definitions are registered.
|
||||||
|
|
||||||
|
## Query Binding
|
||||||
|
|
||||||
|
There are three binder shapes:
|
||||||
|
|
||||||
|
- `bindQuery`: `format` and `units`;
|
||||||
|
- `bindPrecisionQuery`: `format`, `units`, and `precision`;
|
||||||
|
- `bindForecastPrecisionQuery`: `format`, `units`, `precision`, and timezone;
|
||||||
|
- `bindTimezoneQuery`: `format`, `units`, and timezone.
|
||||||
|
|
||||||
|
All binders use feedapi binding helpers with `RejectUnknown: true`. Supported
|
||||||
|
common query values are lowercased and trimmed before binding where applicable.
|
||||||
|
|
||||||
|
`precision` defaults to `0` and must be between `0` and `2`. Timezone parsing is
|
||||||
|
available only through binders used by forecast, discussion, and weather story
|
||||||
|
routes.
|
||||||
|
|
||||||
|
## Timezone Parsing
|
||||||
|
|
||||||
|
Timezone parsing accepts:
|
||||||
|
|
||||||
|
- IANA names through `time.LoadLocation`;
|
||||||
|
- configured aliases such as `Chicago` and `Stl`;
|
||||||
|
- common US abbreviations handled as fixed zones;
|
||||||
|
- signed UTC offsets.
|
||||||
|
|
||||||
|
If both `tz` and `TZ` are provided, they must match case-insensitively.
|
||||||
|
Invalid timezone input is converted to a feedapi invalid-parameter error.
|
||||||
|
|
||||||
|
## Forecast Day Slices
|
||||||
|
|
||||||
|
Forecast base routes return the latest run unchanged except for presentation.
|
||||||
|
`/today` and `/tomorrow` routes call `filterForecastRunByDaySlice`.
|
||||||
|
|
||||||
|
Filtering behavior:
|
||||||
|
|
||||||
|
- nil runs remain nil;
|
||||||
|
- missing timezone means UTC;
|
||||||
|
- day selection is based on `forecastNow()` in the resolved timezone;
|
||||||
|
- periods are included when `period.StartTime` falls on the target local date;
|
||||||
|
- the run is shallow-copied and `Periods` is replaced with the filtered slice.
|
||||||
|
|
||||||
|
The package variable `forecastNow` exists so endpoint tests can make day-slice
|
||||||
|
behavior deterministic.
|
||||||
|
|
||||||
|
## Failure Behavior
|
||||||
|
|
||||||
|
Binder failures become feedapi invalid-parameter responses. Handler service
|
||||||
|
errors are returned unchanged to feedapi for runtime normalization. Nil service
|
||||||
|
payloads are presented as nil data, so response rendering can produce
|
||||||
|
`data: null`.
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
Endpoint definitions bind template names only. Template loading and rendering
|
||||||
|
belong to feedapi. Template files live under `templates/` and are operator
|
||||||
|
configuration through `templates.base_dir`.
|
||||||
|
|
||||||
|
## Tests to Inspect Before Changing
|
||||||
|
|
||||||
|
- `internal/adapters/inbound/httpapi/endpoints_test.go`
|
||||||
|
- `internal/adapters/inbound/httpapi/presenter/payload_test.go` when changing
|
||||||
|
presenter interaction
|
||||||
|
- `docs/api.md` when query behavior, route behavior, or payload behavior changes
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Preserve strict unknown-query rejection.
|
||||||
|
- Keep route-specific query policy in binder functions.
|
||||||
|
- Keep endpoint handlers thin: bind, call service, present, envelope.
|
||||||
|
- Keep day-slice filtering in the HTTP adapter, not in the repository.
|
||||||
|
- Preserve top-level `data` envelopes and nil-data behavior.
|
||||||
157
docs/internal/postgres-repository.md
Normal file
157
docs/internal/postgres-repository.md
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
# 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.DB` selected by runtime composition;
|
||||||
|
- query contexts from application service calls;
|
||||||
|
- weatherfeeder-populated Postgres rows.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
- latest observation, forecast, discussion, weather story, alert, 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/sql` for query execution and nullable scan types.
|
||||||
|
- `github.com/lib/pq` through runtime driver registration.
|
||||||
|
- `weatherfeeder/model` for canonical return values.
|
||||||
|
- `internal/app` for 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 from `observations`, then present-weather
|
||||||
|
rows from `observation_present_weather`.
|
||||||
|
- `CurrentConditions`: aggregates recent rows from `observations` using the
|
||||||
|
application-provided observation window.
|
||||||
|
- `LatestAlertRun`: latest row from `alert_runs`, then child `alerts` and
|
||||||
|
`alert_references`.
|
||||||
|
- `LatestHourlyForecast`: latest `forecasts` row where `product = 'hourly'`,
|
||||||
|
then child `forecast_periods`.
|
||||||
|
- `LatestNarrativeForecast`: latest `forecasts` row where
|
||||||
|
`product = 'narrative'`, then child `forecast_periods`.
|
||||||
|
- `LatestForecastDiscussion`: latest row from `forecast_discussions`, then
|
||||||
|
child `forecast_discussion_key_messages`.
|
||||||
|
- `LatestWeatherStoryRun`: latest row from `weather_story_runs`, then child
|
||||||
|
`weather_stories`.
|
||||||
|
- `LatestWeatherStory`: latest individual row from `weather_stories`.
|
||||||
|
|
||||||
|
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`, then `reference_index`;
|
||||||
|
- forecast periods by `period_index`;
|
||||||
|
- discussion key messages by `message_index`;
|
||||||
|
- weather stories by `story_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.
|
||||||
|
|
||||||
|
## Failure Behavior
|
||||||
|
|
||||||
|
Repository methods wrap failures with operation context, for example:
|
||||||
|
|
||||||
|
- `query latest observation`
|
||||||
|
- `query forecast periods`
|
||||||
|
- `scan forecast period row`
|
||||||
|
- `iterate weather story rows`
|
||||||
|
- `decode 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.go`
|
||||||
|
- `internal/app/service_test.go` for 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, nil` no-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.
|
||||||
143
docs/internal/presenters.md
Normal file
143
docs/internal/presenters.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
# Presenters and Templates
|
||||||
|
|
||||||
|
This document describes payload shaping in
|
||||||
|
`internal/adapters/inbound/httpapi/presenter` and its relationship to text
|
||||||
|
templates. Public response fields are documented in [`docs/api.md`](../api.md).
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
Presenters translate repository/application read models into response-ready
|
||||||
|
payloads. They own unit conversion, numeric rounding, timezone conversion,
|
||||||
|
optional field preservation, and copy semantics.
|
||||||
|
|
||||||
|
## Inputs and Outputs
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
|
||||||
|
- weatherfeeder model values returned by the repository;
|
||||||
|
- `app.CurrentConditions` values returned by the application service;
|
||||||
|
- requested unit mode;
|
||||||
|
- requested precision;
|
||||||
|
- optional timezone location.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
- JSON/XML/text-ready payload structs or canonical model copies;
|
||||||
|
- nil payloads for nil inputs.
|
||||||
|
|
||||||
|
## Boundaries
|
||||||
|
|
||||||
|
Presenters may:
|
||||||
|
|
||||||
|
- convert metric values to US-customary response fields;
|
||||||
|
- round numeric values that are presentation values;
|
||||||
|
- convert timestamps to a requested timezone;
|
||||||
|
- copy slices and pointers before changing presentation values;
|
||||||
|
- add template-only helper fields when they are not serialized.
|
||||||
|
|
||||||
|
Presenters must not:
|
||||||
|
|
||||||
|
- execute SQL;
|
||||||
|
- call services or repositories;
|
||||||
|
- parse HTTP query parameters;
|
||||||
|
- load templates;
|
||||||
|
- change canonical repository models in place.
|
||||||
|
|
||||||
|
## Config Fields Used
|
||||||
|
|
||||||
|
Presenters do not read config directly. They receive unit, precision, and
|
||||||
|
timezone selections from HTTP binders. Text output depends indirectly on
|
||||||
|
`templates.base_dir` because feedapi loads templates from that configured
|
||||||
|
directory.
|
||||||
|
|
||||||
|
## External Adapters Used
|
||||||
|
|
||||||
|
- `weatherfeeder/model` for canonical weather payloads.
|
||||||
|
- `weatherfeeder/standards` for current-condition text from WMO codes.
|
||||||
|
- `internal/app` for the current-conditions read model.
|
||||||
|
- Feedapi templates indirectly consume presenter output during text rendering.
|
||||||
|
|
||||||
|
## State
|
||||||
|
|
||||||
|
Presenters are stateless. Helper functions allocate copied values and return
|
||||||
|
new payloads for each request.
|
||||||
|
|
||||||
|
## Unit Conversion
|
||||||
|
|
||||||
|
Metric mode generally returns weatherfeeder canonical model shapes or
|
||||||
|
metric-named fields. US mode uses explicit US response structs for observations
|
||||||
|
and forecasts and US-specific fields in current conditions.
|
||||||
|
|
||||||
|
Conversion constants live in `constants.go`:
|
||||||
|
|
||||||
|
- Celsius to Fahrenheit;
|
||||||
|
- kilometers per hour to miles per hour;
|
||||||
|
- meters to miles or feet;
|
||||||
|
- pascals to inches of mercury;
|
||||||
|
- millimeters to inches.
|
||||||
|
|
||||||
|
Non-unit fields such as percentages, directions, text, IDs, and ordering values
|
||||||
|
keep their existing values.
|
||||||
|
|
||||||
|
## Precision
|
||||||
|
|
||||||
|
`roundedPtr` and `roundFloat` round numeric presentation values. Precision `0`
|
||||||
|
rounds to whole numbers. Positive precision uses powers of ten. Nil numeric
|
||||||
|
pointers remain nil.
|
||||||
|
|
||||||
|
Latitude and longitude are copied but not rounded by forecast presenters.
|
||||||
|
|
||||||
|
## Timezone Conversion
|
||||||
|
|
||||||
|
`inLocationTime` and `inLocationTimePtr` convert timestamps only when a
|
||||||
|
timezone is supplied. Without a timezone, timestamps are preserved as returned
|
||||||
|
by the repository.
|
||||||
|
|
||||||
|
Timezone conversion is applied by forecast, discussion, and weather story
|
||||||
|
presenters. Observations and current conditions do not currently receive
|
||||||
|
timezone input from their routes.
|
||||||
|
|
||||||
|
## Optional Fields and Copy Semantics
|
||||||
|
|
||||||
|
Presenter helpers copy pointer values before changing them. Slices are copied
|
||||||
|
before inclusion where needed. This preserves nil/omitempty behavior and avoids
|
||||||
|
mutating repository-returned values.
|
||||||
|
|
||||||
|
Nil input payloads return nil. Endpoint handlers wrap those nil payloads in a
|
||||||
|
response envelope so renderers can produce `data: null`.
|
||||||
|
|
||||||
|
## Endpoint Families
|
||||||
|
|
||||||
|
- Observations: metric copy or US response shape, including present-weather
|
||||||
|
slice copy.
|
||||||
|
- Current conditions: single response shape with either metric or US unit
|
||||||
|
fields populated, plus a template-only day/night text helper.
|
||||||
|
- Alerts: pass-through of canonical alert runs, with nil preserved.
|
||||||
|
- Forecasts: metric copy or US response shape, period copy, unit conversion,
|
||||||
|
precision, and timezone conversion.
|
||||||
|
- Discussions: full or focused payload shapes, section copy, key-message copy,
|
||||||
|
timezone conversion.
|
||||||
|
- Weather stories: run/story copy and timezone conversion.
|
||||||
|
|
||||||
|
## Templates
|
||||||
|
|
||||||
|
Text templates consume the same envelope data produced by presenters. Template
|
||||||
|
files live under `templates/` and are loaded by feedapi from `templates.base_dir`.
|
||||||
|
|
||||||
|
Presenter changes can break text output even when JSON and XML still compile.
|
||||||
|
Check template field references before renaming or removing presenter fields.
|
||||||
|
|
||||||
|
## Tests to Inspect Before Changing
|
||||||
|
|
||||||
|
- `internal/adapters/inbound/httpapi/presenter/payload_test.go`
|
||||||
|
- `internal/adapters/inbound/httpapi/endpoints_test.go` for rendered text and
|
||||||
|
envelope behavior
|
||||||
|
- affected template files under `templates/`
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Keep unit conversion and rounding out of handlers and repositories.
|
||||||
|
- Preserve nil pointer and optional field behavior.
|
||||||
|
- Copy before converting, rounding, or timezone-shifting values.
|
||||||
|
- Keep text-template helper fields out of JSON/XML when they are not API fields.
|
||||||
|
- Update `docs/api.md` and templates when payload shape changes.
|
||||||
105
docs/internal/runtime.md
Normal file
105
docs/internal/runtime.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Runtime Composition
|
||||||
|
|
||||||
|
This document describes how the `weatherapi` executable wires configuration,
|
||||||
|
database handles, application services, HTTP endpoints, renderers, and shutdown.
|
||||||
|
It is development-facing; operator commands belong in
|
||||||
|
[`docs/operations.md`](../operations.md), and configuration fields belong in
|
||||||
|
[`docs/config.md`](../config.md).
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
`cmd/weatherapi/main.go` is the composition root. It should stay thin and only
|
||||||
|
connect already-implemented packages. Endpoint logic, SQL, presentation logic,
|
||||||
|
and business read behavior belong outside `cmd`.
|
||||||
|
|
||||||
|
## Inputs and Outputs
|
||||||
|
|
||||||
|
Inputs:
|
||||||
|
|
||||||
|
- config path from `-config`, `WEATHERAPI_CONFIG`, or `config.yml`;
|
||||||
|
- feedapi YAML config containing `server`, `databases`, and `templates`;
|
||||||
|
- OS cancellation signals;
|
||||||
|
- database handles opened by feedapi.
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
|
||||||
|
- a configured feedapi HTTP server;
|
||||||
|
- registered `weatherapi` endpoint definitions;
|
||||||
|
- process logs for fatal startup errors and database close errors.
|
||||||
|
|
||||||
|
## Composition Flow
|
||||||
|
|
||||||
|
The executable:
|
||||||
|
|
||||||
|
1. sets standard logger flags with microsecond precision;
|
||||||
|
2. resolves the config path;
|
||||||
|
3. creates a context canceled by `os.Interrupt` or `SIGTERM`;
|
||||||
|
4. loads config with `feedapi/config.Load`;
|
||||||
|
5. requires at least one configured database;
|
||||||
|
6. opens all configured databases with `feedapi/db.OpenAll`;
|
||||||
|
7. selects the first configured database name as the primary store;
|
||||||
|
8. constructs `postgres.Repository` with the primary `*sql.DB`;
|
||||||
|
9. constructs `app.Service` over the repository;
|
||||||
|
10. builds HTTP endpoint definitions with `httpapi.Definitions`;
|
||||||
|
11. constructs a feedapi app with the DB registry and endpoints;
|
||||||
|
12. starts feedapi with the signal-aware context.
|
||||||
|
|
||||||
|
## Config Fields Used
|
||||||
|
|
||||||
|
Runtime composition uses:
|
||||||
|
|
||||||
|
- `server`: consumed by feedapi for HTTP runtime settings and default format;
|
||||||
|
- `databases`: opened by feedapi, with the first entry selected as primary;
|
||||||
|
- `templates`: consumed by feedapi for text-template loading.
|
||||||
|
|
||||||
|
Do not duplicate the config field reference here. Keep it in
|
||||||
|
[`docs/config.md`](../config.md).
|
||||||
|
|
||||||
|
## External Adapters Used
|
||||||
|
|
||||||
|
- `feedapi/config`: YAML loading.
|
||||||
|
- `feedapi/db`: database registry and lifecycle.
|
||||||
|
- `feedapi/app`: HTTP runtime construction and startup.
|
||||||
|
- `internal/adapters/outbound/postgres`: weather read repository.
|
||||||
|
- `internal/adapters/inbound/httpapi`: endpoint definition registry.
|
||||||
|
- `github.com/lib/pq`: Postgres driver registration through blank import.
|
||||||
|
|
||||||
|
## State and Lifecycle
|
||||||
|
|
||||||
|
`weatherapi` owns no durable weather state. Runtime state is limited to loaded
|
||||||
|
configuration, database pools, endpoint definitions, renderer/template
|
||||||
|
registries managed by feedapi, and the running HTTP server.
|
||||||
|
|
||||||
|
Database handles are closed with a deferred registry close. Close errors are
|
||||||
|
logged but do not change response behavior because they occur during shutdown.
|
||||||
|
|
||||||
|
## Failure Behavior
|
||||||
|
|
||||||
|
`run` wraps startup errors with operation context:
|
||||||
|
|
||||||
|
- `load config`
|
||||||
|
- `config.databases requires at least one entry`
|
||||||
|
- `open databases`
|
||||||
|
- `select primary database`
|
||||||
|
- `build app`
|
||||||
|
|
||||||
|
Errors returned by `a.Start(ctx)` are returned to `main`, which logs a fatal
|
||||||
|
`weatherapi failed: ...` message. Feedapi owns graceful HTTP shutdown after the
|
||||||
|
context is canceled.
|
||||||
|
|
||||||
|
## Tests to Inspect Before Changing
|
||||||
|
|
||||||
|
- `internal/app/service_test.go` for service wiring expectations.
|
||||||
|
- `internal/adapters/inbound/httpapi/endpoints_test.go` for endpoint registry
|
||||||
|
and runtime adapter expectations.
|
||||||
|
- Full `go test ./...` when runtime wiring, config behavior, or feedapi
|
||||||
|
integration changes.
|
||||||
|
|
||||||
|
## Invariants
|
||||||
|
|
||||||
|
- Keep `cmd/weatherapi` as composition code only.
|
||||||
|
- Preserve config path precedence: `-config`, `WEATHERAPI_CONFIG`, `config.yml`.
|
||||||
|
- Preserve first configured database as the primary weather store.
|
||||||
|
- Keep generic HTTP runtime behavior in feedapi.
|
||||||
|
- Keep endpoint definitions in the HTTP adapter.
|
||||||
|
- Keep SQL and row mapping in the Postgres adapter.
|
||||||
Reference in New Issue
Block a user