Skip to content

TPF and DDD Alignment (Current + Future) ​

This guide summarizes how TPF aligns with DDD concepts today, where it diverges, and what we intend to improve. It captures the key ideas from the checkpoint-pipeline discussion so newcomers can quickly orient themselves.

Executive Summary ​

  • TPF aligns with DDD at the use-case level: a pipeline is closest to an application service.
  • TPF diverges at the aggregate level: instead of a single aggregate with multiple tables, TPF uses a progression of immutable aggregate states, each of which is stored in its own table.
  • DDD layering is possible but not enforced: it depends on how steps are written.
  • Cross-context orchestration is compatible but needs explicit contracts and backpressure-aware piping.

DDD Terms (short and concrete) ​

  • Application service: orchestrates a use case; coordinates domain operations and infrastructure.
  • Domain operation: a business behavior on an aggregate or domain service (invariants live here).
  • Aggregate: consistency boundary in the domain model.
  • Bounded context: boundary where a model and language are consistent.
  • Cross-context orchestration: communication across bounded contexts (typically async in traditional DDD; TPF may prefer synchronous piping where practical - see §5 and the discussion below).

TPF Mapping to DDD ​

1) Application Service ​

TPF mapping: A pipeline is a use-case orchestrator. Each pipeline step can be seen as a slice of the application service.

  • Good alignment: pipelines naturally express use-case flow, sequencing, and side effects.
  • Risk: steps can accidentally mix domain logic and infrastructure if not disciplined.
  • TPF guidance: keep steps thin; put business rules inside domain types or domain services called by the step.

2) Domain Operation ​

TPF mapping: A step can wrap a domain operation, but the operation should still live in the domain layer.

  • Good alignment: step calls a domain method (e.g., Order.create, Order.approve).
  • Risk: domain logic implemented directly in step classes (layering leak).
  • TPF guidance: steps should orchestrate; domain operations should enforce invariants.

3) Aggregate Boundary ​

TPF mapping: The pipeline becomes the aggregate boundary, not a single class/table.

  • TPF uses a progression of aggregate states (one per step/table).
  • This is a deliberate trade-off: immutable, append-only checkpoints instead of in-place updates.
  • Example: OrderRequest -> InitialOrder -> ReadyOrder as successive stable states.

4) Bounded Context ​

TPF mapping: A pipeline (or set of pipelines) can represent a bounded context.

  • Works best when the context owns its data and invariants.
  • Cross-context calls should use explicit handoff contracts.
  • TPF guidance: treat pipeline-to-pipeline contracts as public APIs, not shared internal types.

5) Cross-Context Orchestration ​

TPF mapping: Sync, reactive piping is possible; async is optional.

  • The main risk is consistency illusion across pipelines.
  • TPF needs explicit connector semantics (backpressure + idempotency).
  • TPF guidance: only assume strong consistency inside a single pipeline; be explicit about cross-pipeline guarantees.

Workflow Shape: Linear/Tree vs DAG ​

TPF favors linear or tree-like flows where possible:

  • Linear/tree workflows are easier to reason about and preserve pipeline semantics.
  • DAG (directed acyclic graph) workflows introduce joins, fan-in, and potential feedback loops, which complicate ordering, timeouts, and backpressure.

The checkpoint model works best when pipelines are composed in a tree-like fashion, with explicit handoff contracts at each edge.

Decision as Checkpoint (rule of thumb) ​

Rather than introducing a special decision step, a decision can be modeled as a checkpoint boundary:

  • End the current pipeline at the decision.
  • Start one pipeline per outcome branch.
  • This keeps step semantics simple and makes outcomes explicit.

Operationally this adds orchestration, but it keeps the design model stable as the system grows.

Autonomy vs Consistency (TPF stance) ​

TPF does not treat autonomy as a default good. In a single business with a unified tech stack:

  • Strong checkpoint consistency is preferred within a pipeline.
  • Sync piping is preferred between pipelines when practical.
  • Eventual consistency is only accepted when explicitly chosen.

This differs from FTGO (Food To Go, from Chris Richardson's Microservices Patterns) default assumptions of async sagas and autonomy-first boundaries.

CreateOrder / ApproveOrder Examples (FTGO mapping) ​

CreateOrder

  • TPF pipeline steps: OrderRequestProcess -> OrderCreate -> OrderReady
  • Each step persists its own immutable state.
  • The pipeline output is the checkpoint, not a mutable aggregate.

ApproveOrder

  • A separate pipeline that consumes the checkpoint and produces a new immutable state.
  • If not explicitly modeled as a business pipeline step, an approval failure is treated as an operational concern (handled via retries, alerts, monitoring, etc.), whereas modeling it as a business pipeline makes it part of the business workflow and subject to business rules and compensating actions.

DDD Layering in TPF ​

TPF does not enforce layering. A suggested discipline:

  • Domain layer: entities, value objects, domain services.
  • Application layer: pipeline steps that orchestrate domain operations.
  • Infrastructure layer: persistence, mappers, transports.

Current Gaps ​

  • No explicit aggregate boundary declaration.
  • No built-in enforcement of layering.
  • Pipeline-to-pipeline contracts are implicit (build-time checks needed).

Planned Directions ​

  • Handoff contracts between pipelines (explicit, build-validated).
  • Traceability: built-in lineage tracking (TraceEnvelope, a planned runtime wrapper that carries a payload plus a reference to the previous item for lineage).
  • Backpressure contracts across piped pipelines.
  • Error sink as a first-class runtime component.
  • Workflow constraints: optional guidance to keep pipelines linear or tree-like by default.
  • Design diagnostics: flag risky patterns (e.g., implicit DAG joins, unbounded buffering).
  • Workflow fan-out: allow sub-pipelines to branch from a step with explicit branch policies; fan-out implies controlled tree-like branching only (no joins/merges that would create general DAG topologies).
  • Observers vs taps: distinguish stable checkpoint observers from mid-step taps with weak guarantees.
  • Remote subscription trigger: orchestrators accept streaming input for pipeline-to-pipeline chaining.