Skip to main content

11. Módulos y extensibilidad

Descripción General

El sistema implementa una arquitectura modular que permite organizar el código en dominios funcionales y módulos específicos. Esta estructura facilita la extensibilidad, reutilización de código y separación de responsabilidades. El framework proporciona mecanismos para crear, extender y personalizar tanto dominios base como módulos especializados.

Arquitectura

La arquitectura modular se basa en tres conceptos clave:

  1. Dominios: Representan áreas funcionales independientes con sus propios controladores, servicios, DTOs y rutas.
  2. Módulos: Agrupan múltiples dominios relacionados bajo un namespace común, generalmente para una funcionalidad empresarial específica.
  3. Extensión de dominios: Mecanismo para especializar dominios base, permitiendo personalizar su comportamiento.

Estructura organizativa:

src/
├── ApiLayer/
│ ├── Domains/ # Dominios base
│ │ └── Products/ # Dominio de productos base
│ └── Modules/ # Módulos especializados
│ ├── Permut/ # Módulo Permut
│ │ └── Domains/ # Dominios específicos del módulo
│ └── Tpv/ # Módulo TPV
│ └── Domains/ # Dominios específicos del módulo
└── Infrastructures/ # Capa de infraestructura
└── Legacy/ # Conexiones con sistemas legacy

Interfaces

Públicas

La estructura de módulos y dominios sigue una convención estándar:

// Estructura básica de un dominio
namespace ApiLayer\Domains\{DomainName};

// Estructura básica de un dominio en un módulo
namespace ApiLayer\Modules\{ModuleName}\Domains\{DomainName};

Convenciones de Implementación

Cada dominio, ya sea base o dentro de un módulo, implementa típicamente las siguientes clases:

{DomainName}Controller.php    # Gestiona las peticiones HTTP
{DomainName}Service.php # Implementa la lógica de negocio
{DomainName}Routes.php # Define las rutas del dominio
DTOs/ # Objetos de transferencia de datos
├── Req{Resource}DTO.php # DTOs para peticiones
└── Res{Resource}DTO.php # DTOs para respuestas

Flujo de Datos

El flujo de datos a través de los módulos y dominios sigue este patrón:

  1. La solicitud HTTP llega al sistema y es procesada por el router.
  2. El router identifica el controlador y método apropiados basados en la URL.
  3. Si la solicitud corresponde a un dominio de un módulo específico, se instancia su controlador.
  4. El controlador delega al servicio correspondiente del dominio.
  5. El servicio interactúa con los repositorios y realiza la lógica de negocio.
  6. El servicio utiliza DTOs para transformar los datos antes de devolverlos.
  7. La respuesta fluye de vuelta a través del controlador y middlewares.

Módulos y Dominios Específicos

11.1. Estructura de Módulos

Un módulo es una agrupación lógica de múltiples dominios relacionados. La estructura de un módulo típico se muestra a continuación:

Modules/{ModuleName}/
├── Domains/ # Dominios específicos del módulo
│ ├── Domain1/
│ └── Domain2/
├── Middlewares/ # Middlewares específicos del módulo
├── Services/ # Servicios compartidos
└── Helpers/ # Funciones auxiliares

Ejemplo de creación de un módulo:

// ApiLayer/Modules/Ejemplo/Domains/MiDominio/MiDominioController.php
namespace ApiLayer\Modules\Ejemplo\Domains\MiDominio;

use App\Base\DomainController;

class MiDominioController extends DomainController
{
protected $miDominioService;

public function __construct(MiDominioService $service)
{
parent::__construct($service);

// Validaciones específicas para este dominio
$this->validations = [
'headers' => ['x-ejemplo' => 'required|string'],
'body' => ['campo' => 'required|string'],
];
}
}

11.2. Sistema de Módulos TPV

El módulo TPV (Terminal Punto de Venta) es un ejemplo completo de implementación modular que incluye dominios para gestionar carritos de compra, productos y validaciones específicas para transacciones comerciales.

Estructura Principal:

Modules/Tpv/
├── Domains/
│ ├── Carts/ # Gestión de carritos de compra
│ │ ├── CartsController.php
│ │ ├── CartsService.php
│ │ ├── CartsRoutes.php
│ │ ├── LogicalValidations/
│ │ │ └── CartItemValidator.php
│ │ └── DTOs/
│ └── Products/ # Gestión de productos en TPV
│ ├── ProductsController.php
│ ├── ProductsService.php
│ ├── ProductsRoutes.php
│ └── DTOs/
└── Middlewares/
├── XTiendaClaseHabitualMiddleware.php
└── XMiddleware.php

Ejemplo del middleware específico del módulo TPV:

// ApiLayer/Modules/Tpv/Middlewares/XTiendaClaseHabitualMiddleware.php
namespace ApiLayer\Modules\Tpv\Middlewares;

