Distributed Spans and Causality
The same subscribe request that takes 800ms is spending 400ms in the payment adapter, 200ms in the projection update, 100ms in three other places. Without distributed tracing, the 800ms is opaque. @frenchexdev/ddd-tracing reifies the port that makes those 800ms decomposable into named spans with parent-child relationships, so the slow operation is identifiable rather than guessed at.
What Tracing Reifies
A span names a unit of work: a HTTP request, a database query, a domain service invocation. Spans have a start time, an end time, attributes, and a parent — the span that triggered them. The parent-child chain, propagated across process boundaries, is what makes a distributed trace coherent: a request enters one service, spawns a span; that service calls another; the trace context propagates; the second service's spans are children of the first; an aggregate viewer (Jaeger, Tempo, Honeycomb) stitches the chain into a single timeline.
The port reifies four operations. startSpan(name, attrs) opens a span. endSpan(id, status) closes it with success or error. inject(carrier) serialises the current trace context into a carrier (HTTP headers, message-bus headers, outbox row) so the next process can pick it up. extract(carrier) is the inverse — pull the trace context out of a carrier and use it as the parent for new spans. The four operations are the OpenTelemetry shape; the corpus types them and lets the adapter handle the wire format.
The Runtime: ddd-tracing and adapter
Two packages — ddd-tracing, ddd-tracing-otel-adapter — both M4/M5 stubs pinned to TelemetryStructuredObservabilityRequirement. The OpenTelemetry adapter is the obvious first substrate — OpenTelemetry is the closest the industry has come to a vendor-neutral tracing wire format, and most modern aggregators speak it.
The Analyzer: ddd-tracing-analyzer
Spec-first (spec.ts). Priority Medium. One info-severity rule, DDD-TRACING-001, recommending the Tracer suffix on the port class.
The cross-cutting nature of tracing means future analyzer rules are conceivable but not yet shipped — for example, verifying that every @CommandHandler opens at least one span, that every adapter call propagates the trace context, that every @DomainEvent carries the context across the @Outbox. These would be cross-AST rules; the spec is where they will be added.
Cross-Links
- Telemetry trio member with
@Logand@Metrics. - Spans wrap
@CommandHandler,@QueryHandler, and@DomainServiceinvocations. - Context propagates through the
@Outbox(the carrier is a column on the outbox row) and the@EventBus(the carrier is a field on the event envelope) so cross-process causality is preserved. - Lives behind
@Port/@Adapter; the OpenTelemetry adapter satisfies the conformance suite.
Back to the series index.