Principio de responsabilidad única (SRP) con Laravel

El Principio SRP (Principio de una responsabilidad) es uno de los principios fundamentales para escribir código compatible. En este artículo, mostraré cómo aplicar este principio utilizando el ejemplo del lenguaje PHP y el marco Laravel.

A menudo, cuando se describe el modelo de desarrollo MVC, se asignan tareas excesivamente grandes al controlador. Obtención de parámetros, lógica empresarial, autorización y respuesta.

Por supuesto, en artículos y libros esto se describe como un ejemplo, pero a menudo se percibe como un llamado a la acción en proyectos de trabajo. Tal enfoque inevitablemente conducirá a un crecimiento de clase descontrolado y complicará en gran medida el soporte de código.

Principio de responsabilidad compartida


Por ejemplo, tomaremos un tipo de controlador "grueso" bastante tosco, pero a menudo conocido.

class OrderController extends Controller
{
    public function create(Request $request)
    {
        $rules = [
            'product_id' => 'required|max:10',
            'count' => 'required|max:10',
        ];
        $validator = Validator::make($request->all(), $rules);
        if ($validator->fails()) {
            return response()->json($validator->errors(), 400);
        }

         //  
        $order = Order::create($request->all());

        //   

        //  sms    

        return response()->json($order, 201);
    }
}

En este ejemplo, se puede ver que el controlador sabe demasiado sobre "hacer un pedido", también se le confía la tarea de notificar al comprador y reservar los bienes.

Nos esforzaremos por asegurarnos de que el controlador solo tenga que controlar todo el proceso y que no tenga lógica comercial.

Para comenzar, validaremos los parámetros en una clase de solicitud separada.

class OrderRequest extends Request
{
    public function rules(): array
    {
         return [
             'product_id' => 'required|max:10',
             'count' => 'required|max:10',
         ];    
    }
}

Y mueva toda la lógica empresarial a la clase OrderService

public class OrderService
{
    public function create(array $params)
    {
        //  

        //  

        //     sms
        $sms = new RuSms($appId);

        //  ,    
        $sms->send($number, $text);
     }
}

Usando el contenedor de servicios implementaremos nuestro servicio en el controlador.

Como resultado, tenemos un controlador "delgado".

class OrderController extends Controller
{
    protected $service;
	
    public function __construct(OrderService $service)
    {
	$this->service = $service;	
    }

    public function create(OrderRequest $request)
    {
        $this->service->create($request->all());

        return response()->noContent(201);
    }
}

Ya mejor. Toda la lógica empresarial se ha trasladado al servicio. El controlador solo conoce las clases OrderRequest y OrderService : el conjunto mínimo de información necesaria para hacer su trabajo.

Pero ahora nuestro servicio también necesita refactorización. Vamos a sacar la lógica de enviar sms a una clase separada.

class SmsRu
{
    protected $appId;

    public function __constructor(string $appId)
    {
        $this->appId = $appId;
    }

    public function send($number, $text): void
    {
          //     
    }
}    

E implementarlo a través del constructor

class OrderService
{
    private $sms;

    public function __construct()
    {
	$this->sms = new SmsRu($appId);	
    }

    public function create(array $params): void
    {
          //  

          //  

          //  ,    
          $this->sms->send($nubmer, $text);
    }
}    

Ya es mejor, pero la clase OrderService todavía sabe demasiado sobre el envío de mensajes. Es posible que necesitemos reemplazar el proveedor de mensajería en el futuro o agregar pruebas unitarias. Continuamos refactorizando agregando la interfaz SmsSender y especificamos SmsRu a través del proveedor SmsServiceProvider .

interface SmsSenderInterface
{
    public function send($number, $text): void;
}

class SmsServiceProvider implements ServiceProviderInterface
{
    public function register(): void
    {
        $this->app->singleton(SmsSenderInterface::class, function ($app) {
            return new SmsRu($params['app_id']);
        });
    }
}

Ahora el servicio también está libre de detalles innecesarios.

class OrderService
{
    private $sms;

    public function __construct(SmsSenderInterface $sms)
    {
	$this->sms = $sms;	
    }

    public function create(): void
    {
          //  

          //  

          //  ,    
          $this->sms->send($nubmer, $text);
    }
}    

Arquitectura dirigida por eventos


Enviar mensajes no es parte del proceso básico de creación de pedidos. El pedido se creará independientemente del mensaje enviado, también es posible que en el futuro se agregue la opción de cancelar las notificaciones por SMS. Para no sobrecargar el OrderService con detalles de notificación innecesarios, puede usar Laravel Observers . Una clase que rastreará eventos con un cierto comportamiento de nuestro modelo de Pedido y le asignará toda la lógica de notificación al cliente.

class OrderObserver
{
    private $sms;

    public function __construct(SmsSenderInterface $sms)
    {
	$this->sms = $sms;	
    }

    /**
     * Handle the Order "created" event.
     */
    public function created(Order $order)
    {
        $this->sms->send($nubmer, $text);
    }

No olvide registrar OrderObserver en AppServiceProvider .

Conclusión


Entiendo que estoy describiendo cosas bastante triviales en esta publicación, pero quería mostrar cómo se implementa este principio en el marco de Laravel.

El principio SPR es uno de los más importantes y allana el camino para el resto. Se requiere su uso, especialmente porque el marco Laravel proporciona todas las herramientas necesarias.

All Articles