Skip to content

Is a pipeline just an application service in a trench coat?

Elevator answer

Both coordinate a use case. The pipeline earns the trench coat when the method also has to survive API calls, retries, Kafka, a callback on Thursday, and an operator asking what happened.

3 coffee-machine misconceptions

DDD Diego

“Rename the application service Pipeline, and the aggregate will immediately respect its own invariants.”

Enterprise Edna

“An application service becomes strategic after it receives enough injected collaborators to require a table of contents.”

Functional Fran

“Coordination is impure, therefore it should be hidden in a module named after a weather pattern.”

The real explanation

The question is fair. An application service receives PlaceOrder, loads what it needs, invokes domain behavior, saves the result, and perhaps calls outward. A pipeline also coordinates that work. If the only change is renaming PlaceOrderService to PlaceOrderPipeline, the architecture has acquired outerwear.

An application service can hide the whole itinerary in process(): map the request, split the items, call the provider, merge results, and publish. A pipeline puts the step types, mappings, connector calls, count/order promises, and required adapters where the compiler and runtime can inspect them together. Same coordination territory; fewer crucial facts hiding between method bodies and configuration files.

That extra declaration does not demote an application service. It makes a particular kind of application service more explicit: one whose behavior crosses an operational boundary and needs shared semantics around it. Consider an order path that validates a command, obtains a pricing fact, decides an outcome, publishes an external event, and later receives a correlated completion. The domain decisions can remain in rich entities, policies, or small domain services. The pipeline owns the itinerary through those decisions and the shell that makes the itinerary reliable: admission, transport, retries, correlation, generated adapters, telemetry, and durable waiting where required.

This is why a pipeline does not correspond to every use case. “Change the customer’s preferred display name” is probably one transaction and a short service. “Accept an order, fetch a risk score, reserve stock, notify fulfillment, then correlate its later callback” has a failure story that no single happy-path method body tells. That is where the explicit flow starts paying rent.

The distinction also protects the domain model from a common application-service failure mode. Application services often grow into places where business policy, repository choreography, exception translation, message production, retry behavior, and framework calls accumulate because each next requirement is convenient there. A pipeline can grow badly too, but its declared steps, inputs, outputs, and boundaries make the growth visible. It is harder to pretend that an unrelated side effect is merely another line in a coordinator when it must occupy a named place in a flow.

TPF does not prescribe that all coordination be declarative or that every business rule become a step. The business function should still have a coherent name and a testable reason for existing. The pipeline is the composition of those functions for an execution path; it is not the new home for every conditional. That is why a rich domain model and a pipeline can complement each other. The domain owns meaning and invariants. The pipeline owns a use-case-shaped execution contract around that meaning.

The trade-off is that the boundary must be designed. A vague “process order” pipeline can become a giant application service with nicer metadata. A team needs to split a flow when it crosses a genuine ownership boundary, keep domain behavior in the domain, and resist turning orchestration into the only place business language is allowed. TPF helps enforce a model; it cannot choose the model’s responsibility for you.

The memorable idea is therefore not “pipeline replaces application service.” It is “a pipeline makes the operationally significant application service visible as a contract.” Where that significance does not exist, use an application service and enjoy the shorter file.

Trade-offs

TPF gains a shared, compilable description of coordination that crosses boundaries. It gives up some local freedom: important flow choices must be named and validated rather than left inside a method. Teams still need ordinary application services for local work.

When TPF is not a good fit

Use a conventional application service when a use case is local, transactional, and easily understood in one module. TPF is not useful merely because a method coordinates two domain calls; it earns its model when execution semantics, adapters, or external boundaries are part of the concern.