Skip to content

Dependency direction and call rules

Pulsar separates passive type references from behavioral calls. Delivery calls inward to Domain, Infrastructure implements Domain Contracts, and the detailed matrix below decides the permitted behavioral edges.

Passive type reference and behavioral call

A passive type reference only mentions a type in a signature, return value, property, or data construction. It does not transfer workflow control. A behavioral call invokes another type’s behavior. Imports alone never make a prohibited behavioral edge permissible.

Context: paired reference and call — A Controller may carry a Domain DTO but enters behavior through one UseCase.

php
final class CreateOrderController
{
    public function store(CreateOrderData $data, CreateOrder $useCase): OrderResource
    {
        $order = $useCase->execute($data); // one behavioral call

        return OrderResource::make($order);
    }
}

CreateOrderData is a permitted passive Domain DTO reference. Calling the UseCase is permitted behavior. A Controller must not make a behavioral call to a Query, Operation, or Action. A Contract signature may mention DTOs, Enums, Value Objects, Models, Events, other Contracts, or Laravel contracts. It must not make a behavioral call to a UseCase, Action, Operation, Service, or concrete Infrastructure type.

Domain Contract layer direction

Origin layerBehavioral direction
Service deliveryInward to Domain through its documented entry types.
DomainItself and its own Contracts.
InfrastructureInward to Domain Contracts it implements.

Nothing points at Delivery: Domain and Infrastructure do not make behavioral calls to Services. That rule does not turn permitted passive values into a delivery-orchestration loophole.

Dependency matrix for behavioral calls

OriginBehavioral targetDecision
Controllerexactly one UseCaseAllowed. The Controller stays thin and may assemble delivery response after it returns.
ControllerQuery, Operation, or ActionProhibited.
Job or Commandexactly one UseCaseAllowed. Validate, authorize, and establish context first.
Job or CommandAction, Operation, transaction, or branching business logicProhibited.
UseCaseAction, Operation, Query, Event, or Domain ContractAllowed within its stated responsibilities.
UseCaseanother UseCaseProhibited. Extract reusable workflow to an Operation.
OperationAction, Query, Model, or ContractAllowed.
Operationanother Operation, UseCase, or Event emissionProhibited.
ActionModel or ContractAllowed.
Actionanother Action or QueryProhibited.
QueryModelAllowed.
QueryAction or another QueryProhibited.
synchronous ListenerContract side effect, Notification, or JobAllowed. It must not call a UseCase.
queued Listenerat most one UseCaseConditional. Only for queued, idempotent form; synchronous Listener to UseCase is prohibited.
ContractUseCase, Action, Operation, Service, or concrete InfrastructureProhibited as a behavioral dependency.
Infrastructure Adapterits Domain ContractAllowed. It implements the consumer-owned port.
Infrastructure AdapterService, UseCase, Action, or OperationProhibited.
Service AService BProhibited. Cross-Service communication uses events, not a Service-to-Service call.
Domain or Serviceconcrete Infrastructure AdapterProhibited. Depend on the Domain Contract.

Keep unspecified details unspecified

The matrix is the exhaustive classified dependency rule set. It does not invent an Adapter-to-Adapter rule. Transaction mechanics, Event delivery tiers, authorization, tenancy, and binding details remain on later architecture pages. Use Placement for paths and Preset for the subset a test can check.