Skip to content

How Pulsar workflow types divide work

UseCases own application workflows; Operations reuse fragments. UseCases coordinate Actions, Operations, Queries, Contracts, transactions, and Events. Operations share branching fragments without becoming nested UseCases, while Actions stay atomic and Queries stay read-only.

Choose the workflow owner

UseCase vs Operation and Action vs Query

TypeOwns and may callReturnMust not
UseCaseOne workflow; Actions, Operations, Queries, Events, and Domain Contracts.Delivery-neutral value or void.Call a UseCase or return HTTP/CLI delivery types.
OperationReusable multi-step branch for UseCases; Actions, Queries, Models, and Contracts.Delivery-neutral value.Be called by an adapter, call an Operation or UseCase, own a transaction or emit an Event.
ActionOne atomic business operation over an aggregate; Model or Contract.Domain value or void.Call an Action or Query, coordinate Domains, own a transaction or Event.
QueryOne complex read; Model.Delivery-neutral read value.Mutate or call a Query or Action.

Every workflow-bearing type uses execute(). Stable dependencies belong in a constructor; runtime values belong in execute(). An HTTP Request and ambient Request or tenant state belong at the Adapter, not in a workflow constructor or signature.

Call graph and return boundary

The execute method carries runtime data

An HTTP Controller calls one read-only UseCase, which calls one or more Queries and returns a delivery-neutral value for Resource or Inertia assembly without a transaction.
Read through a UseCase without a transaction. Reads retain the Controller-to-UseCase boundary while Queries stay inside the read-only workflow.

Interpretation. An HTTP Request reaches one Controller and then one read-only UseCase. That UseCase calls one or more Queries, returns a collection, DTO, or value without a transaction, and the Controller assembles a Resource or Inertia response.

The package's first feature proves the write route, not a Query route. Its absence is intentional: the generic figure preserves the current Controller → UseCase rule instead of manufacturing a fixture.

Put conditional work in Operations

Reuse one shared workflow fragment

Use an Operation only when multiple UseCases need the same multi-step sequence or conditional branch. The Operation remains a fragment inside its caller's workflow: it has no Adapter authority, transaction boundary, or Event-emission authority.

Context: ❌ Prohibited nested workflow — A reusable branch must not become a second UseCase.

php
$useCase->execute($data);

Context: ✅ Correct reusable fragment — The UseCase delegates the shared branch to an Operation.

php
$operation->execute($data);

Transform nested UseCases and Action composition

Replace a nested UseCase

An Action is not a workflow container. Keep its aggregate Operation atomic; make the UseCase or a reusable Operation select and order separate steps. Likewise, a UseCase never calls another UseCase: extract the shared fragment into an Operation rather than nesting transactions, Events, or application entrypoints.