Skip to main content
Welcome. This site supports keyboard navigation and screen readers. Press ? at any time for keyboard shortcuts. Press [ to focus the sidebar, ] to focus the content. High-contrast themes are available via the toolbar.
serard@dev00:~/cv

Comparison

"The best framework is the one you don't have to fight. The second best is the one that fights for you at compile time."


Amplification Ratio

A typical DistributedTask declaration — the attributes, the request class, the step overrides — weighs in around 55 lines of developer-written code. From those 55 lines, the source generator produces 700+ lines of infrastructure:

  • Saga orchestrator with state machine
  • Controller with submit, cancel, status endpoints
  • Queue consumer with deserialization and dispatch
  • Per-step retry wrappers with Polly
  • Compensation handlers with rollback ordering
  • OpenTelemetry spans per step
  • Redis distributed locking
  • Idempotency deduplication
  • S3 upload/download plumbing
  • Five listening transports (Polling, SSE, WebSocket, SignalR, Webhook)
  • Health checks and metrics
  • Strongly-typed request/response DTOs

That is a ~13x amplification ratio. The developer writes the "what." The generator writes the "how."


DistributedTask.Dsl vs MassTransit Sagas

MassTransit is a mature messaging abstraction for .NET. Its saga state machines are powerful — but they are imperative. You write a class that inherits MassTransitStateMachine<T>, define states, events, transitions, and activities in code. You wire message consumers manually. You handle compensation yourself.

DistributedTask.Dsl is declarative. You annotate a class with [DistributedTask], mark steps with [SagaStep], and the generator produces the state machine. Compensation is declared with [CompensationFor]. Retry is declared with [RetryPolicy].

Aspect MassTransit DistributedTask.Dsl
Paradigm Imperative state machine class Declarative attributes
Validation Runtime (exceptions on misconfiguration) Compile-time (Roslyn analyzers)
Compensation Manual (you write it) Generated orchestration (you write the handler)
Transport Abstracted (RabbitMQ, Azure SB, etc.) RabbitMQ (explicit, not abstracted)
Learning curve Medium-high Low (attributes + Generation Gap overrides)

MassTransit is a library — it runs your code at runtime. DistributedTask.Dsl is a code generator — it writes your code at compile time.

DistributedTask.Dsl vs Temporal / Durable Functions

Temporal (and Azure Durable Functions) use replay-based recovery: when a workflow fails, the engine replays the workflow function from the beginning, skipping already-completed steps using a journal. This is elegant but requires a dedicated server (Temporal Server or Azure infrastructure) and imposes constraints on workflow code (must be deterministic, no side effects in the replay path).

DistributedTask.Dsl uses saga-pattern compensation: when a step fails after retries, the orchestrator runs compensation handlers in reverse order. No replay. No dedicated server. The infrastructure is self-hosted: MinIO for S3-compatible storage, RabbitMQ for messaging, Redis for locking and pub/sub.

Aspect Temporal / Durable Functions DistributedTask.Dsl
Recovery model Replay-based journal Saga compensation
Infrastructure Temporal Server or Azure Self-hosted (MinIO + RabbitMQ + Redis)
Language support Multi-language SDKs C# only
Determinism constraint Strict (workflow must be deterministic) None (steps are independent units)
Cloud dependency Yes (Temporal Cloud or Azure) None (runs on-prem, in Docker, anywhere)

If your team already runs Temporal in production and needs multi-language workflows, use Temporal. If you are a .NET shop that wants compile-time safety and self-hosted infrastructure, DistributedTask.Dsl is the lighter path.

DistributedTask.Dsl vs Hangfire

Hangfire is a job scheduler. It excels at fire-and-forget background work: send an email, generate a report, clean up old records. It has a nice dashboard and supports delayed and recurring jobs.

But Hangfire is not a saga orchestrator. It has no concept of multi-step workflows with typed data flowing between steps. No compensation. No fan-out/fan-in. No distributed locking. No S3-first-class integration.

Use Hangfire for simple background jobs. Use DistributedTask.Dsl when the job is a saga.

DistributedTask.Dsl vs Camunda / Zeebe

Camunda is a full Business Process Management (BPM) engine with a BPMN visual designer. Non-technical stakeholders can draw process flows. It supports long-running human tasks, complex routing, DMN decision tables, and multi-tenant process deployment.

DistributedTask.Dsl is not a BPM engine. It is a lightweight C# DSL for developers who think in code, not diagrams. It generates infrastructure for technical sagas — file processing, data pipelines, integration workflows — where every participant is a machine.

