Distributed Task DSL
"A distributed task is not a domain concept. It is an infrastructure concern that crosses every domain. That is exactly why it belongs in its own DSL."
Why a Separate DSL
The CMF has five M2 DSLs: DDD, Content, Admin, Pages, and Workflow. Each models a specific domain concern — aggregates, content parts, admin panels, page routing, state machines. They are tightly integrated because they describe the same system from different angles.
DistributedTask is different. It does not model a domain. It models an infrastructure pattern: submit a multi-step task, process it asynchronously with saga compensation, report progress, handle failure. This pattern appears in every domain — e-commerce order fulfillment, media processing, document generation, data migration — but it is not specific to any of them.
Embedding it inside the CMF would violate separation of concerns. A team using DistributedTask.Dsl for a video transcoding pipeline should not need a dependency on Cmf.Content.Lib. A team using the CMF for content management should not need RabbitMQ and MinIO if they have no distributed tasks.
The solution: DistributedTask.Dsl is a standalone M2 DSL that composes with the CMF but does not depend on it.
M3 Grounding
All DistributedTask attributes — [DistributedTask], [SagaStep], [TaskRequest], [TaskResponse], [CompensationFor], [RetryPolicy], [S3Upload], [S3Download], [Parallel], [RequiresRole], [EncryptMessages] — are registered as MetaConcepts in the M3 meta-metamodel.
This means:
- The
MetamodelRegistryknows about them. Tooling that enumerates "all DSL concepts in the solution" will include DistributedTask concepts alongside DDD aggregates, Content parts, and Workflow states. - Cross-DSL analyzers can validate references. A Workflow transition that triggers a DistributedTask can be checked at compile time: does the task exist? Does its request type match the transition's payload?
- The CLI (
designtool) can scaffold DistributedTask declarations the same way it scaffolds aggregates or content types.
M3 registration is the contract that makes a DSL a first-class citizen of the ecosystem, regardless of whether it lives in the CMF repository or in its own package.
Entity.Dsl: Saga State Persistence
Every running saga needs state — which step it is on, what data each step produced, whether compensation has started. That state is an aggregate, and Entity.Dsl already knows how to generate persistence for aggregates.
The DistributedTaskInstance entity is defined using Entity.Dsl attributes. The generator produces an EF Core DbContext, migrations, and a repository. The saga orchestrator reads and writes through that repository. No hand-written SQL. No manual migration scripts.
Workflow: Bidirectional Composition
A Workflow transition can trigger a DistributedTask. For example, when a content item moves from Draft to Processing, the transition fires a ProcessContentTask saga that optimizes images, generates thumbnails, and validates links. When the saga completes (or fails), it posts an event that the Workflow state machine consumes, advancing to Published or rolling back to Draft.
The reverse also works: a saga step can advance a Workflow. A document generation saga might move the document through Generating -> ReviewReady -> Approved as steps complete.
Both directions are compile-time checked. The analyzers verify that the task name referenced in a transition exists and that the Workflow state referenced in a saga step exists.
DDD: Typed Request and Response
A [TaskRequest] can reference DDD aggregate types. A saga that processes an order takes an OrderId (a DDD value object). A saga that generates invoices returns an InvoiceAggregate summary. The source generator ensures the request and response DTOs include proper serialization for DDD types — EntityId as string, value objects as flattened properties.
Content: Processing Pipelines
The Content DSL defines parts and blocks — images, documents, video embeds. Content processing (image optimization, PDF generation, video transcoding) is a natural fit for DistributedTask.Dsl. A ContentPublishingPipeline saga can iterate over a content item's parts, process each file through S3, and update the content record when done.
The integration is not structural (no shared attributes) but compositional: the saga references content types, and the content system fires events that trigger sagas.
Admin: Task Monitoring Dashboard
The Admin DSL generates back-office panels. A future integration will generate an admin page that lists running tasks, shows step progress, allows cancellation, and displays saga history. This is not yet implemented — it is tracked as a feature in the Requirements DSL.
The Full Series
The DistributedTask.Dsl design is documented across sixteen articles covering the problem statement, DSL syntax, code generation, saga orchestration, resilience, observability, listening strategies, comparison with alternatives, and security:
What the DSL Generates
From a set of C# attributes (~55 lines of developer code), the DistributedTask.Dsl source generator produces ~700+ lines of infrastructure: a saga orchestrator with state machine and compensation, an ASP.NET Core controller with submit/cancel/status endpoints, queue consumers with deserialization and dispatch, per-step Polly retry wrappers, OpenTelemetry tracing spans, Redis distributed locking, idempotency deduplication, S3 upload/download plumbing, five notification transports (Polling, SSE, WebSocket, SignalR, Webhook), health checks, metrics, and 14 compile-time Roslyn analyzers that catch misconfigurations before the code leaves the IDE. The developer writes intent. The generator writes infrastructure.