2.2 KiB
Architecture Principles
Standalone Subsystems
Each class should behave like a small standalone subsystem. It should own one clear responsibility, expose a narrow public interface, and avoid hidden dependencies on the internals of other classes.
Good subsystem boundaries:
- accept explicit inputs
- return explicit outputs
- keep internal state private
- avoid reaching into global state
- avoid performing unrelated work
- can be tested with recorded or fixture data
Examples:
- a weather client fetches forecast data
- a weather parser converts API payloads into forecast points
- a weather builder normalizes external forecast records for storage
- a storage class persists records
- Gibil makes decisions from snapshots
Data Models Between Subsystems
Subsystems should communicate through shared data models rather than through source-specific payloads.
For example:
- Open-Meteo JSON should become
WeatherForecastRun - Modbus register reads should become
Observation - HASS entity state should become
Observation - Gibil should reason from
Snapshot
This keeps the edges messy and the core calm.
Side Effects At The Edges
Network calls, database writes, MQTT publishes, and filesystem writes should live at clear boundaries.
Core reasoning classes should generally be pure or nearly pure:
- input data in
- answer out
- no surprise I/O
Stateful classes are allowed, but their state should be deliberate and inspectable.
Grow By Composition
Astrape should grow by connecting small subsystems together, not by building one large object that knows everything.
The desired shape is:
source client -> parser -> model -> storage -> query/snapshot -> Gibil -> publisher
Each part should be replaceable without rewriting the others.
Prefer Working Slices
Build one thin working path at a time. A thin slice may start with empty storage or recorded source data, but it should still follow the real subsystem boundaries.
For example, the weather slice can start with:
Open-Meteo forecast run -> WeatherBuilder -> clean forecast records
Then grow into:
Open-Meteo -> parser -> WeatherBuilder -> TimescaleDB -> weather_predictor.py