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

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