Skip to content

Where a Pulsar transaction belongs

A UseCase owns the transaction boundary. A UseCase opens the transaction for its mutation workflow; Operations and Actions participate without owning one. A read-only UseCase may call Queries without a transaction, and returned values remain delivery-neutral.

Choose a transaction boundary

Only a UseCase opens DB::transaction(). Controllers, Jobs, Commands, Listeners, Operations, Actions, and Queries never begin, commit, or roll back one. A mutation UseCase opens a boundary when the workflow requires atomicity; a read-only UseCase does not require one.

Context: source-backed first-feature shape — The UseCase encloses the Action and returns the completed Domain value.

php
return DB::transaction(function () use ($data) {
    return $createOrderAction->execute($data);
});

Participation and failure

Operations and Actions participate by running inside the UseCase closure. They never create nested transaction ownership. A thrown exception leaves the closure, lets Laravel roll back, and has no success return or post-success Event. On success, the UseCase returns after the boundary so the Adapter can shape delivery.

WorkflowTransaction ruleReturn rule
Mutation UseCaseOpen one boundary when atomicity is required.Return a delivery-neutral result after success.
Read-only UseCaseNo transaction requirement; may call one or more Queries.Return collection, DTO, Value Object, primitive, array, or void.
Operation or ActionParticipate in the calling UseCase boundary.Return only to the workflow caller.

Return after the workflow

Resources, Inertia wrappers, redirects, response objects, CLI exits, and scheduler timing are Adapter work. A UseCase must not return them. This separation gives a Job, Command, and Controller the same workflow result while retaining their own delivery responsibilities.

Transform transaction-in-Action

Context: ❌ Prohibited Action transaction — An Action must not create its own boundary.

php
return DB::transaction(fn () => $model->save());

Context: ✅ Correct UseCase transaction — The UseCase owns the boundary and calls the Action inside it.

php
return DB::transaction(fn () => $action->execute($data));