Friday, April 19, 2024
HomePHPModelling Busines Processes in Laravel

Modelling Busines Processes in Laravel


As builders, we regularly map enterprise processes to digital processes, from sending an electronic mail to one thing fairly complicated. Let us take a look at the right way to take a extra difficult course of and write clear and chic code.

All of it begins with a workflow. I tweeted about scripting this tutorial to see if there can be any suggestions on enterprise processes folks would discover useful – I solely actually obtained one response, although.

So with that in thoughts, let us take a look at the Order/Transport course of, one thing with sufficient transferring elements to get the thought throughout – however I will not go into an excessive amount of element from a site logic perspective.

Think about you run a web-based merch retailer, have a web-based store, and use a dropshipping service to ship merch out on demand when an order is positioned. We want to consider what the enterprise course of may appear like with none digital assist – this permits us to know the enterprise and its wants.

An merchandise is requested (we’re utilizing a print-on-demand service, so inventory is not a difficulty).
We take the purchasers’ particulars.
We create an order for this new buyer.
We settle for cost for this order.
We verify the order and cost to the shopper.
We then place our order with the print-on-demand service.

The print-on-demand service will periodically replace us on the order standing, which we are able to replace our clients, however this is able to be a distinct enterprise course of. Let us take a look at the order course of first and picture this was all finished inline in a single controller. It could get fairly difficult to handle or change.

class PlaceOrderController

{

public operate __invoke(PlaceOrderRequest $request): RedirectResponse

{

// Create our buyer document.

$buyer = Buyer::question()->create([]);

 

// Create an order for our buyer.

$order = $buyer->orders()->create([]);

 

attempt {

// Use a cost library to take cost.

$cost = Stripe::cost($buyer)->for($order);

} catch (Throwable $exception) {

// Deal with the exception to let the shopper know cost failed.

}

 

// Affirm the order and cost with the shopper.

Mail::to($buyer->electronic mail)->ship(new OrderProcessed($buyer, $order, $cost));

 

// Ship the order to the Print-On-Demand service

MerchStore::create($order)->for($buyer);

 

Session::put('standing', 'Your order has been positioned.');

 

return redirect()->again();

}

}

So if we stroll by this code, we see that we create a consumer and order – then settle for the cost and ship an electronic mail. Lastly, we add a standing message to the session and redirect the shopper.

So we write to the database twice, speak to the cost API, ship an electronic mail, and eventually, write to the session and redirect. It’s quite a bit in a single synchronous thread to deal with, with a whole lot of potential for issues to interrupt. The logical step right here is to maneuver this to a background job in order that we’ve a stage of fault tolerance.

class PlaceOrderController

{

public operate __invoke(PlaceOrderRequest $request): RedirectResponse

{

// Create our buyer document.

$buyer = Buyer::question()->create([]);

 

dispatch(new PlaceOrder($buyer, $request));

 

Session::put('standing', 'Your order is being processed.');

 

return redirect()->again();

}

}

We’ve got cleaned up our controller so much – nevertheless, all we’ve finished is transfer the issue to a background course of. Whereas transferring this to a background course of is the fitting strategy to deal with this, we have to strategy this so much otherwise.

Firstly, we need to first or create the shopper – in case they’ve made an order earlier than.

class PlaceOrderController

{

public operate __invoke(PlaceOrderRequest $request): RedirectResponse

{

// Create our buyer document.

$buyer = Buyer::question()->firstOrCreate([], []);

 

dispatch(new PlaceOrder($buyer, $request));

 

Session::put('standing', 'Your order is being processed.');

 

return redirect()->again();

}

}

Our subsequent step is to maneuver the creation of a buyer to a shared class – that is considered one of many instances we might need to create or get a buyer document.

class PlaceOrderController

{

public operate __construct(

non-public readonly FirstOrCreateCustomer $motion,

) {}

 

public operate __invoke(PlaceOrderRequest $request): RedirectResponse

{

// Create our buyer document.

$buyer = $this->motion->deal with([]);

 

dispatch(new PlaceOrder($buyer, $request));

 

Session::put('standing', 'Your order is being processed.');

 

return redirect()->again();

}

}

Let us take a look at the background course of code if we moved it instantly there.

class PlaceOrder implements ShouldQueue

{

use Dispatchable;

use InteractsWithQueue;

use Queueable;

use SerializesModels;

 

public operate _construct(

public readonly Buyer $buyer,

public readonly Request $request,

) {}

 

public operate deal with(): void

{

// Create an order for our buyer.

$order = $this->buyer->orders()->create([]);

 

attempt {

// Use a cost library to take cost.

$cost = Stripe::cost($this->buyer)->for($order);

} catch (Throwable $exception) {

// Deal with the exception to let the shopper know cost failed.

}

 

// Affirm the order and cost with the shopper.

Mail::to($this->buyer->electronic mail)

->ship(new OrderProcessed($this->buyer, $order, $cost));

 

// Ship the order to the Print-On-Demand service

MerchStore::create($order)->for($this->buyer);

}

}

