Aprovechando los patrones DTO en APIs REST de Laravel: mejorando la estructura del código y su mantenibilidad

En el siempre cambiante panorama del desarrollo web, crear aplicaciones escalables y mantenibles es fundamental. Laravel, uno de los frameworks de PHP más populares, proporciona a los desarrolladores una base sólida para construir APIs RESTful. Sin embargo, a medida que las aplicaciones crecen, gestionar la transferencia de datos entre capas puede volverse complejo. Es aquí donde entra en juego el patrón de Objeto de Transferencia de Datos (DTO).

En este artículo, exploraremos por qué incorporar DTOs en tu API REST de Laravel puede mejorar significativamente la estructura del código, aumentar la mantenibilidad y simplificar el manejo de datos. También profundizaremos en cómo relacionar los DTOs con los modelos y demostraremos su uso utilizando el popular paquete Spatie\DataTransferObject.

05.09.2024 — Marc Bernet — 8 min read

¿Qué es un Objeto de Transferencia de Datos (DTO)?

Screenshot 2024 10 14 at 12 11 24

Un Objeto de Transferencia de Datos es un objeto simple que encapsula datos y los transporta entre procesos. Los DTO se utilizan para transferir datos entre subsistemas de aplicaciones de software. No deben contener ninguna lógica de negocio, solo campos para almacenar datos y métodos simples de getters/setters.

¿Por qué usar DTOs en APIs REST de Laravel?

1. Mejor Lectura y Mantenibilidad del Código

Los DTOs proporcionan un contrato claro para las estructuras de datos, lo que hace que el código sea más legible. Definen exactamente qué datos se esperan, reduciendo la ambigüedad y facilitando el mantenimiento.

2. Desacoplamiento entre Capas

Al usar DTOs, desacoplas las capas de tu aplicación (por ejemplo, Controladores, Servicios, Repositorios). Esta separación de responsabilidades permite un código más flexible, fácil de probar y modificar.

3. Validación Mejorada de Datos

Los DTOs pueden usarse para validar datos en el momento de su construcción, asegurando que solo datos válidos se muevan a través del sistema. Esto genera aplicaciones más robustas.

4. Beneficios de Seguridad

Los DTOs ayudan a prevenir ataques de sobreescritura de datos permitiendo especificar exactamente qué propiedades pueden establecerse, en lugar de aceptar toda la entrada del usuario directamente en tus modelos.

Integración de DTOs con Modelos en Laravel

En Laravel, los modelos representan los datos de tu base de datos. Cuando utilizas DTOs, creas una capa entre tus modelos y controladores. Así puedes relacionar los DTOs con los modelos:

  1. Mapeo de Datos: Mapea los datos de tu DTO a los atributos de tu modelo.
  2. Métodos de Conversión: Implementa métodos en tu DTO para convertir hacia y desde instancias de modelo.
  3. Patrón de Repositorio: Usa repositorios para manejar operaciones de datos, aceptando DTOs como parámetros.

Usando Spatie\DataTransferObject
 

Spatie\DataTransferObject es un paquete que proporciona una forma conveniente de crear DTOs en PHP.

Instalación

composer require spatie/data-transfer-object

 

Definir un DTO

use Spatie\DataTransferObject\DataTransferObject;

class UserData extends DataTransferObject
{
    public string $name;
    public string $email;
    public string $password;
}

 

Usar el DTO en un controlador

use App\DTOs\UserData;

public function store(Request $request)
{
    $userData = new UserData($request->all());

    // Pass the DTO to a service or repository
    $this->userService->createUser($userData);

    return response()->json(['message' => 'User created successfully.']);
}

 

Relacionar DTOs con modelos

use App\Models\User;

class UserService
{
    public function createUser(UserData $userData)
    {
        $user = new User();
        $user->name = $userData->name;
        $user->email = $userData->email;
        $user->password = bcrypt($userData->password);
        $user->save();

        return $user;
    }
}

Por qué usar modelos y DTOs no es redundante

Muchos desarrolladores pueden pensar que usar tanto modelos como DTOs es redundante, ya que ambos tratan con estructuras de datos. Sin embargo, cumplen propósitos distintos dentro de una aplicación:

  • Modelos: Representan la estructura de datos de tu base de datos y gestionan la persistencia de datos, las relaciones y la lógica de negocio relacionada con el almacenamiento de datos.
  • DTOs: Son objetos simples usados para transferir datos entre capas de tu aplicación o hacia clientes externos. Pueden incluir campos calculados o datos de servicios externos que no forman parte del esquema de tu base de datos.

Al separar estas responsabilidades, se logra:

  • Flexibilidad en la Representación de Datos: Los DTOs pueden incluir solo los datos necesarios para una operación específica, incluyendo campos calculados y datos de otros servicios.
  • Mantenibilidad Mejorada: Los cambios en el esquema de la base de datos no afectan directamente a los DTOs utilizados en las respuestas de la API o la lógica de negocio.
  • Mayor Seguridad y Control de Datos: Los DTOs te permiten especificar exactamente qué propiedades se exponen, evitando fugas de datos no intencionadas.

