Skip to content

Architecture

Hyperion is organised as a set of layers following a ports-and-adapters (hexagonal / DDD) design. The goal: the same orchestration code runs on a laptop or on AWS, and which backend is used is a configuration decision made in exactly one place.

The layers

Layer Package Responsibility May depend on
Domain hyperion.domain Pure asset identities, messages, geo math. No I/O. lite core only
Ports hyperion.ports Abstract contracts: StoragePort, Cache, Queue, SchemaStore, SecretsManager, KeyValueStore, Geocoder. domain
Adapters hyperion.adapters Concrete backends (memory / filesystem / S3 / DynamoDB / SQS / Google Maps / avro). domain, ports
Application hyperion.catalog.catalog, hyperion.application Orchestration — the Catalog. Depends on ports, never on adapters. domain, ports
Composition hyperion.composition The only module that imports both ports and adapters and builds adapters from config. everything
Data hyperion.data The pandera/polars validation stack — a leaf, behind [data]. domain

The lite core (hyperion.log, hyperion.config, hyperion.dateutils, hyperion.asyncutils, hyperion.typeutils) sits below all of this and is always installed.

How a request flows

Catalog.from_config() asks the composition root for adapters; the composition root reads hyperion.config and constructs the concrete S3Storage / LocalSchemaStore / cache / queue that the environment selects. The Catalog then talks only to the port interfaces — it never imports an adapter (the one documented carve-out is the avro serializer). Swapping filesystem for S3 changes configuration, not the Catalog code.

config ──> composition root ──constructs──> adapters (S3 / filesystem / …)
                  │                              │ implement
                  └──> Catalog ── depends on ──> ports (StoragePort, Cache, …)

A Source sits on top: it extracts external data and yields assets that the Catalog stores; messages about arrivals flow through the Queue port.

Why this shape

  • Testability — the application tier is exercised with in-memory adapters, no AWS.
  • Optionality — heavy backends live in adapters and are gated by extras (see Lite core and extras); the lite core stays dependency-light.
  • One wiring point — backend selection is centralised in the composition root instead of being scattered through call sites.

The precise "who may import whom" rules and how they are enforced are covered in Ports and adapters.