Not too dangerous, however – what if a step fails and we retry the job? We’ll find yourself redoing elements of this course of time and again when not wanted. We must always first look to create the order inside a database transaction.

class CreateOrderForCustomer

{

public operate deal with(Buyer $buyer, information $payload): Mannequin

{

return DB::transaction(

callback: static fn () => $buyer->orders()->create(

attributes: $payload,

),

);

}

}

Now we are able to replace our background course of to implement this new command.

class PlaceOrder implements ShouldQueue

{

use Dispatchable;

use InteractsWithQueue;

use Queueable;

use SerializesModels;

 

public operate _construct(

public readonly Buyer $buyer,

public readonly Request $request,

) {}

 

public operate deal with(CreateOrderForCustomer $command): void

{

// Create an order for our buyer.

$order = $command->deal with(

buyer: $buyer,

payload: $this->request->solely([]),

);

 

attempt {

// Use a cost library to take cost.

$cost = Stripe::cost($this->buyer)->for($order);

} catch (Throwable $exception) {

// Deal with the exception to let the shopper know cost failed.

}

 

// Affirm the order and cost with the shopper.

Mail::to($this->buyer->electronic mail)

->ship(new OrderProcessed($this->buyer, $order, $cost));

 

// Ship the order to the Print-On-Demand service

MerchStore::create($order)->for($this->buyer);

}

}

This strategy works nicely. Nonetheless, it is not best, and also you do not need a lot visibility at any level. We might mannequin this otherwise in order that we’re modeling our enterprise course of as an alternative of splitting it out into elements.

All of it begins with the Pipeline facade, enabling us to construct this course of accurately. We’ll nonetheless need to create our buyer within the controller, however we are going to deal with the remainder of the method inside the background job utilizing a enterprise course of.

To start with, we are going to want an summary class that our enterprise course of courses can prolong to attenuate code duplication.

summary class AbstractProcess

{

public array $duties;

 

public operate deal with(object $payload): blended

{

return Pipeline::ship(

satisfactory: $payload,

)->by(

pipes: $this->duties,

)->thenReturn();

}

}

Our enterprise course of class may have many related duties, which we declare within the implementation. Then our summary course of will take the passed-on payload and ship it by these duties – ultimately returning. Sadly, I am unable to consider a pleasant strategy to return an precise sort as an alternative of blended, however generally we’ve to compromise…

class PlaceNewOrderForCustomer extends AbstractProcess

{

public array $duties = [

CreateNewOrderRecord::class,

ChargeCustomerForOrder::class,

SendConfirmationEmail::class,

SendOrderToStore::class,

];

}

As you’ll be able to see, that is tremendous clear to take a look at and works nicely. These duties may be reused in different enterprise processes the place it is smart.

class PlaceOrder implements ShouldQueue

{

use Dispatchable;

use InteractsWithQueue;

use Queueable;

use SerializesModels;

 

public operate _construct(

public readonly Buyer $buyer,

public readonly Request $request,

) {}

 

public operate deal with(PlaceNewOrderForCustomer $course of): void

{

attempt {

$course of->deal with(

payload: new NewOrderForCustomer(

buyer: $this->buyer->getKey(),

orderPayload: $this->request->solely([]),

),

);

} catch (Throwable $exception) {

// Deal with the potential exceptions that might happen.

}

}

}

Our background course of now tries to deal with the enterprise course of, and if any exceptions occur, we are able to fail and retry the method in a while. As Laravel will use its DI container to cross by what you want into the roles deal with methodology, we are able to cross our course of class into this methodology and let Laravel resolve this for us.

class CreateNewOrderRecord

{

public operate __invoke(object $payload, Closure $subsequent): blended

{

$payload->order = DB::transaction(

callable: static fn () => Order::question()->create(

attributes: [

$payload->orderPayload,

'customer_id' $payload->customer,

],

),

);

 

return $subsequent($payload);

}

}

Our enterprise course of duties are invokable courses that get handed the “traveller”, which is the payload we need to cross by, and a Closure which is the following activity within the pipeline. That is just like how the middleware performance works in Laravel, the place we are able to chain on as many as we’d like, and they’re simply sequentially known as.

The payload we cross in is usually a easy PHP object we are able to use to construct because it goes by a pipeline, extending it at every step, permitting the following activity within the pipeline to entry any info it wants with out working a database question.

Utilizing this strategy, we are able to break down our enterprise processes that are not digital and make digital representations of them. Chaining them collectively on this method provides automation the place we’d like it. It’s fairly a easy strategy, actually, however it is vitally highly effective.

Have you ever discovered a pleasant strategy to deal with enterprise processes in Laravel? What did you do? Tell us on twitter!



RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments