Skip to main content

9. Transferencia de datos

Descripción General

El sistema implementa un patrón de Objetos de Transferencia de Datos (DTOs) para gestionar el flujo de información entre las diferentes capas de la aplicación. Los DTOs encapsulan los datos que se intercambian entre la capa de presentación (API) y la capa de dominio, proporcionando una clara separación de responsabilidades, validación contextual y mapeo de datos coherente.

Arquitectura

La arquitectura de transferencia de datos se basa en dos tipos principales de DTOs:

  1. DTOs de entrada (ReqDTO): Reciben y validan los datos de las solicitudes entrantes
  2. DTOs de salida (ResDTO): Formatean los datos para las respuestas API

Ambos tipos implementan interfaces que garantizan una estructura coherente y el cumplimiento del contrato establecido:

InterfaceReqDTO (contrato) <- ReqDTO (abstracta) <- Implementaciones específicas
InterfaceResDTO (contrato) <- ResDTO (abstracta) <- Implementaciones específicas

9.1. DTOs de petición (ReqDTO)

Contrato e implementación base

Los DTOs de petición implementan la interfaz InterfaceReqDTO y extienden la clase abstracta ReqDTO:

namespace App\Dtos\Contracts;

interface InterfaceReqDto
{
/**
* Procesa los datos y devuelve el DTO
*
* @param array $data
* @return InterfaceReqDto
*/
public function handle(array $data): InterfaceReqDto;

/**
* Convierte el DTO a un array
*
* @return array
*/
public function toArray(): array;

/**
* Mapea los filtros del request
*
* @param Request $request
* @return void
*/
public function mapFilters($request): void;
}

La clase base ReqDTO implementa la funcionalidad común para todos los DTOs de petición:

namespace App\Dtos;

abstract class ReqDto implements InterfaceReqDto
{
/**
* Mapeo de claves para filtros
*
* @var array
*/
protected $mapFilters = [];

abstract public function handle(array $data): InterfaceReqDto;
abstract public function toArray(): array;

public function mapFilters($request): void
{
// Implementación para mapear filtros de solicitudes
// [...]
}

// Métodos adicionales para procesamiento de arrays
// [...]
}

Ejemplo de implementación

Un ejemplo típico de implementación de un DTO de petición:

namespace ApiLayer\Domains\Products\DTOs;

use App\Dtos\Contracts\InterfaceReqDto;
use App\Dtos\ReqDto;

class CreateMaesartiProductosDTO extends ReqDto implements InterfaceReqDto
{
protected $nombre;

protected $mapFilters = [
'name' => 'nombre',
];

/**
* @param array $data
* @return InterfaceReqDto
*/
public function handle(array $data): InterfaceReqDto
{
$this->nombre = $data['name'] ?? null;
return $this;
}

public function toArray(): array
{
return [
'nombre' => (string) $this->nombre,
];
}
}

Mapeo de filtros

Una característica importante de los ReqDTO es su capacidad para mapear filtros de solicitudes HTTP:

public function mapFilters($request): void
{
$filterFields = ['filters', 'permanentFilters'];

foreach ($filterFields as $field) {
if (isset($request->{$field})) {
$data = json_decode($request->{$field}, true);
$mappedData = $this->processArray($data);
$mappedJson = json_encode($mappedData);

// Actualiza el valor en el request
if (method_exists($request, 'query') && method_exists($request->query, 'set')) {
$request->query->set($field, $mappedJson);
} else if (is_object($request)) {
$request->{$field} = $mappedJson;
}
}
}
}

Este método transforma filtros de API en filtros compatibles con la capa de modelo, permitiendo operaciones como:

API: { "name": { "=": "Product 1" } }

BD: { "nombre": { "=": "Product 1" } }

9.2. DTOs de respuesta (ResDTO)

Contrato e implementación base

Los DTOs de respuesta implementan la interfaz InterfaceResDTO y extienden la clase abstracta ResDTO:

namespace App\Dtos\Contracts;

interface InterfaceResDto
{
public function handle(array $data): InterfaceResDto;
public function toArray(): array;
}

La clase base ResDTO implementa funcionalidad común, incluyendo útiles métodos como conversión de fechas:

namespace App\Dtos;

