Expand description
§ferro-projection
Live read-model runtime: subscribe to domain events, persist per-key snapshots, broadcast deltas.
Not to be confused with ferro-projections (plural). That crate
is the Service Projection abstraction (ServiceDef → IntentGraph → JsonUiRenderer). This crate (ferro-projection, singular) is the
live read-model runtime that subscribes to domain events, maintains a
materialized state, and broadcasts deltas. The two abstractions are
orthogonal — most apps will use both for different reasons.
ferro-projection is the live-read-model primitive. ferro-events
says something happened. ferro-broadcast says something is
visible to clients. ferro-projection composes the two: events fold
into per-key state, deltas land on projection.{name}.{key} channels.
§Per-key serialization
Event::dispatch() ─┐
│
ProjectionListener<P> ──┐
│
▼
┌── per-key Mutex (DashMap<String, Arc<Mutex<()>>>) ──┐
│ 1. load snapshot from projection_snapshots │
│ 2. apply(&mut state, &event) → Delta │
│ 3. upsert snapshot (state, version+1) │
│ 4. broadcast on projection.{name}.{key} │
└─────────────────────────────────────────────────────┘
│
▼
WebSocket clients receive the delta§Schema and migration
ferro-projection ships a SeaORM migration as
CreateProjectionSnapshotsTable. Register it in your consumer-side
Migrator:
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(ferro_projection::CreateProjectionSnapshotsTable),
// ... your app migrations
]
}
}§Operational footguns
- Broadcast failure does NOT roll back state. If
Broadcast::sendreturnsErr, the snapshot row is already persisted; the runtime logs attracing::warn!and surfacesProjectionError::Broadcast. Subscribers reconcile by re-reading the snapshot. - Single-instance assumption. v0 assumes a single application instance owns each projection’s listener. Multi-instance deployments must elect a single projection-runner node or accept last-writer-wins behavior on concurrent applies to the same key from different nodes.
registeris not idempotent onArcidentity. CallingArc<ProjectionRuntime<P>>::register()twice registers two listeners — both fire on each dispatch (same semantic as Laravel’sEvent::listen). Register once at app startup.
Structs§
- Create
Projection Snapshots Table - Projection
Key - Opaque stringly-typed identifier (D-11).
- Projection
Runtime - Live read-model runtime owning the DB connection, the broadcaster handle, the projection impl, and the per-key Mutex registry (D-13).
- Projection
Snapshot Active Model - Generated by sea-orm-macros
- Projection
Snapshot Entity - Generated by sea-orm-macros
- Projection
Snapshot Model
Enums§
Traits§
- Projection
- Consumer-implemented live read-model (D-06).