Sedikit lebih banyak tentang lapisan layanan di PHP

Dalam kehidupan setiap pengembang, ada saatnya ketika satu pemahaman tentang pola dan aturan populer untuk menulis kode bersih mulai kurang. Ini biasanya terjadi ketika proyek dikirimkan ke aliran yang lebih kompleks daripada situs katalog biasa. Saat membuat proyek seperti itu, sangat penting untuk meletakkan arsitektur yang benar (terutama jika proyek tersebut berjangka panjang), yang akan dapat beradaptasi secara fleksibel dan cepat dengan persyaratan bisnis baru.





- ( service layer), ,   . MVC Laravel.





, , , .   , , -, , , .





, Service layer - . , .





, :





(Service layer) — , .





, . ,   ( ) -, . , S SOLID.





  , Eloquent , .. , , . , -, , . , .





Email

, - - , . .





namespace App\Http\Controllers;

use App\Http\Requests\CreateOrderRequest;
use Illuminate\Support\Facades\Mail;

class OrderController
{
    public function createOrder(CreateOrderRequest $request)
    {
        //   ...

        Mail::send('mail.order_created', [
            'order' => $order
        ], function ($message) use ($order) {
            $message->to($order->email)
                ->subject(trans('mail/order_created.mail_title'));
        });
    }
}
      
      



, . Laravel . , , .





public function editOrder(EditOrderRequest $request)
{
    //    ...

    Mail::send('mail.order_updated', [
        'order' => $order
    ], function ($message) use ($order) {
        $message->to($order->email)
            ->subject(trans('mail/order_updated.mail_title'));
    });
}
      
      



, , .





public function registerCustomer(RegisterCustomerRequest $request)
{
    //   ...

    Mail::send('mail.customer_register', [
        'customer' => $customer
    ], function ($message) use ($customer) {
        $message->to($customer->email)
            ->subject(trans('mail/customer_register.mail_title'));
    });
}
      
      



, Mail , , - .





, email . , , , .. - email , . .





, email , , - . , Mail . ( )? , . , , . , .





, NotificationService.





namespace App\Services;

use Illuminate\Support\Facades\Mail;
use App\Mail\Events\MailEventInterface;
use App\Mail\Events\OrderCreatedEvent;
use App\Mail\Events\OrderUpdatedEvent;
use App\Mail\Events\CustomerRegisterEvent;

class NotificationService
{
    public function notify(string $event, array $data)
    {
        $event = $this->makeNotificationEvent($event, $data);

        Mail::send($event->getView(), $event->getData(), function ($message) use ($event) {
            $message->to($event->getEmail())
                ->subject($event->getMailSubject());
        });
    }

    private function makeNotificationEvent(string $event, array $data) : MailEventInterface
    {
        switch ($event) {
            case 'order_created':
                return new OrderCreatedEvent($data);
            case 'order_updated':
                return new OrderUpdatedEvent($data);
            case 'customer_register':
                return new CustomerRegisterEvent($data);
            default:
                throw new \InvalidArgumentException("Undefined event $event");
        }
    }
}
      
      



,  MailEventInterface.





namespace App\Mail\Events;

interface MailEventInterface
{
    public function getView() : string;
    public function getData() : array;
    public function getEmail() : string;
    public function getMailSubject() : string;
}
      
      



, ,  OrderCreatedEvent ( ).





namespace App\Mail\Events;

class OrderCreatedEvent implements MailEventInterface
{
    private $order;

    public function __construct(array $data)
    {
        //   ( )

        $this->order = $data['order'];
    }

    public function getView(): string
    {
        return 'mail.order_created';
    }

    public function getData(): array
    {
        return [
            'order' => $this->order
        ];
    }

    public function getEmail(): string
    {
        return $this->order->email;
    }

    public function getMailSubject(): string
    {
        return trans('mail/order_created.mail_title');
    }
}
      
      



, .





namespace App\Http\Controllers;

use App\Http\Requests\CreateOrderRequest;
use App\Services\NotificationService;

class OrderController
{
    private $notificationService;
    
    public function __construct(NotificationService $notificationService)
    {
        $this->notificationService = $notificationService;
    }

    public function createOrder(CreateOrderRequest $request)
    {
        //   ...
        
        $this->notificationService->notify('order_created', [
            'order' => $order
        ]);
    }
}
      
      



? , . , , . ( -), , . , , " " . .





?

. . . , .   ?  , NotificationServiceInterface , -. - .





$this->app->when(OrderController::class)
    ->needs(NotificationServiceInterface::class)
    ->give(function () {
        return new ESputnikNotificationService();
    });

$this->app->when(OrderUpdateController::class)
    ->needs(NotificationServiceInterface::class)
    ->give(function () {
        return new MailNotificationService();
    });
      
      



, 95% , - .





?

, single responsibility , , , .





.





1. . , , try/catch.





class OrderController
{
    public function saveOrder(
        SaveOrderRequest $request, 
        OrderService $orderService, 
        NotificationService $notificationService
    ) {
        try {
            $order = $orderService->createOrderFromRequest($request);
            $notificationService->notify('order_created', [
                'order' => $order
            ]);

            return response()->json([
                'success' => true,
                'data' => [
                    'order' => $order
                ]
            ]);
        }
        catch (OrderServiceException|NotificationServiceException $e) {
            return response()->json([
                'success' => false,
                'exception' => $e->getMessage()
            ]);
        }
    }
}
      
      



2. , . , Operation (CreateOrderOperation). try/catch, OperationResult, . .





class OrderController
{
    public function saveOrder(
        SaveOrderRequest $request,
        CreateOrderOperation $createOrderOperation
    ) {
        //         ..
        $result = $createOrderOperation->createOrderFromRequest($request);

        //    ,  OperationResult
        //   JsonSerializable

        return response()->json($result);
    }
}
      
      



UPD: , , . , , ..Service.





UPD: Dan tentu saja, tidak sepenuhnya benar untuk mentransfer data tambahan ke lapisan layanan dalam bentuk seluruh permintaan. Akan jauh lebih baik untuk meneruskan DTO yang valid. Anda juga perlu mengembalikan sesuatu yang dapat dimengerti dari layanan. Pendekatan ini masuk akal setidaknya di ekosistem Laravel.





Pada titik ini, artikel tersebut sampai pada kesimpulan logisnya. Saya berharap ini akan membantu pengembang pemula dan mereka yang tidak terbiasa dengan lapisan layanan untuk sepenuhnya memahami esensi dari pendekatan dan masalah yang dipecahkannya.





Terima kasih atas perhatiannya!








All Articles