abstract class ResDto implements InterfaceResDto
{
abstract public function handle(array $data): InterfaceResDto;
abstract public function toArray(): array;

/**
* Convierte strings de fecha y hora en un objeto DateTime
*/
public function toDateObject($fecha, $hora = null): DateTime
{
// Implementación para conversión de fechas
// [...]
}
}

Ejemplo de implementación

Un ejemplo típico de implementación de un DTO de respuesta:

namespace ApiLayer\Domains\Products\DTOs;

use App\Dtos\ResDto;
use App\Dtos\Contracts\InterfaceResDto;

class MaesartiProductosDTO extends ResDto
{
public static $selectables = [
'nombre',
'cod_articulo',
'cod_familia_1',
'cod_familia_2',
'cod_familia_3',
'cod_articulo_par',
];

protected $id;
protected $nombre;
protected $cod_articulo;
protected $cod_familia_1;
protected $cod_familia_2;
protected $cod_familia_3;
protected $cod_articulo_par;

public function handle(array $data): InterfaceResDto
{
$this->id = $data['id'];
$this->nombre = $data['nombre'];
$this->cod_articulo = $data['cod_articulo'];
$this->cod_familia_1 = $data['cod_familia_1'];
$this->cod_familia_2 = $data['cod_familia_2'];
$this->cod_familia_3 = $data['cod_familia_3'];
$this->cod_articulo_par = $data['cod_articulo_par'];
return $this;
}

public function toArray(): array
{
$provider1 = [
'provider_id' => 234,
'provider_name' => 'Juan',
'provider_code' => 'aab',
'min_order_quantity' => 10,
];
$provider2 = [
'provider_id' => 235,
'provider_name' => 'Pedro',
'provider_code' => 'xxw',
'min_order_quantity' => 10,
];
$providers = [$provider1, $provider2];

return [
'id' => (string) $this->id,
'name' => (string) $this->nombre,
'providers' => (array) $providers,
'product_code' => (string) $this->cod_articulo,
];
}
}

Propiedad $selectables

Una característica importante de los ResDTO es la propiedad estática $selectables que define los campos a seleccionar de la base de datos:

public static $selectables = [
'nombre',
'cod_articulo',
// ...
];

Estos campos son utilizados por el Repositorio para construir consultas optimizadas que solo recuperan los datos necesarios:

$this->MaesartiProductosRepository->selectable(MaesartiProductosDTO::$selectables);

9.3. Mapeo de datos entre capas

Mapper

El sistema incluye la clase App\Base\DB\Mapper que facilita el mapeo entre las estructuras de datos de la API y la base de datos:

namespace App\Base\DB;

class Mapper
{
private $map;
private $links;

public function __construct($map, $links)
{
$this->map = $map;
$this->links = $links;
}

public function map($objectToTransform, $map = null)
{
// Implementación para transformar datos de BD a formato API
// [...]
}

public function unMap($objectToTransform, $map = null)
{
// Implementación para transformar datos de API a formato BD
// [...]
}
}

Esta clase es utilizada por los Modelos y Repositorios para transformar los datos entre los formatos de la base de datos y la API.

Mapeo en servicios

Los servicios utilizan el método mapToDTO para convertir colecciones de datos utilizando el patrón DTO:

protected function mapToDTO(array $collection, $dto)
{
return array_map(function ($item) use ($dto) {
return $dto->handle($item)->toArray();
}, $collection);
}

Ejemplo de uso en un servicio:

public function index(Request $request)
{
$resources = $this->MaesartiProductosRepository->collection($request->all());
$resources['collection'] = $this->mapToDTO($resources['collection'], $this->MaesartiProductosDTO);
return $resources;
}

9.4. Filtrado y transformación

Transformación de tipos

Los DTOs realizan transformaciones de tipos para garantizar la consistencia de los datos:

public function toArray(): array
{
return [
'id' => (string) $this->id,
'name' => (string) $this->nombre,
'price' => (float) $this->posarttic_prec_mone,
'quantity' => (int) $this->posprelin_cant,
// ...
];
}

Formateado de fechas

La clase base ResDTO proporciona un método toDateObject para transformar fechas y horas del formato de base de datos a objetos DateTime:

