0

I'am using Laravel 11.x as Framework and currently trying to figure out "the best" way to handle a big process from the amazon sp-api. I want to handle the API Requests within Jobs and want to chain them for a bigger process. E.g. the process of getting a shipping label for an Fullfilment package need like ~16 steps. (a horrible thing..)

I create Jobs for each request and want to chain them as needed. e.g. each POST Request must follow a request to the operationStatus Ressource which should anwser finally with SUCCESS or FAILED.

So the question is, can I capsulate smaller processes in functions and chain them finally for the big process? E.g. this are three functions which should be processed one after another:

public function createInboundPlan(Request $request){
    $uuid = Str::orderedUuid()->toString();
    Bus::chain([
        new CreateInboundPlan($uuid, $request),
        new GetInboundOperationStatus($uuid, Cache::pull($uuid.':'.'CreateInboundPlan'.':'.'operationId'), $request),
    ])->catch(function (Throwable $e) {
        // A job within the chain has failed...
    })->dispatch();
}

public function generatePackingOptions(string $inboundPlanId, Request $request){
    $uuid = Str::orderedUuid()->toString();
    Bus::chain([
        new GeneratePackingOptions($uuid, $inboundPlanId, $request),
        new GetInboundOperationStatus($uuid, Cache::pull($uuid.':'.'GeneratePackingOptions'.':'.'operationId'), $request),
    ])->catch(function (Throwable $e) {
        // A job within the chain has failed...
    })->dispatch();
}

public function listPackingOptions(string $inboundPlanId, Request $request){
    $uuid = Str::orderedUuid()->toString();
    Bus::chain([
        new ListPackingOptions($uuid, $inboundPlanId, $request),
        new SelectInboundPackingOption($uuid, Cache::pull($uuid.':'.'ListPackingOptions'.':'.'packingOptions'), $request),
    ])->catch(function (Throwable $e) {
        // A job within the chain has failed...
    })->dispatch();
}

Is there a way to archive something like this?

Bus::chain([
$this->createInboundPlan($request),
$this->generatePackingOptions($inboundPlanId, $request),
$this->generatelistPackingOptions($inboundPlanId, $request)
])->dispatch();

Currently the only Idea I have is to not work with functions and instead create "parent" jobs, which get chained. I mean I could just create a big chain for that big process and don't capsulate everything, but I may need single parts of this later again and I like the idea of that structure^^

Greetings

3 Answers 3

1

For example

<?php

require __DIR__.'/vendor/autoload.php';

use Illuminate\Contracts\Bus\Dispatcher;
use Illuminate\Foundation\Queue\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Pipeline\Pipeline;
use Illuminate\Support\Str;

class RequestOptions
{
    protected $planId;
    protected $uuid;

    // etc properties

    public function __construct(string $uuid)
    {
        $this->uuid = $uuid;
    }

    /**
     * @param mixed $planId
     */
    public function setPlanId($planId): void
    {
        $this->planId = $planId;
    }

}

class InboundPlanProcess implements ShouldQueue
{
    use Queueable;

    protected array $steps = [];

    public function __construct(array $steps)
    {
        // each step implement your interface
        $this->steps = $steps;
    }

    public function handle(string $uuid)
    {
        $options = new RequestOptions($uuid);

        return app(Pipeline::class)
            ->send($options)
            ->through($this->steps)
            ->thenReturn();
    }
}

interface StepInterface
{

}

class CreateInboundPlan implements StepInterface
{
    public function __invoke(RequestOptions $options, $next)
    {
        $options->setPlanId(123);

        return $next($options);
    }
}

class GeneratePackingOptions implements StepInterface
{
    public function __invoke(RequestOptions $options, $next)
    {
        // generate packing options

        return $next($options);
    }
}
$uuid = Str::orderedUuid()->toString();

$process = new InboundPlanProcess([
    new CreateInboundPlan(),
    new GeneratePackingOptions(),

// OR

//    function (RequestOptions $options, $next) {
//        $options->setPlanId(123);
//
//        return $next($options);
//    },
//    function (RequestOptions $options, $next) {
//        return $next($options);
//    },
]);


$result = dispatch($process);

Sign up to request clarification or add additional context in comments.

Comments

1

I suggest you use Pipeline to merge the processes.

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Pipeline;

class InboundPlanProcess
{
    protected array $steps = [];

    public function __construct(array $steps)
    {
        // each step implement your interface 
        $this->steps = $steps;
    }

    public function apply(mixed $passedDataHere): mixed
    {
        return app(Pipeline::class)
            ->send($passedDataHere)
            ->through($this->steps)
            ->thenReturn();
    }
}
    class CreateInboundPlan implements StepInterface {
      public function __invoke() {
        // 
      }
    } 
    class GeneratePackingOptions implements StepInterface {...}

then you create all the steps

class PlanController extends Controller
{
    
    public function process(Request $request): JsonResponse
    {
        $pipeline = new InboundPlanProcess([
            CreateInboundPlan::class,
            GeneratePackingOptions::class,
            generatelistPackingOptions::class,
            .......... more
        ]);

        $result = $pipeline->apply(Product::query());

        // do something
    }

If even one step goes wrong, the process will be aborted. For example, this is how middleware works in Laravel

1 Comment

I like your Idea, but how do I use this construction in combination with the queue? I need to run the task as a job in the background, would I edit the "PlanController" in your example to a job and dispatch that one? And how do the steps get new vars? e.g. "CreateInboundPlan" create a inboundPlanId, can I just return that in the invoke class and "GeneratePackingOptions" (next step) get that? Unfortunately nearly every step adds a var to the process for the next one.
0

Okay.. may I made this a bit too complicated.. After some further thinking about that structure I think it would be way more handy to dispatch directly depending tasks inside the initial Job.

E.g. the POST Requests which need to call the operationStatus Ressource should do that inside their jobs after finishing succesfull their Request. Furthermore that way should be better for requests which are followed by a decision making job (listPlacements => decide which placement should be used), because no cache handling are needed that way.

So I will build the jobs in a way that directly depending jobs(tasks) get dispatched inside. That way I capsulate already enough imo.

The big processes still will get builded as chains as otherwise I would duplicate to much code.

May that thinking process helps someone :D At least it helped me.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.