Ein bisschen mehr über die Service-Schicht in PHP

Im Leben eines jeden Entwicklers kommt eine Zeit, in der es an Verständnis für gängige Muster und Regeln für das Schreiben von sauberem Code mangelt. Dies geschieht normalerweise, wenn ein Projekt an den Stream gesendet wird, der komplexer als eine typische Katalogsite ist. Bei der Erstellung eines solchen Projekts ist es sehr wichtig, die richtige Architektur festzulegen (insbesondere wenn das Projekt langfristig ist), die sich so flexibel und schnell an neue Geschäftsanforderungen anpassen kann.





- ( 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: Und natürlich ist es nicht ganz richtig, zusätzliche Daten in Form einer ganzen Anfrage an die Service-Schicht zu übertragen. Es wäre viel besser, ein gültiges DTO weiterzuleiten. Sie müssen auch etwas Verständliches von den Diensten zurückgeben. Dieser Ansatz ist zumindest im Laravel-Ökosystem sinnvoll.





Zu diesem Zeitpunkt kam der Artikel zu seiner logischen Schlussfolgerung. Ich hoffe, es wird unerfahrenen Entwicklern und solchen, die mit der Service-Schicht nicht vertraut sind, helfen, die Essenz des Ansatzes und die damit verbundenen Probleme vollständig zu verstehen.





Vielen Dank für Ihre Aufmerksamkeit!








All Articles