use App\Core\Interfaces\Middleware;
use App\Core\RequestFactory as Request;
use App\Core\QuApp;
use Infrastructures\Legacy\PPos\PostieTienda\PostieTiendaRepository;
use Infrastructures\Legacy\PPos\PostieTienda\PostieTiendaModel;

class XTiendaClaseHabitualMiddleware implements Middleware
{
public function handle(Request $request, callable $next)
{
$tiendaRepository = new PostieTiendaRepository(new PostieTiendaModel());
$tiendaRepository->selectable(['posartcla_id']);

// Obtener y validar el header x-tienda
try {
$xTienda = $request->headers->get('x-tienda');
} catch (\Throwable $th) {
throw new \Exception('Se requiere el header x-tienda');
}

// Obtener la tienda y su clase habitual
try {
$tienda = $tiendaRepository->selectById($xTienda);
$claseHabitualTienda = $tienda['posartcla_id'];
} catch (\Throwable $th) {
throw new \Exception('Hubo un error al obtener la tienda');
}

// Determinar el tipo (H o T)
$HoT = 'H'; // H = clase habitual, T = todas las clases
if (isset($request->type)) {
if ($request->type != $claseHabitualTienda) {
$HoT = 'T';
}
}

// Guardar en el contenedor para uso posterior
$container = QuApp::getContainer();
$container->set('postie_id', function () use ($xTienda) {
return $xTienda;
});
$container->set('HoT', function () use ($HoT) {
return $HoT;
});

return $next($request);
}
}

Validación específica del módulo TPV:

// ApiLayer/Modules/Tpv/Domains/Carts/LogicalValidations/CartItemValidator.php
namespace ApiLayer\Modules\Tpv\Domains\Carts\LogicalValidations;

use App\Exceptions\BadRequestException;

class CartItemValidator
{
private $cartItem;
private $productData;

public function __construct(array $cartItem, array $productData)
{
$this->cartItem = $cartItem;
$this->productData = $productData;
}

public function validate(): void
{
$this->validatePrice();
$this->validateDiscount();
$this->validateUnits();
}

private function validatePrice(): void
{
$isPriceModifiable = (int) ($this->productData['posarttic_sw_prec_modi'] ?? 0);
$isPriceZeroAllowed = (int) ($this->productData['posarttic_sw_precio_0'] ?? 0);
$basePrice = (float) ($this->productData['posarttic_prec_mone'] ?? 0);
$price = (float) $this->cartItem['price'];

if ($isPriceModifiable !== 1 && $price !== $basePrice) {
throw new BadRequestException("El precio no es modificable y debe ser {$basePrice}");
}

// Más validaciones...
}

// Validación de descuento y unidades...
}

11.3. Sistema de Módulos Permut

El módulo Permut representa otro caso de implementación modular que extiende dominios base para proporcionar funcionalidades específicas.

Estructura de Permut:

Modules/Permut/
└── Domains/
└── Products/ # Extensión del dominio Products
├── ProductsController.php
├── ProductsService.php
├── ProductsRoutes.php
└── DTOs/
├── MaesartiProductosDTO.php
└── CreateMaesartiProductosDTO.php

Ejemplo de extensión de un dominio base:

// ApiLayer/Modules/Permut/Domains/Products/ProductsController.php
namespace ApiLayer\Modules\Permut\Domains\Products;

use ApiLayer\Domains\Products\ProductsController as DomainProductsController;

class ProductsController extends DomainProductsController
{
public function __construct(ProductsService $service)
{
parent::__construct($service);
// Aquí se pueden sobreescribir validaciones
// $this->validations = [
// 'headers' => ['x-tienda' => 'required|string'],
// 'body' => ['samplexx' => 'required|string'],
// ];
}
}
// ApiLayer/Modules/Permut/Domains/Products/ProductsService.php
namespace ApiLayer\Modules\Permut\Domains\Products;

use ApiLayer\Domains\Products\ProductsService as DomainProductsService;
use Infrastructures\Legacy\PMaes\MaesartiProductos\MaesartiProductosRepository;
use App\Dtos\Contracts\InterfaceReqDto;
use App\Dtos\Contracts\InterfaceResDto;

class ProductsService extends DomainProductsService
{
protected $MaesartiProductosRepository;
protected $CreateMaesartiProductosDTO;
protected $MaesartiProductosDTO;

public function __construct(
MaesartiProductosRepository $MaesartiProductosRepository,
InterfaceReqDto $CreateMaesartiProductosDTO,
InterfaceResDto $MaesartiProductosDTO
) {
parent::__construct($MaesartiProductosRepository, $CreateMaesartiProductosDTO, $MaesartiProductosDTO);
}
}

11.4. Extensión de Dominios Base