Aspect Camunda / Zeebe DistributedTask.Dsl
Audience Business analysts + developers Developers only
Process definition BPMN XML / visual designer C# attributes
Runtime JVM (Zeebe is Go + Java clients) .NET
Human tasks First-class Not supported
Weight Heavy (full BPM platform) Lightweight (source generator)

If your processes involve human approvals, regulatory compliance, and non-technical stakeholders drawing diagrams, use Camunda. If your processes are technical sagas written by developers, use DistributedTask.Dsl.


Feature Comparison Matrix

Feature DistributedTask.Dsl MassTransit Temporal Hangfire Camunda
Typed request/response Yes (generated DTOs) Manual Yes (SDK) No No (XML)
Saga compensation Yes (generated) Manual Replay-based No Yes (BPMN)
Per-step retry Yes ([RetryPolicy]) Manual (Polly) Built-in Global only Yes (BPMN)
Fan-out/fan-in Yes ([Parallel]) Manual Yes No Yes (BPMN)
Client-chosen notifications Yes (5 transports) No No Polling only No
Compile-time validation Yes (14 analyzers) No No No No
Generation Gap overrides Yes N/A N/A N/A N/A
Distributed locking Yes (RedLock) No Built-in No Built-in
Idempotency Yes (generated) Manual Built-in No Built-in
OpenTelemetry Yes (generated spans) Yes (built-in) Yes (built-in) No Partial
S3 first-class Yes ([S3Upload]) No No No No
InProcess dev mode Yes No Local dev server In-memory No

Compile-Time Diagnostics

The source generator ships 14 Roslyn analyzers that catch misconfigurations before the code compiles:

ID Severity Rule Fires When
DST001 Error Missing [TaskRequest] [DistributedTask] class has no request type
DST002 Error Missing [TaskResponse] [DistributedTask] class has no response type
DST003 Error Duplicate step order Two [SagaStep] attributes share the same Order value
DST004 Error Compensation target not found [CompensationFor] references a step name that does not exist
DST005 Error Circular step dependency Step graph contains a cycle
DST006 Warning Missing compensation A step that modifies external state has no [CompensationFor] handler
DST007 Error Invalid retry configuration MaxRetries is negative or DelayMs is zero with exponential backoff
DST008 Warning Unreachable step A step has no incoming dependency and is not the first step
DST009 Error Fan-out without fan-in [Parallel] group has no corresponding join step
DST010 Error S3 bucket not configured [S3Upload] or [S3Download] used but no [S3Configuration] on the task
DST011 Warning Large step count Saga has more than 20 steps (likely a design smell)
DST012 Error InProcess mode with distributed lock [DistributedLock] used in InProcess mode (no Redis available)
DST013 Warning Webhook without HMAC ListeningStrategy.Webhook enabled but no [WebhookHmacSecret] configured
DST014 Error Duplicate task name Two [DistributedTask] classes share the same name

These diagnostics appear in the IDE as you type — red squiggles, warnings in the Error List, and CI build failures. No runtime surprises.


Use DistributedTask.Dsl when:

  • You are a .NET shop and want to stay in C#
  • Your workflows follow the saga pattern (multi-step, compensatable)
  • You process files through S3 (uploads, transformations, downloads)
  • You want compile-time safety — analyzers catch errors before runtime
  • You want Generation Gap overrides — customize generated code without forking
  • You need multiple notification transports (SSE, WebSocket, SignalR, Webhook, Polling)
  • You prefer self-hosted infrastructure over cloud dependencies

Use MassTransit when:

  • You are already invested in MassTransit's ecosystem (consumers, sagas, middleware)
  • You need transport abstraction (switch between RabbitMQ, Azure Service Bus, Amazon SQS)
  • Your team is comfortable with imperative state machine code

Use Temporal when:

  • You need replay-based recovery with a durable execution journal
  • Your team works in multiple languages (Go, Java, Python, TypeScript, .NET)
  • You are cloud-native and can run Temporal Server (self-hosted or Temporal Cloud)
  • You need long-running workflows (days, weeks) with sleep/timer support

Use Hangfire when:

  • You need simple background jobs — send email, generate report, clean up
  • You want a dashboard out of the box
  • You do not need compensation, saga orchestration, or typed step data

Use Camunda when:

  • Non-technical stakeholders design and modify business processes
  • You need BPMN compliance for regulatory or audit reasons
  • You need human task management (approvals, reviews, escalations)
  • You are willing to run a JVM-based BPM platform

What's Next

Part XVI: Security — authorization handlers, HMAC-signed webhooks, and queue encryption. The final piece before the system is production-ready.