Skip to content

post-1.0: Declarative dependency ordering for ingestion pipeline middlewares #135

Description

@bitbiter-dev

Current state

Middleware execution order is explicit via int Order on IIngestionMiddleware, with an IngestionOrder constants class defining canonical positions (Logging=0, ContentHashing=10, IdempotencyCheck=20, ExifExtraction=30, Persistence=40). IngestionPipelineRunner sorts by Order at construction time and throws InvalidOperationException on duplicate orders — misconfiguration fails at DI scope creation, not mid-file.

CanInvoke / OnCannotInvoke / PipelineConfigurationException were removed as part of implementing this. The positional/semantic ordering concern from the original design (logging must be outermost, idempotency before EXIF) is fully addressed by the Order constants.

Remaining enhancement (post-1.0)

As the pipeline grows, manually assigning integer slots becomes awkward — especially for middleware added by contributors who shouldn't need to know the global order table. A data-driven dependency graph would let each middleware declare what it produces/needs and have the runner derive the order automatically.

Each IIngestionMiddleware would declare:

internal interface IIngestionMiddleware
{
    // ...existing members...

    /// Context properties this middleware expects to be populated before it runs.
    IReadOnlySet<string> Needs => [];

    /// Context properties this middleware guarantees to populate after it runs.
    IReadOnlySet<string> Produces => [];
}

IngestionPipelineRunner.BuildValidated would perform a topological sort and throw a descriptive error at startup for:

  • An unsatisfied dependency (a Needs entry with no matching Produces).
  • A circular dependency.

The int Order property would remain as a tie-breaker for pairs that share no data dependency (e.g. IdempotencyCheck vs ExifExtraction — both need ContentHash but neither produces what the other needs; Order keeps IdempotencyCheck earlier to avoid reading EXIF for an already-ingested file). LoggingMiddleware has no data deps either and stays outermost via Order=0.

Example declarations:

ContentHashingMiddleware   produces: { ContentHash }
IdempotencyCheckMiddleware needs: { ContentHash }
ExifExtractionMiddleware   needs: { ContentHash }   produces: { Exif }
PersistenceMiddleware      needs: { ContentHash, Exif }
LoggingMiddleware          (no data deps; Order=0 keeps it outermost)

Acceptance criteria

  • IIngestionMiddleware exposes Needs and Produces with empty-set defaults (no breaking change for existing middlewares).
  • IngestionPipelineRunner derives execution order from the declared dependency graph (topological sort), using int Order as a tie-breaker.
  • Startup throws a clear error for unsatisfied dependencies or cycles, naming the offending middlewares.
  • All existing middleware tests continue to pass; new tests cover topological sort, cycle detection, and missing-producer detection.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    deferredConsciously skipped — not needed for v1.0 but important to address laterenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions