Skip to main content

dfe-fetcher architecture

The repo is a Cargo workspace: one app crate and a source framework split into an I/O-free core plus one leaf crate per shape family, with the file tailer vendored from Vector under third-party/. Every crate is publish = false and inherits its version from [workspace.package], so a release bump touches the root manifest only. Diagrams (system, the tick sequence, the profile grammar, the crate DAG) live in internals.md; this file is the codemap, the invariants and the build graph.

Codemap​

WhereWhat lives there
crates/core (dfe-fetcher-core)The row model (Row, Mark, RowSource, UnitSpec, TickCtx), the batcher, the compiled per-row rules (CEL filter, routes, added fields), the snapshot envelope and its reassembler, the checkpoint contract (CursorStore, CursorValue, mark folding), the once-resolved secret cell (the resolver is the I/O crate's), the framework's error type and the list of metric names. No HTTP, no transport, no filesystem.
crates/rest (dfe-fetcher-rest)The declarative REST profile grammar and its validation, {{ cel }} templates, the runtime axes (auth modes, pagers, streaming decoders, the request executor with retry and its per-unit origin gate), the hooks (row builders and the S3 lister), the queue shape, and RestShape, the RowSource for a bound profile.
crates/db (dfe-fetcher-db)The Store contract (dump, keyset tail), the sources.db grammar, DbShape (the RowSource over an instance's stores), the per-dialect keyset predicate, the blocking-cursor pump and the block-to-row framing that leases every buffered block. Engines are features: odbc (unixODBC + arrow-odbc typing + arrow-json writing), clickhouse (server-side JSONEachRow, dfe-clickhouse for the ping and typed exceptions) and mongodb (the official driver: find to dump, change stream or _id to tail).
crates/file (dfe-fetcher-file)The FileSource contract, the sources.file grammar, the dump reader (NDJSON, JSON array, CSV, gzip by magic) and the tail spec; the tailer sits behind the tail feature over the vendored crates.
third-party/vector-file-source{,-common}Vector's file tailer (MIT), vendored and depended on by path from crates/file only; provenance in third-party/README.md.
crates/fetcher (dfe-fetcher, the binary)The config cascade and hot reload, the typed source blocks, their registry and their mapping onto profile instances (config/builtin.rs), the scheduler, Driver (the tick the scheduler runs for every framework shape), the emitter, the pipeline (enrichment, DLQ, readiness), the output transports, the cursor file store, the shipped REST profiles (profiles/*.yaml), the deployment contract and capability catalog, the ingest server and the container and Vector extractors.
config.example.yaml, docs/Operator-facing surface, at the repo root; the app crate's tests reach them through the manifest directory.

Adding a REST source is a profile (YAML) plus an instance in sources.rest; adding a hook is a variant on an axis enum in crates/rest; adding an engine is a feature-gated module in crates/db. A typed source block (sources.github, sources.aws) keeps its struct for the operator and maps onto an instance of the shipped profile of the same name at load; the driver runs it like any sources.rest entry.

Negative invariants​

  • dfe-fetcher-core depends on no other workspace crate and on scalo only for expression. It never names an HTTP client, a transport or a file. Async appears only as futures-core stream and future types; tokio is linked for its clock types and its once-cell and never spawns a task there.
  • dfe-fetcher-rest, dfe-fetcher-db and dfe-fetcher-file depend on core and never on each other or on the app. None sends to a transport, writes a cursor or reads the config cascade; each receives bound units and yields rows.
  • The app never re-implements a shape, a pager, a decoder or an auth mode; it owns wiring, transports, checkpoint persistence, config and the deployment contract. There is no per-provider Rust in the app: a provider is a profile, and what a profile cannot say is a hook on an axis in crates/rest.
  • No crate depends on a Vector crate from a registry. The file tailer is the vendored copy under third-party/, depended on path-only by dfe-fetcher-file.
  • Nothing deployment-specific: no hostnames, addresses, vault paths or licence detail outside config.example.yaml placeholders and tests.

Build graph​

core is the root and compiles in seconds (serde, bytes, chrono, uuid, smallvec, csv-core, cel via scalo expression). rest (reqwest, async-compression, backon, reqsign), db and file are siblings off core and compile in parallel. The cold-compile critical path is unchanged from the single-crate layout: scalo with transport-kafka (librdkafka) and transport-grpc (tonic, prost), then the app's link. A profile or pager change rebuilds rest and the app; an engine bump rebuilds db alone. db is always linked for its grammar (a few serde types), so a sources.db block naming an engine the binary lacks is refused at load with the feature to build; the engines themselves and the file tailer are off the default build.

Feature flags on the app follow scalo's <capability>-<backend> naming: db-odbc, db-clickhouse, db-mongodb, file, file-tail, plus jemalloc and full. .hyperi-ci.yaml lists feature sets explicitly so a runner is never asked for a native driver it does not carry; db-odbc needs unixODBC on the host. The release build adds db-odbc, and its image carries unixODBC and the open-licence drivers (docs/reference/db-drivers.md).