-
-
Notifications
You must be signed in to change notification settings - Fork 468
@bus directive
#2648
Introduction
Lately, as I'm working a lot with Laravel's command bus, I started noticing that most mutation resolvers in codebases I'm working on merely dispatch a job onto the command bus (as validation and authorization can all be done through existing Lighthouse directives). Unless I missed something, you currently have to create a custom resolver in order to dispatch a job on the command bus. For example:
namespace App\GraphQL\Mutation; use App\Jobs\ProcessPodcast as ProcessPodcastJob; use App\Models\Podcast; use Illuminate\Contracts\Bus\Dispatcher; use Nuwave\Lighthouse\Support\Contracts\GraphQLContext; final readonly class ProcessPodcast { public function __construct( private Dispatcher $bus, ) {} public function __invoke(null $root, array $args, GraphQLContext $context): Podcast { return $this->bus->dispatch(new ProcessPodcastJob($args['podcast'])); } }
This leads me to wonder if it could be interesting to have a Lighthouse directive to directly map mutations to a job from within the schema definition.
Proposal
I would propose to introduce a new @bus directive with a similar setup to the @event directive:
""" Dispatch a (queued) job using the Laravel Command Bus. """ directive @bus( """ FQCN for the job to dispatch. """ dispatch: String! """ Dispatch a job immediately (synchronously). The job will not be queued and will be executed immediately within the current process. """ sync: Boolean! = false """ Set the pipes using FQCNs the job should be piped through before dispatching. """ pipeThrough: [String!]! = [] ) on FIELD_DEFINITION
With this specification, the example in the introduction could be implemented as follows:
type Mutation { processPodcast(podcast: ID!): Podcast @bus(dispatch: "App\\Jobs\\ProcessPodcast") }
Forcing sync dispatch
By default, jobs would be dispatched through Illuminate\Contracts\Bus\Dispatcher::dispatch(), which inspects the job to determine whether the job should be queued or not. However, the async argument on the @bus directive would allow developers to force dispatching a job synchronously by using Illuminate\Contracts\Bus\Dispatcher::dispatchSync()instead.
type Mutation { processPodcast(podcast: ID!): Podcast @bus(dispatch: "App\\Jobs\\ProcessPodcast", sync: true) }
Manipulating jobs before or after dispatch
Laravel's command bus allows developers to define pipes to pass a job through upon dispatch. These pipes basically behave like middleware, meaning jobs can be manipulated before or after dispatch. This could be useful to allow for example formatting the directive's return value in order to match the schema definition:
type Mutation { processPodcast(podcast: ID!): Podcast @bus( dispatch: "App\\Jobs\\ProcessPodcast" pipeThrough: [ "App\\GraphQL\\Mutation\\WrapJobResult", ] ) }
Breaking changes
As this would introduce a new directive, no breaking changes should be made to implement this idea.
Implementation
I haven't developed this yet, but are willing to do so if there's any interest. This is merely a basic proposal, any suggestions to improve or extend the idea are welcome.
All reactions
Replies: 1 comment 1 reply
I generally support the idea and think it is a useful analogue to @event.
How exactly would the job class be instantiated? Is it going to be passed the field arguments as an array or perhaps converted to positional parameters?
What is the signature of a pipe? Is this documented in Laravel somewhere?
All reactions
I would have to try it out to be sure, but I think by default the job class could be instantiated with the resolver arguments as positional parameters. I can imagine the user (which lives in the resolver context) to be useful as well, but it can be injected in the arguments through @inject if I'm not mistaken.
As for command bus pipes, it seems like it was only in the docs until Laravel 5 (https://laravel.com/docs/5.0/bus#command-pipeline). Not sure why it's been removed, as the functionality is still there and works exactly as documented back then.