artículos / Refactorizando backends Laravel legacy c...

Refactorizando backends Laravel legacy con Pipelines y Actions: de controladores gordos a flujos mantenibles

geosuna 14 minutos de lectura 32 vistas

Cómo usar Pipelines, Actions y Jobs en Laravel para transformar controladores monolíticos en flujos desacoplados, testeables y listos para escalar en producción.

Refactorizando backends Laravel legacy con Pipelines y Actions: de controladores gordos a flujos mantenibles

Refactorizando backends Laravel legacy con Pipelines y Actions: de controladores gordos a flujos mantenibles

Muchos proyectos Laravel en producción arrancaron como MVPs y terminaron con controladores enormes que mezclan validación, dominio, integración con terceros y side effects, volviendo doloroso cualquier cambio nuevo.

El problema de los controladores gordos en Laravel

Un patrón clásico en proyectos legacy es encontrar métodos de controlador de 300+ líneas que hacen desde validar el request hasta disparar notificaciones, escribir logs y hablar con servicios externos, todo en un único flujo acoplado.

Este enfoque rompe el principio de responsabilidad única, dificulta el testing aislado y complica tareas de observabilidad y feature flags en producción.

1. Presentando Pipelines y Actions como capa de orquestación

Qué es un Pipeline en Laravel

El componente Illuminate\Pipeline\Pipeline permite modelar un flujo de pasos donde cada etapa recibe un payload, lo transforma y lo pasa a la siguiente, ideal para orquestar casos de uso complejos sin inflar el controlador.

// app/Support/Pipelines/CreateOrderPipeline.php

use Illuminate\Pipeline\Pipeline;

class CreateOrderPipeline
{
    public function __construct(private Pipeline $pipeline) {}

    public function handle(array $payload): array
    {
        return $this->pipeline
            ->send($payload)
            ->through([
                \App\Actions\Orders\ValidateOrderData::class,
                \App\Actions\Orders\CalculateTotals::class,
                \App\Actions\Orders\PersistOrder::class,
                \App\Actions\Orders\DispatchPostCreateJobs::class,
            ])
            ->thenReturn();
    }
}

Cada Action encapsula una responsabilidad concreta y es fácilmente testeable, lo que reduce el riesgo de regresiones en entornos con alto tráfico.

2. Extrayendo lógica de un controlador legacy

Antes: controlador monolítico

// app/Http/Controllers/OrderController.php

public function store(Request $request)
{
    $data = $request->validate([
        'items' => 'required|array|min:1',
        'customer_id' => 'required|exists:customers,id',
    ]);

    // cientos de líneas con lógica de negocio, cálculos, integraciones...
}

Después: controlador delgado + Pipeline

public function store(Request $request, CreateOrderPipeline $pipeline)
{
    $payload = $request->all();

    $result = $pipeline->handle($payload);

    return response()->json([
        'order_id' => $result['order']->id,
        'status' => 'created',
    ], 201);
}

El controlador apenas coordina el input/output HTTP, mientras que la lógica de negocio vive en Actions autocontenidas reutilizables incluso desde Jobs o comandos Artisan.

3. Actions autocontenidas y testeables

Ejemplo de Action de dominio

// app/Actions/Orders/CalculateTotals.php

namespace App\Actions\Orders;

class CalculateTotals
{
    public function __invoke(array $payload): array
    {
        $subtotal = collect($payload['items'])
            ->sum(fn ($item) => $item['price'] * $item['qty']);

        $payload['subtotal'] = $subtotal;
        $payload['total'] = $subtotal; // aplicar impuestos, descuentos, etc.

        return $payload;
    }
}

Este diseño permite tests unitarios de cada etapa sin bootstrapping completo de Laravel, acelerando CI y mejorando la calidad del dominio.

4. Integración con Jobs, colas y observabilidad

Separando side effects en Jobs

Las Actions de orquestación pueden delegar side effects pesados a Jobs encolados, como integración con gateways de pago o notificaciones.

// app/Actions/Orders/DispatchPostCreateJobs.php

namespace App\Actions\Orders;

use App\Jobs\SendOrderConfirmation;

class DispatchPostCreateJobs
{
    public function __invoke(array $payload): array
    {
        dispatch(new SendOrderConfirmation($payload['order']));

        return $payload;
    }
}

Esto facilita aplicar retries, timeouts y circuit breakers a nivel de infraestructura (Horizon, Redis, supervisores) sin acoplar esa lógica al controlador.

5. Beneficios en producción y DevOps

  • Controladores livianos que reducen conflictos en merges y facilitan code review.
  • Flujos de negocio expresados como Pipelines fácilmente observables en logs y traces.
  • Tests más rápidos y fiables, cruciales para pipelines de CI/CD frecuentes.
  • Capacidad de activar/desactivar etapas con feature flags sin tocar el endpoint HTTP.

6. Extensión hacia arquitectura limpia

La combinación de Controllers delgados + Pipelines + Actions es un primer paso hacia arquitecturas más limpias (hexagonal, DDD light) sin reescribir todo el proyecto de cero.

Sobre esta base, se pueden introducir puertos/adaptadores, repositorios explícitos y políticas de validación consistentes en todo el backend Laravel.

Conclusión

Refactorizar un backend Laravel legacy no requiere un big bang; requiere introducir patrones como Pipelines y Actions que permitan dividir el problema en pasos manejables.

Adoptar estos patrones mejora la mantenibilidad, reduce el riesgo en producción y sienta las bases para evolucionar el proyecto hacia una arquitectura moderna preparada para escalar.

compartir_artículo

LinkedIn Facebook X

artículos_relacionados