Skip to main content

2. Arquitectura del sistema

2.1. Patrones de diseño implementados

La arquitectura del sistema está construida sobre patrones de diseño bien establecidos que contribuyen a su modularidad, extensibilidad y mantenibilidad. Estos patrones se han implementado de forma consistente a través de todo el código base.

2.1.1. Patrón Repositorio

El patrón Repositorio actúa como una capa de abstracción entre la lógica de negocio y la fuente de datos subyacente, aislando el código de dominio de los detalles específicos de persistencia.

Implementación:

  • Se define a través de la interfaz RepositoryInterface y la clase base Repository
  • Cada entidad de dominio tiene su propio repositorio (ej. ProductsRepository, MaesartiProductosRepository)
  • Los repositorios se conectan a la capa de modelos que interactúan directamente con la infraestructura legacy

Ejemplo:

class PosartticVArticuloRepository extends Repository
{
public function __construct(PosartticVArticuloModel $model)
{
parent::__construct($model);
}

public function getByCodOrAcceso($codOrAcceso)
{
$articulo = $this->selectOne(['cod_articulo' => $codOrAcceso]);
if ($articulo) {
return $articulo;
}
return $this->selectOne(['acceso' => $codOrAcceso]);
}
}

Beneficios:

  • Centraliza la lógica de acceso a datos
  • Facilita el testing mediante mocks o stubs
  • Permite migrar o cambiar la fuente de datos sin afectar la lógica de negocio
  • Simplifica la gestión de transacciones a través de métodos estándar

2.1.2. Patrón Servicio

El patrón Servicio encapsula la lógica de negocio de un dominio específico, coordinando las operaciones entre repositorios y otros servicios.

Implementación:

  • Se define a través de la interfaz ServiceInterface y la clase base DomainService
  • Cada dominio tiene su servicio correspondiente (ej. ProductsService, CartsService)
  • Los servicios contienen la lógica de negocio y utilizan repositorios para el acceso a datos

Ejemplo:

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

public function __construct(
MaesartiProductosRepository $MaesartiProductosRepository,
InterfaceReqDto $CreateMaesartiProductosDTO,
InterfaceResDto $MaesartiProductosDTO
) {
$this->MaesartiProductosRepository = $MaesartiProductosRepository;
$this->CreateMaesartiProductosDTO = $CreateMaesartiProductosDTO;
$this->MaesartiProductosRepository->selectable(MaesartiProductosDTO::$selectables);
$this->MaesartiProductosDTO = $MaesartiProductosDTO;
}

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

// Otros métodos CRUD y de negocio...
}

Beneficios:

  • Separa la lógica de negocio de controladores y modelos
  • Permite la reutilización de lógica a través de diferentes puntos de entrada
  • Facilita la implementación de reglas de negocio complejas
  • Mejora la testabilidad del sistema

2.1.3. Patrón Controlador

El patrón Controlador gestiona las solicitudes HTTP, delegando el procesamiento a los servicios apropiados y formateando las respuestas.

Implementación:

  • Se define a través de la interfaz ControllerInterface y clases base como Controller y DomainController
  • Cada dominio tiene su controlador correspondiente (ej. ProductsController, CartsController)
  • Los controladores reciben solicitudes HTTP, las validan, las delegan a servicios y formatean las respuestas

Ejemplo:

class CartsController extends Controller
{
protected $CartsService;

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

public function store(Request $request)
{
ValidaRequest::make($request)
->validateHeaders([
'x-tienda' => 'required|numeric',
])
->validateBody([
'user_id' => 'required|numeric',
])
->action('store')
->handle();

$xTienda = $request->headers->get('x-tienda');
$request->request->add(['x_tienda' => $xTienda]);

return response()->json($this->CartsService->store($request->all()), 201);
}

// Otros métodos para gestionar diferentes endpoints...
}

Beneficios:

  • Establece un punto único para el manejo de solicitudes HTTP
  • Separa la lógica de presentación de la lógica de negocio
  • Facilita la implementación de validaciones de entrada
  • Estandariza el formato de las respuestas

2.1.4. Patrón DTO (Data Transfer Object)

El patrón DTO se utiliza para encapsular datos que se transfieren entre capas del sistema, normalizando estructuras y validando campos.

Implementación:

  • Se definen a través de las interfaces InterfaceReqDto y InterfaceResDto
  • Implementados en clases base como ReqDto y ResDto
  • Dos tipos principales: DTOs de entrada (Request) y DTOs de salida (Response)

Ejemplo de DTO de respuesta:

class PosartticVArticuloDTO extends ResDto implements InterfaceResDto
{
public static $selectables = [
"id", "postie_id", "id_maesarti", "cod_articulo", "acceso", "nombre", "posartcla_id",
// Otros campos...
];

protected $id;
protected $postie_id;
protected $nombre;
// Otras propiedades...

public function handle(array $data): InterfaceResDto
{
$this->id = $data['id'];
$this->nombre = $data['nombre'];
// Asignación de otras propiedades...
return $this;
}

public function toArray(): array
{
return [
'id' => (string) $this->id,
'name' => (string) trim($this->nombre),
'price' => (float) $this->posarttic_prec_mone,
// Transformación de otros campos...
];
}
}

Beneficios:

  • Proporciona un contrato claro para la transferencia de datos
  • Facilita la transformación entre formatos internos y externos
  • Permite la validación centralizada de datos
  • Evita la exposición directa de entidades de dominio

2.2. Estructura de directorios

La organización de directorios del sistema sigue una estructura que refleja las capas arquitectónicas y facilita la localización de componentes:

/api2_para_consolidar/
├── bootstrap.php # Inicialización del sistema
├── index.php # Punto de entrada principal
├── artesano # CLI para generación de código
├── keys/ # Almacenamiento de claves de seguridad
├── src/
│ ├── ApiLayer/ # Capa de API
│ │ ├── Domains/ # Dominios principales
│ │ │ └── Products/ # Ejemplo de dominio de productos
│ │ │ ├── DTOs/ # Data Transfer Objects
│ │ │ ├── ProductsController.php
│ │ │ ├── ProductsService.php
│ │ │ └── ProductsRoutes.php
│ │ └── Modules/ # Módulos específicos
│ │ ├── Permut/ # Módulo Permut
│ │ │ └── Domains/ # Dominios dentro del módulo
│ │ └── Tpv/ # Módulo TPV
│ │ ├── Domains/ # Dominios dentro del módulo
│ │ │ ├── Carts/
│ │ │ └── Products/
│ │ └── Middlewares/ # Middlewares específicos del módulo
│ ├── App/ # Núcleo de la aplicación
│ │ ├── Base/ # Clases base y abstracciones
│ │ │ ├── DB/ # Componentes de base de datos
│ │ │ ├── Interfaces/ # Interfaces del sistema
│ │ │ ├── Controller.php
│ │ │ ├── DomainController.php
│ │ │ ├── Repository.php
│ │ │ ├── Model.php
│ │ │ └── Service.php
│ │ ├── Commands/ # Comandos CLI para artesano
│ │ ├── Config/ # Configuraciones del sistema
│ │ ├── Core/ # Componentes centrales
│ │ │ ├── Interfaces/ # Interfaces del núcleo
│ │ │ ├── Router/ # Sistema de enrutamiento
│ │ │ ├── QuApp.php # Estado global
│ │ │ ├── QuKernel.php # Kernel de la aplicación
│ │ │ ├── QuContainer.php # Contenedor de dependencias
│ │ │ └── CoreQuartup.php # Puente a sistema legacy
│ │ ├── Dtos/ # Clases base para DTOs
│ │ ├── Exceptions/ # Excepciones personalizadas
│ │ ├── Helpers/ # Funciones y utilidades
│ │ ├── Middlewares/ # Middlewares globales
│ │ └── Validations/ # Sistema de validación
│ └── Infrastructures/ # Capa de infraestructura
│ └── Legacy/ # Integración con sistemas legacy
│ ├── PMaes/ # Módulo PMaes legacy
│ └── PPos/ # Módulo PPos legacy
└── vendor/ # Dependencias externas

Esta estructura refleja claramente la separación de responsabilidades:

  • El directorio ApiLayer contiene los componentes que definen la API pública
  • App proporciona las abstracciones y funcionalidades del framework
  • Infrastructures gestiona la comunicación con sistemas externos

2.3. Flujo de solicitudes (Request lifecycle)

