Files
Astrape/docs/architecture-principles.md
rpotter6298 9d15860f0b first_commit
2026-04-25 20:35:25 +02:00

72 lines
2.2 KiB
Markdown

# 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:
```text
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:
```text
Open-Meteo forecast run -> WeatherBuilder -> clean forecast records
```
Then grow into:
```text
Open-Meteo -> parser -> WeatherBuilder -> TimescaleDB -> weather_predictor.py
```