Laravel Venture

website·Indexed 30 days ago

https://laravel-venture.netlify.app/

A workflow management package for Laravel that allows developers to define, nest, and track workflows. It includes features for handling duplicate jobs, managing failures, customizing internal models and migrations, and a first-class set of test helpers for asserting workflow execution.

Tokens
6.6K
Snippets
36
Records
40
Agent score
74%

What's inside Venture

  1. Overview of Venture workflow testing

    Venture provides a set of first-class test helpers designed to allow developers to reason about workflows without the need to actually start them or dispatch the associated sequence of jobs. The two primary testing scenarios for workflows are: asserting that a workflow was started and testing the specific definition of a workflow.
  2. Understand how job failures affect workflow execution

    In a Venture workflow, if a job fails, all subsequent jobs in that specific subtree (those with a direct or transitive dependency on the failed job) are prevented from running. Jobs that do not depend on the failed job will continue to execute normally, ensuring the workflow is robust and not 'all-or-nothing'.
  3. Publish Venture database migrations for customization

    To alter the migrations provided by the Venture package, you must first publish them to your application's database/migrations folder. This allows you to modify the schema, though you should avoid changing default columns as it may cause the package to malfunction.
    php artisan vendor:publish --provider="Sassnowski\Venture\VentureServiceProvider" --tag=migrations
  4. Fake and assert workflow execution in tests

    Use the Workflow facade's fake() method to prevent workflows from actually executing during a test. Once faked, you can use assertStarted to verify a specific workflow class was triggered, or assertNotStarted to verify it was not. To perform more granular assertions on the workflow instance, pass a callback as the second argument to the assertion method; the test will fail if the callback returns false.
    <?php declare(strict_types=1);
    
    namespace Testseature;
    
    use Tests//TestCase;
    use App//Models//Podcast;
    use App//Workflows//AnotherWorkflow;
    use Sassnowski//Venture//Facades//Workflow;
    use App//Workflows//PublishPodcastWorkflow;
    
    class ExampleTest extends TestCase
    {
        /** @test */
        public function testPodcastPublishing(): void
        {
            // Prevent workflows from actually running
            Workflow::fake();
    
            // Action that triggers workflows
            // Publish podcast...
    
            // Assert that a specific workflow was started
            Workflow::assertStarted(PublishPodcastWorkflow::class);
    
            // Assert that a workflow was not started
            Workflow::assertNotStarted(AnotherWorkflow::class);
    
            // Assert with a custom callback for specific instance data
            Workflow::assertStarted(
                PublishPodcastWorkflow::class,
                function (PublishPodcastWorkflow $workflow) use ($podcast) {
                    return $workflow->podcast->is($podcast);
                }
            );
        }
    }
  5. Upgrade Venture to 3.0 from 2.x

    When migrating to Venture 3.0, note the following changes:

    • PHP Version: The minimum required PHP version is now 8.0.
    • addJobWithDelay Removal: The addJobWithDelay method in WorkflowDefinition has been removed. Use the addJob method with PHP 8 named parameters instead.
    • Nested Workflow Job IDs: Jobs from nested workflows (added via addWorkflow) are now internally prefixed with the ID of the parent workflow. This affects tests using hasJob() assertions.
    // Old way (Removed)
    Workflow::define('Workflow name')->addJobWithDelay(new Job(), now()->addMinute());
    
    // New way (Venture 3.0)
    Workflow::define('Workflow name')->addJob(new Job(), delay: now()->addMinute());
  6. Modify a workflow before it is saved using the beforeCreate hook

    To apply transformations to a workflow before it is saved to the database for the first time, implement the beforeCreate method within your workflow class (which should extend AbstractWorkflow).

    The beforeCreate method receives an instance of Sassnowski\Venture\Models\Workflow, which is a standard Eloquent model. Because this hook executes before the initial persistence, the workflow model will not yet have an id assigned.

    <?php declare(strict_types=1);
    
    use Sassnowski\Venture\Models\Workflow;
    use Sassnowski\Venture\AbstractWorkflow;
    use Sassnowski\Venture\WorkflowDefinition;
    
    class PublishPodcastWorkflow extends AbstractWorkflow
    {
        private Podcast $podcast;
    
        public function __construct(Podcast $podcast)
        {
            $this->podcast = $podcast;
        }
    
        public function definition(): WorkflowDefinition
        {
            // ...
        }
    
        public function beforeCreate(Workflow $workflow): void
        {
            // Perform some transformation to the workflow before it gets saved...
            $workflow->user_id = $this->podcast->user_id;
        }
    }
  7. Add jobs and define dependencies in a workflow

    Jobs are added to a workflow using the addJob method. To specify that a job depends on others, pass an array of the dependency job class names as the second argument. The job will only execute once all listed dependencies have finished. Venture automatically handles transitive dependencies (dependencies of dependencies).
    Workflow::define('Publish new podcast')
        ->addJob(new ProcessPodcast($podcast))
        ->addJob(new OptimizePodcast($podcast))
        ->addJob(new PublishPodcastOnTransistorFM($podcast), [
            ProcessPodcast::class,
            OptimizePodcast::class
        ]);
  8. Manage duplicate jobs in nested workflows

    Venture automatically prefixes job IDs within nested workflows to prevent naming conflicts with the parent workflow. This allows a nested workflow to contain a job class that also exists in the parent workflow without requiring explicit IDs for the nested instance.

    Caveat: You should always depend on the nested workflow itself rather than a specific job inside that workflow to maintain encapsulation and avoid altering the workflow structure.

    // Nested Workflow
    class FlacPodcastWorkflow extends AbstractWorkflow
    {
        public function definition(): WorkflowDefinition
        {
            return Workflow::define('Flac Workflow')
                ->addJob(new EncodePodcast('flac', $this->podcast))
                ->addJob(
                    new NotifyAudiophileMailingList($this->podcast),
                    dependencies: [EncodePodcast::class],
                );
        }
    }
    
    // Parent Workflow
    Workflow::define('Publish podcast')
        ->addJob(new ProcessPodcast($this->podcast))
        ->addJob(
            new EncodePodcast('mp3', $this->podcast),
            dependencies: [ProcessPodcast::class],
            id: 'encode-mp3'
        )
        ->addWorkflow(
            new FlacPodcastWorkflow($this->podcast),
            dependencies: [ProcessPodcast::class],
        );
  9. Define a workflow with job dependencies in Laravel Venture

    To create a workflow in Laravel Venture, extend the AbstractWorkflow class and implement the definition() method. Use Workflow::define() to name the workflow and addJob() to attach jobs. You can specify job dependencies by passing an array of class names as the second argument to addJob(); the job will only execute once all listed dependencies have successfully finished. Jobs without interdependencies are automatically parallelized.
    class PublishNewPodcastWorkflow extends AbstractWorkflow
    {
        private Podcast $podcast;
    
        public function __construct(Podcast $podcast)
        {
            $this->podcast = $podcast;
        }
    
        public function definition(): WorkflowDefinition
        {
            Workflow::define('Publish new podcast')
                ->addJob(new ProcessPodcast($this->podcast))
                ->addJob(new OptimizePodcast($this->podcast))
                ->addJob(new ReleaseOnTransistorFM($this->podcast), [
                    // This job runs only after ProcessPodcast and OptimizePodcast finish
                    ProcessPodcast::class,
                    OptimizePodcast::class
                ])
                ->addJob(new ReleaseOnApplePodcasts($this->podcast), [
                    ProcessPodcast::class,
                    OptimizePodcast::class
                ])
                ->addJob(new CreateAudioTranscription($this->podcast), [
                    ProcessPodcast::class,
                ])
                ->addJob(new TranslateAudioTranscription($this->podcast), [
                    CreateAudioTranscription::class,
                ])
                ->addJob(new NotifySubscribers($this->podcast), [
                    ReleaseOnTransistorFM::class,
                    ReleaseOnApplePodcasts::class,
                ])
                ->addJob(new SendTweetAboutNewPodcast($this->podcast), [
                    TranslateAudioTranscription::class,
                    ReleaseOnTransistorFM::class,
                    ReleaseOnApplePodcasts::class,
                ]);
        }
    }
  10. Add multiple instances of the same job to a workflow

    When adding multiple jobs of the same class to a workflow, you must provide a unique id for each instance using the id parameter in the addJob method. If no ID is provided, Venture defaults to using the Fully Qualified Class Name (FQCN) of the job class. If two jobs share the same ID within a single workflow definition, Venture will throw a DuplicateJobException.
    Workflow::define('Publish podcast')
        ->addJob(new ProcessPodcast($this->podcast))
        ->addJob(
            new EncodePodcast('mp3', $this->podcast),
            dependencies: [ProcessPodcast::class],
            id: 'encode-mp3'
        )
        ->addJob(
            new EncodePodcast('wav', $this->podcast),
            dependencies: [ProcessPodcast::class],
            id: 'encode-wav'
        );
  11. Delay the execution of a workflow job

    To delay a job, use the addJobWithDelay method. The job class must use the Illuminate\Bus\Queueable trait to support this functionality.
    $workflow = Workflow::new('Publish new podcast')
        ->addJobWithDelay(new ProcessPodcast($podcast), now()->addDays(2));
  12. Run Venture workflow jobs synchronously

    Venture requires all workflow jobs to implement the ShouldQueue interface because it relies on Laravel's queue system events. To run a job synchronously while maintaining this requirement, you must set the job's connection to sync. This requires the job to use the Illuminate\Bus\Queueable trait. You can achieve this either by setting $this->connection = 'sync' in the constructor or by calling onConnection('sync') when adding the job to the workflow definition.
    // Option 1: In the job constructor
    namespace App\Jobs;
    
    use Illuminate\Bus\Queueable;
    use Sassnowski\Venture\WorkflowStep;
    use Illuminate\Contracts\Queue\ShouldQueue;
    
    class MyJob implements ShouldQueue
    {
        use WorkflowStep, Queueable;
    
        public function __construct()
        {
            $this->connection = 'sync';
        }
    }
    
    // Option 2: When defining the workflow
    Workflow::define('My workflow')
        ->addJob((new MyJob())->onConnection('sync'));