侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

在Laravel中构建业务流程模型

Laravel  /  管理员 发布于 2年前   458

作为开发者,我们经常将业务流程映射为数字流程,从发送电子邮件到相当复杂的东西。

让我们来看看如何把一个更复杂的流程,写成干净而优雅的代码。

这一切都从工作流程开始。

我在推特上写了这个教程,看看是否会有任何关于业务流程的反馈,人们会觉得有帮助--不过我只得到了一个回应。

> 下一个教程决定了! 在Laravel中映射业务流程
 密切关注@laravelnews的报道
 如果你有一个你想看到的业务流程映射的例子,请留言! #php #phpc #laravel
 - JustSteveKing (@JustSteveKing) 3月22日, 2023

因此,考虑到这一点,让我们来看看订单/运输过程,这个过程有足够多的活动部件来表达这个想法

--但我不会从领域逻辑的角度去讨论太多细节。

想象一下,你经营一家在线商品商店,有一个在线商店,并使用滴滴出行服务,在下订单时按需发送商品。

我们需要考虑在没有任何数字帮助的情况下,业务流程可能是什么样的

--这使我们能够理解业务和它的需求。

要求提供商品(我们使用的是按需印刷服务,所以库存不是问题)。

我们获取客户的详细资料。
我们为这个新客户创建一个订单。
我们接受这个订单的付款。
我们向客户确认订单和付款。
然后我们向按需印刷服务下订单。

按需印刷服务将定期更新我们的订单状态,我们可以更新我们的客户,但这将是一个不同的业务流程。

让我们先看一下订单流程,想象一下这一切都在一个控制器中在线完成。

这将使管理或改变变得相当复杂。

class PlaceOrderController
{
    public function __invoke(PlaceOrderRequest $request): RedirectResponse
    {
        // Create our customer record.
        $customer = Customer::query()->create([]);
 
        // Create an order for our customer.
        $order = $customer->orders()->create([]);
 
        try {
            // Use a payment library to take payment.
            $payment = Stripe::charge($customer)->for($order);
        } catch (Throwable $exception) {
            // Handle the exception to let the customer know payment failed.
        }
 
        // Confirm the order and payment with the customer.
        Mail::to($customer->email)->send(new OrderProcessed($customer, $order, $payment));
 
        // Send the order to the Print-On-Demand service
        MerchStore::create($order)->for($customer);
 
        Session::put('status', 'Your order has been placed.');
 
        return redirect()->back();
    }
}

因此,如果我们浏览这段代码,我们会看到我们创建了一个用户和订单--然后接受付款并发送电子邮件。最后,我们在会话中添加一条状态信息,并重定向给客户。


因此,我们向数据库写了两次,与支付API对话,发送电子邮件,最后,写到会话并重定向。

一个同步线程要处理的事情很多,有很多可能会出现问题。

合理的步骤是将其转移到后台工作,这样我们就有了一定程度的容错能力。

class PlaceOrderController
{
    public function __invoke(PlaceOrderRequest $request): RedirectResponse
    {
        // Create our customer record.
        $customer = Customer::query()->create([]);
 
        dispatch(new PlaceOrder($customer, $request));
 
        Session::put('status', 'Your order is being processed.');
 
        return redirect()->back();
    }
}

我们已经对控制器进行了大量的清理--然而,我们所做的只是将问题转移到后台进程中。

虽然把这个问题转移到后台进程是正确的处理方式,但我们需要以不同的方式来处理这个问题。

首先,我们要首先或创建客户--以防他们之前下过订单。

class PlaceOrderController
{
    public function __invoke(PlaceOrderRequest $request): RedirectResponse
    {
        // Create our customer record.
        $customer = Customer::query()->firstOrCreate([], []);
 
        dispatch(new PlaceOrder($customer, $request));
 
        Session::put('status', 'Your order is being processed.');
 
        return redirect()->back();
    }
}

我们的下一步是将客户的创建转移到一个共享类中

--这是我们想要创建或获取客户记录的许多时候之一。

class PlaceOrderController
{
    public function __construct(
        private readonly FirstOrCreateCustomer $action,
    ) {}
 
    public function __invoke(PlaceOrderRequest $request): RedirectResponse
    {
        // Create our customer record.
        $customer = $this->action->handle([]);
 
        dispatch(new PlaceOrder($customer, $request));
 
        Session::put('status', 'Your order is being processed.');
 
        return redirect()->back();
    }
}

让我们来看看后台进程的代码,如果我们直接把它移到那里。

class PlaceOrder implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;
 
    public function _construct(
        public readonly Customer $customer,
        public readonly Request $request,
    ) {}
 
    public function handle(): void
    {
        // Create an order for our customer.
        $order = $this->customer->orders()->create([]);
 
        try {
            // Use a payment library to take payment.
            $payment = Stripe::charge($this->customer)->for($order);
        } catch (Throwable $exception) {
            // Handle the exception to let the customer know payment failed.
        }
 
        // Confirm the order and payment with the customer.
        Mail::to($this->customer->email)
            ->send(new OrderProcessed($this->customer, $order, $payment));
 
        // Send the order to the Print-On-Demand service
        MerchStore::create($order)->for($this->customer);
    }
}

不是太糟糕,但是--如果一个步骤失败了,我们重试工作怎么办?

我们最终会在不需要的时候一次又一次地重做这个过程的一部分。

我们应该首先考虑在数据库事务中创建订单。