El ciclo de vida de una solicitud en el sistema sigue un flujo predecible que atraviesa varias capas:

  1. Recepción de la solicitud HTTP:

    • El punto de entrada index.php recibe la solicitud
    • Se crea una instancia de Request utilizando RequestFactory
    • Se establece el contexto global mediante QuApp::setRequest()
  2. Inicialización del sistema:

    • Se carga la configuración desde bootstrap.php
    • Se establece el entorno (DEV/PROD)
    • Se registran los servicios globales en el contenedor de dependencias
  3. Middleware pipeline:

    • Se añaden middleware globales como TransactionStartMiddleware
    • Se verifica la autenticación si la ruta lo requiere
    • Se procesan otros middleware como SwitchUtf8Latin1Middleware y RateLimitMiddleware
  4. Enrutamiento:

    • El QuRouter analiza la URL y determina el controlador y método a invocar
    • Se extraen los parámetros de ruta
  5. Resolución de dependencias:

    • El QuContainer resuelve las dependencias necesarias para el controlador
    • Se aplican las vinculaciones específicas de la ruta
  6. Ejecución del controlador:

    • Se instancia el controlador adecuado
    • Se validan los datos de entrada según las reglas definidas
    • Se invoca el método correspondiente al endpoint
  7. Lógica de negocio:

    • El controlador delega en el servicio de dominio
    • El servicio procesa la solicitud, interactuando con repositorios y DTOs
  8. Acceso a datos:

    • Los repositorios interactúan con modelos para acceder a datos
    • Los modelos se comunican con la infraestructura legacy
  9. Formación de la respuesta:

    • El servicio mapea los datos de salida a través de DTOs
    • El controlador construye una respuesta HTTP utilizando response()->json()
  10. Procesamiento de salida:

    • Los middleware de salida procesan la respuesta
    • TransactionEndMiddleware finaliza la transacción y registra la operación
  11. Envío de la respuesta:

    • QuApp::sendResponseAndEnd() envía la respuesta al cliente
    • Se registran logs y se cierra la sesión

Este flujo está diseñado para manejar tanto el caso exitoso como situaciones de error, donde excepciones son capturadas y transformadas en respuestas HTTP apropiadas.

2.4. Sistema de dependencias e inyección

El sistema utiliza un contenedor de inyección de dependencias propio, implementado en QuContainer, que sigue los principios del patrón Service Container.

Funcionamiento del contenedor:

El contenedor permite:

  • Registrar servicios para su uso posterior
  • Resolver dependencias automáticamente mediante reflection
  • Construir objetos con sus dependencias inyectadas
  • Aplicar configuraciones específicas por ruta o contexto

Ejemplo de registro de servicios:

// En services.php
return [
Services\Interfaces\MailServiceInterface::class => Services\MailLog::class,
Services\Interfaces\StoreTransactionServiceInterface::class => Services\StoreTransactionLog::class
];

// En index.php
$container = new QuContainer;
foreach ($services as $interface => $implementation) {
$container->set($interface, function () use ($implementation) {
return new $implementation;
});
}

Resolución automática:

El contenedor puede construir objetos mediante análisis de constructor:

// En QuContainer.php
public function build(string $class)
{
$reflector = new ReflectionClass($class);
$constructor = $reflector->getConstructor();

if ($constructor === null) {
return new $class;
}

$parameters = $constructor->getParameters();
$dependencies = [];

foreach ($parameters as $parameter) {
// Resolver dependencia por tipo...
$type = $parameter->getType();
$name = $type->getName();

try {
$dependency = $this->get($name);
} catch (Exception $e) {
$dependency = $this->build($parameter->getType()->getName());
$this->set($name, $dependency);
}

$dependencies[] = $dependency;
}

return $reflector->newInstanceArgs($dependencies);
}

Binding contextual:

El sistema permite registrar bindings específicos para rutas particulares, lo que facilita la implementación de diferentes comportamientos según el contexto:

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

// En QuKernel::handle()
$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;
}
}

Beneficios del sistema de dependencias:

  • Desacoplamiento: Los componentes no necesitan conocer cómo crear sus dependencias
  • Testabilidad: Facilita la sustitución de implementaciones reales por mocks en pruebas
  • Flexibilidad: Permite cambiar implementaciones sin modificar los consumidores
  • Modularidad: Apoya la organización del código en módulos cohesivos y débilmente acoplados
  • Extensibilidad: Facilita la ampliación del sistema al permitir reemplazar componentes

La inyección de dependencias es un pilar fundamental de la arquitectura, facilitando la implementación de los demás patrones y el mantenimiento del sistema a largo plazo.