Ejemplo: Entidad de Logística con Campos Calculados

Escenario:

Supón que tienes un modelo de Envío que representa envíos en tu base de datos con campos como id, origen, destino, peso y fecha de creación. Al devolver datos a los clientes u otras partes de tu aplicación, necesitas incluir información adicional como:

  • Tiempo Estimado de Entrega: Un campo calculado basado en el origen, destino y la fecha actual.
  • Estado Actual del Envío: Información obtenida de un servicio de seguimiento externo.
  • Costo de Envío: Calculado en función del peso y la distancia.

Implementación Conjunta de Modelos y DTOs

1. Modelo de envío

class Shipment extends Model
{
    protected $fillable = ['origin', 'destination', 'weight', 'created_at'];
}

2. ShipmentData DTO

use Spatie\DataTransferObject\DataTransferObject;

class ShipmentData extends DataTransferObject
{
    public int $id;
    public string $origin;
    public string $destination;
    public float $weight;
    public string $estimated_delivery_time;
    public string $current_tracking_status;
    public float $shipping_cost;
}

3. Usar el DTO en un controlador

use App\Models\Shipment;
use App\DTOs\ShipmentData;
use App\Services\ShippingService;
use App\Services\TrackingService;

public function show($id)
{
    $shipment = Shipment::findOrFail($id);

    // Calculate the estimated delivery time
    $estimatedDeliveryTime = ShippingService::calculateEstimatedDeliveryTime(
        $shipment->origin,
        $shipment->destination
    );

    // Get the current tracking status from an external service
    $currentTrackingStatus = TrackingService::getCurrentStatus($shipment->id);

    // Calculate the shipping cost
    $shippingCost = ShippingService::calculateShippingCost(
        $shipment->weight,
        $shipment->origin,
        $shipment->destination
    );

    // Create the DTO
    $shipmentData = new ShipmentData([
        'id' => $shipment->id,
        'origin' => $shipment->origin,
        'destination' => $shipment->destination,
        'weight' => $shipment->weight,
        'estimated_delivery_time' => $estimatedDeliveryTime,
        'current_tracking_status' => $currentTrackingStatus,
        'shipping_cost' => $shippingCost,
    ]);

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

Explicación:

  • Modelo: Gestiona la recuperación de datos y las interacciones con la base de datos.
  • DTO: Combina datos del modelo, campos calculados y datos de servicios externos en un solo objeto para su transferencia.

Beneficios de Este Enfoque:

  • Separación de Responsabilidades: Los modelos y DTOs tienen roles distintos, lo que resulta en un código más limpio.
  • Mantenibilidad: Los cambios en la lógica de cálculo o en servicios externos no afectan directamente a los modelos de base de datos.
  • Control de Datos: Tienes control explícito sobre qué datos se exponen al cliente.
  • Reutilización de Código: Los DTOs pueden reutilizarse en diferentes partes de la aplicación o puntos de acceso de la API.

Ventajas de usar Spatie\DataTransferObject

  • Seguridad de Tipos: Asegura que los tipos de datos de las propiedades sean los esperados.
  • Objetos Inmutables: Los DTOs son inmutables por defecto, lo que previene efectos secundarios no intencionados.
  • Validación Automática: Valida los datos al momento de crear el objeto.

Mejores prácticas

  • Mantén los DTOs Simples: Solo deben contener propiedades y métodos simples como los getters.
  • Usa DTOs para Entrada y Salida: Considera usar DTOs separados para los datos de solicitud y los datos de respuesta.
  • Versiona Tus DTOs: Al crear APIs, versiona tus DTOs para gestionar cambios a lo largo del tiempo.

Conclusión

Incorporar patrones DTO en tu API REST de Laravel puede mejorar significativamente la estructura y mantenibilidad de tu aplicación. Al desacoplar tus capas y proporcionar contratos de datos claros, haces que tu base de código sea más robusta y fácil de trabajar. Herramientas como Spatie\DataTransferObject agilizan este proceso, ofreciendo beneficios adicionales como seguridad de tipos y validación automática.

Palabras Clave: Laravel, DTO, Data Transfer Object, REST API, Spatie\DataTransferObject, Estructura de Código, Mantenibilidad, Modelos, PHP, Desarrollo Web

Al implementar DTOs en tus proyectos de Laravel, no solo estás siguiendo una mejor práctica, sino que también estás preparando tu aplicación para el éxito futuro. Si encontraste útil este artículo, ¡considera compartirlo con tus compañeros desarrolladores!

⚡️ Consigue la subvención del Kit Digital para digitalizar tu negocio. Saber más.