class CreateOrderForCustomer
{
    public function handle(Customer $customer, data $payload): Model
    {
        return DB::transaction(
            callback: static fn () => $customer->orders()->create(
                attributes: $payload,
            ),
        );
    }
}

现在我们可以更新我们的后台进程来实现这个新命令。

class PlaceOrder implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;
 
    public function _construct(
        public readonly Customer $customer,
        public readonly Request $request,
    ) {}
 
    public function handle(CreateOrderForCustomer $command): void
    {
        // Create an order for our customer.
        $order = $command->handle(
            customer: $customer,
            payload: $this->request->only([]),
        );
 
        try {
            // Use a payment library to take payment.
            $payment = Stripe::charge($this->customer)->for($order);
        } catch (Throwable $exception) {
            // Handle the exception to let the customer know payment failed.
        }
 
        // Confirm the order and payment with the customer.
        Mail::to($this->customer->email)
            ->send(new OrderProcessed($this->customer, $order, $payment));
 
        // Send the order to the Print-On-Demand service
        MerchStore::create($order)->for($this->customer);
    }
}

这种方法效果不错。然而,这并不理想,你在任何时候都没有多少可见性。

我们可以用不同的方式来建模,这样我们就可以对我们的业务流程进行建模,而不是把它分割成几个部分。


这一切都从管道门面开始,使我们能够正确地建立这个流程。

我们仍然希望在控制器中创建我们的客户,但我们将在后台工作中使用业务流程处理其余的过程。


首先,我们需要一个抽象类,我们的业务流程类可以扩展,以减少代码的重复。

abstract class AbstractProcess
{
    public array $tasks;
 
    public function handle(object $payload): mixed
    {
        return Pipeline::send(
            passable: $payload,
        )->through(
            pipes: $this->tasks,
        )->thenReturn();
    }
}

我们的业务流程类将有许多相关的任务,我们在实现中声明这些任务。

然后,我们的抽象流程将接受传入的有效载荷,并通过这些任务来发送它--最终返回。

不幸的是,我想不出一个好的方法来返回一个实际的类型而不是混合的,但有时我们必须妥协......

class PlaceNewOrderForCustomer extends AbstractProcess
{
    public array $tasks = [
        CreateNewOrderRecord::class,
        ChargeCustomerForOrder::class,
        SendConfirmationEmail::class,
        SendOrderToStore::class,
    ];
}

正如你所看到的,这看起来超级干净,而且运作良好。

这些任务可以在其他有意义的业务流程中重复使用。

class PlaceOrder implements ShouldQueue
{
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;
 
    public function _construct(
        public readonly Customer $customer,
        public readonly Request $request,
    ) {}
 
    public function handle(PlaceNewOrderForCustomer $process): void
    {
        try {
            $process->handle(
                payload: new NewOrderForCustomer(
                    customer: $this->customer->getKey(),
                    orderPayload: $this->request->only([]),
                ),
            );
        } catch (Throwable $exception) {
            // Handle the potential exceptions that could occur.
        }
    }
}

我们的后台进程现在试图处理业务流程, 如果有任何异常发生, 我们可以失败并在以后重试。

因为Laravel会使用它的DI容器来传递你所需要的东西到jobs handle方法中, 

我们可以把我们的进程类传递到这个方法中,让Laravel为我们解决这个问题。

class CreateNewOrderRecord
{
    public function __invoke(object $payload, Closure $next): mixed
    {
        $payload->order = DB::transaction(
            callable: static fn () => Order::query()->create(
                attributes: [
                    $payload->orderPayload,
                    'customer_id' $payload->customer,
                ],
            ),
        );
 
        return $next($payload);
    }
}

我们的业务流程任务是可调用的类, 它被传递给 "旅行者", 也就是我们要传递的有效载荷, 和一个Closure, 也就是管道中的下一个任务。

这类似于Laravel中的中间件功能的工作方式, 我们可以根据自己的需要进行链式调用, 而他们只是按顺序调用.


我们传入的有效载荷可以是一个简单的PHP对象,我们可以用它来构建,

因为它通过管道,在每一步扩展它,允许管道中的下一个任务访问任何它需要的信息,而无需运行数据库查询。


使用这种方法,我们可以将我们的业务流程分解为非数字化的流程,并对其进行数字化表示。

以这种方式将它们串联起来,在我们需要的地方增加自动化。

这是一个相当简单的方法,真的,但它是非常强大的。


你有没有在Laravel中找到一个处理业务流程的好方法? 

你是怎么做的? 请在twitter上告诉我们!

转:

https://laravel-news.com/modelling-busines-processes-in-laravel

  • 上一条:
    windows系统中安装FFMpeg及在phpstudy环境php7.3 + php-ffmpeg扩展的使用流程步骤
    下一条:
    在Laravel项目中使用中间件方式统计用户在线时长功能代码示例
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • Laravel 11.14版本发布 - 新的字符串助手和ServeCommand改进(0个评论)
    • Laravel 11.12版本发布 - Artisan的`make`命令自动剪切`.php `扩展(0个评论)
    • Laravel的轻量型购物车扩展包:binafy/laravel-cart(0个评论)
    • Laravel 11.11版本发布 - 查看模型中的第三方关系:show(0个评论)
    • 近期文章
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-01
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-02
    • 2024-03
    • 2024-04
    • 2024-05
    • 2024-06
    • 2024-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客