public function toDateObject($fecha, $hora = null): DateTime
{
// Normalizar formato de fecha
$fecha = preg_replace('/[^0-9]/', '', $fecha);

// Separar fecha en componentes
$year = substr($fecha, 0, 4);
$month = substr($fecha, 4, 2);
$day = substr($fecha, 6, 2);

// Procesar la hora si está disponible
if ($hora !== null) {
// Normalizar formato de hora
$hora = preg_replace('/[^0-9]/', '', $hora);
$hours = substr($hora, 0, 2);
$minutes = substr($hora, 2, 2);
$seconds = strlen($hora) === 6 ? substr($hora, 4, 2) : '00';

return new DateTime("$year-$month-$day $hours:$minutes:$seconds");
}

return new DateTime("$year-$month-$day 00:00:00");
}

Ejemplo de uso:

public function toArray(): array
{
return [
// ...
'created_at' => $this->toDateObject($this->posprecab_fecha, $this->posprecab_hora),
// ...
];
}

Integración con resolvers

El framework utiliza un sistema de resolvers para registrar los DTOs específicos para cada ruta:

// src/App/Config/resolvers.php
return [
'/products' => [
InterfaceResDto::class => \ApiLayer\Domains\Products\DTOs\MaesartiProductosDTO::class,
InterfaceReqDto::class => \ApiLayer\Domains\Products\DTOs\CreateMaesartiProductosDTO::class,
],
'/permut/products' => [
InterfaceResDto::class => \ApiLayer\Modules\Permut\Domains\Products\DTOs\MaesartiProductosDTO::class,
InterfaceReqDto::class => \ApiLayer\Modules\Permut\Domains\Products\DTOs\CreateMaesartiProductosDTO::class,
],
];

Esto permite que el contenedor de dependencias resuelva automáticamente los DTOs adecuados para cada dominio y módulo:

// En QuKernel.php
$resolvers = Config::resolvers();
$path = $request->getPathInfo();

foreach ($resolvers as $routePrefix => $bindings) {
if (strpos($path, $routePrefix) === 0) {
foreach ($bindings as $interface => $concreteClass) {
$this->container->set($interface, function () use ($concreteClass) {
return new $concreteClass();
});
}
break;
}
}

Herencia y especialización de DTOs

El sistema permite la herencia y especialización de DTOs entre módulos:

// DTO base en el dominio principal
namespace ApiLayer\Domains\Products\DTOs;

class MaesartiProductosDTO extends ResDto { /* ... */ }

// DTO especializado en un módulo
namespace ApiLayer\Modules\Permut\Domains\Products\DTOs;

class MaesartiProductosDTO extends ResDto { /* ... */ }

Esta capacidad permite personalizar la transferencia de datos para diferentes contextos mientras se mantiene la coherencia en la estructura general.

Consideraciones técnicas

  1. Inmutabilidad: Los DTOs son principalmente inmutables, con métodos que retornan nuevas instancias en lugar de modificar el estado interno
  2. Transformación explícita: Las transformaciones de tipo son explícitas para evitar conversiones implícitas que puedan causar comportamientos inesperados
  3. Validación contextual: La validación se realiza en el contexto adecuado, separando reglas de validación (ReqDTO) de la transformación de datos (ResDTO)
  4. Optimización de consultas: El uso de $selectables permite optimizar las consultas de base de datos

Decisiones de diseño

  1. Separación ReqDTO/ResDTO: Se optó por separar claramente los DTOs de entrada y salida para mantener una clara separación de responsabilidades
  2. Interfaces: El uso de interfaces garantiza que todos los DTOs cumplan con un contrato definido
  3. Clases abstractas: Las clases base abstractas reducen la duplicación de código y promueven la coherencia
  4. Mapeo explícito: El mapeo explícito de campos proporciona control total sobre la transformación de datos

Casos de uso

  1. Transformación de datos API → BD: Los ReqDTO convierten datos de la API al formato de la base de datos
  2. Transformación de datos BD → API: Los ResDTO convierten datos de la base de datos al formato de la API
  3. Selección de campos: La propiedad $selectables optimiza las consultas
  4. Transformación de tipos: Garantiza la coherencia de tipos en las respuestas API
  5. Mapeo de filtros: Traduce criterios de filtrado de la API a criterios de consulta de base de datos