El sistema permite extender dominios base para crear implementaciones específicas. Este patrón se utiliza ampliamente en los módulos para reutilizar la funcionalidad básica mientras se personaliza el comportamiento.

Patrón de extensión:

  1. Crear una clase que extienda de la clase base correspondiente
  2. Sobreescribir los métodos necesarios
  3. Mantener la compatibilidad con la interfaz original
  4. Registrar las rutas apropiadas

Ejemplo de rutas específicas del módulo:

// ApiLayer/Modules/Permut/Domains/Products/ProductsRoutes.php
namespace ApiLayer\Modules\Permut\Domains\Products;

use App\Core\Router\Route;
use ApiLayer\Modules\Permut\Domains\Products\ProductsController;
use App\Middlewares\AuthMiddleware;

$middlewares = ['before' => [AuthMiddleware::class]];
Route::group(ProductsController::class, function () use ($middlewares) {
// Rutas con prefijo específico del módulo
Route::get('permut/products/', 'index', $middlewares);
Route::get('permut/products/:id', 'show', $middlewares);
Route::post('permut/products/', 'store', $middlewares);
Route::put('permut/products/:id', 'update', $middlewares);
Route::delete('permut/products/:id', 'delete', $middlewares);
});

Mecanismos de Extensibilidad

Generación de Código

El sistema incluye un comando Artesano para crear nuevos dominios, facilitando la expansión del sistema:

// App/Commands/CreateDomainCommand.php
namespace App\Commands;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class CreateDomainCommand extends Command
{
protected function configure()
{
$this->setName('app:create-domain')
->setDescription('Crea un nuevo Domain con su estructura basica')
->addArgument('domain', InputArgument::REQUIRED, 'Nombre del domain')
->addArgument('infrastructure', InputArgument::REQUIRED, 'Nombre de la infraestructura principal')
->addArgument('module', InputArgument::OPTIONAL, 'Nombre del modulo');
}

protected function execute(InputInterface $input, OutputInterface $output)
{
// Lógica para crear la estructura del dominio
// ...
}
}

Resolución Dinámica de DTOs

El sistema implementa un mecanismo de resolución dinámica de DTOs basado en la ruta, permitiendo que cada módulo defina sus propios DTOs sin cambiar el código base:

// 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,
],
];

Este mecanismo se utiliza en el kernel para registrar las implementaciones concretas:

// App/Core/QuKernel.php (extracto)
$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;
}
}

Dependencias

  • App\Core\Router\Route: Para definir las rutas de cada dominio y módulo
  • App\Core\QuContainer: Contenedor DI para resolver dependencias específicas
  • App\Commands: Comandos para generar estructura de código
  • App\Base\DomainController: Controlador base que puede ser extendido
  • App\Base\DomainService: Servicio base que puede ser extendido
  • App\Dtos\Contracts: Interfaces para los DTOs

Consideraciones Técnicas

Independencia de Módulos

Los módulos están diseñados para ser lo más independientes posible, con acoplamiento mínimo entre ellos. Esto permite:

  1. Desarrollo independiente de cada módulo
  2. Despliegue selectivo según necesidades
  3. Mejora de la mantenibilidad y testabilidad

Reuso de Código

El sistema promueve la reutilización de código a través de:

  1. Herencia de clases base (controladores, servicios)
  2. Composición mediante inyección de dependencias
  3. Middlewares compartidos o específicos por módulo

Convenciones de Nombres

Se siguen convenciones de nombres consistentes para facilitar la navegación y comprensión:

  • Nombres CamelCase para clases
  • Prefijos de namespace para modules (ApiLayer\Modules\)
  • Estructura de directorios que refleja la organización lógica

Decisiones de Diseño

  1. Extensión sobre composición: Se ha preferido la herencia para extender funcionalidad base, lo que simplifica la implementación pero puede limitar la flexibilidad en algunos casos.

  2. Agrupación por módulos: La organización por módulos facilita la separación de responsabilidades y el desarrollo paralelo por equipos diferentes.

  3. Resolución dinámica de implementaciones: El mecanismo de resolución basado en rutas permite adaptar el comportamiento sin modificar el código base.

  4. Convenciones sobre configuración: El sistema se basa en convenciones de nombres y estructura, reduciendo la necesidad de configuración explícita.

  5. Middlewares específicos por módulo: Cada módulo puede definir sus propios middlewares, permitiendo un control granular sobre el procesamiento de solicitudes.

Casos de Uso

  1. Creación de funcionalidad específica: Implementar módulos para áreas de negocio distintas (TPV, Permut) sin afectar el código base.

  2. Personalización de comportamiento: Extender dominios base para adaptar la lógica a requisitos específicos.

  3. Reutilización de infraestructura: Compartir modelos de datos y repositorios entre diferentes módulos.

  4. Validaciones específicas por dominio: Implementar reglas de validación particulares para cada dominio o módulo.