Guía para el Desarrollo de Extensiones de Quartup normalizadas
0. Objetivo y Contexto de las Extensiones normalizadas
Las extensiones de Quartup normalizadas son módulos diseñados para implementar funcionalidades que operan de forma independiente de la base de código Legacy del sistema. Esto es especialmente útil para nuevos desarrollos, como conectores con aplicaciones externas, que necesitan un enfoque moderno y flexible.
Al usar las extensiones, minimizamos la dependencia del código Legacy y podemos aplicar patrones de diseño e interfaces estándar. Esto garantiza que los nuevos módulos sean más consistentes, modulares y fáciles de mantener a largo plazo.
A continuación, se detalla el proceso de desarrollo de una extensión, utilizando la extensión DocuHub como ejemplo práctico.
(Esta extensión se llama Conectores a Sistemas Externos de Documentos y sirve para el sistema Veri-Factu entre otros)
1. Creación de la Extensión en el Directorio /src
Para registrar una nueva extensión, debes crear un subdirectorio con su nombre dentro del directorio principal /src de Quartup.
Los namespace que estén referidos a este directorio deben empezar por App\ seguido de los nombres de los directorios que cuelguen de /src.
Esta estructura es fundamental para el sistema de autoloading de Quartup, ya que le permite localizar y cargar automáticamente cada clase.
Ejemplo de estructura:
Para acceder a las clases del directorio DocuHub/..., los namespaces de tus clases han de ser de la forma App\DocuHub\...
src/
├── DocuHub/
└── Outbox/
2. Estructura de Directorios y Clases
Dentro del directorio que acabas de crear (DocuHub), debes organizar tu código siguiendo una estructura clara que separe las responsabilidades. Los siguientes directorios se utilizan para agrupar clases con funciones similares:
Connector/: Contiene las interfaces y clases que adaptan la lógica de negocio a servicios externos. Esto permite interactuar con APIs de terceros sin acoplar directamente el código principal a sus especificaciones.DTO/(Data Transfer Objects): Clases simples que se utilizan para transferir datos de manera estructurada entre diferentes capas de la aplicación. Son especialmente útiles para normalizar los datos de sistemas externos.Exceptions/: Contiene clases de excepción personalizadas para manejar errores específicos de la extensión. Esto facilita la captura y gestión de errores de forma controlada.Clients/: Aquí se ubica la lógica de negocio principal de la extensión, orquestando las llamadas a los conectores y manejando los DTOs.
Ejemplo de estructura de directorios:
DocuHub/
├── Connector/
│ ├── ConnectorInterface.php
│ ├── ConnectorFactory.php
│ └── ConnectorB2Brouter.php
├── DTO/
│ ├── TenantDTO.php
│ ├── ServiceTypeDTO.php
│ ├── CustomerDTO.php
│ └── InvoiceDTO.php
├── Exceptions/
│ ├── TenantException.php
│ ├── ServiceTypeException.php
│ ├── CustomerException.php
│ ├── InvoiceException.php
│ └── GeneralException.php
├── Clients/
│ ├── ClientTenant.php
│ ├── ClientServiceType.php
│ ├── ClientCustomer.php
│ └── ClientInvoice.php
└── Mock.php
3. Uso de Namespaces (PSR-4)
Aunque Quartup tiene su propio sistema de carga, es obligatorio utilizar namespaces en todas las clases de la extensión, siguiendo la recomendación PSR-4. Esto mantiene el código organizado y evita conflictos de nombres.
El namespace base para esta extensión es App\DocuHub\. A partir de ahí, se definen sub-namespaces según la estructura de directorios.
Ejemplo de namespaces:
// DocuHub/Connector/ConnectorInterface.php
namespace App\DocuHub\Connector;
use App\DocuHub\DTO\TenantDTO;
use App\DocuHub\DTO\ServiceTypeDTO;
use App\DocuHub\DTO\CustomerDTO;
use App\DocuHub\DTO\InvoiceDTO;
use App\DocuHub\Exceptions\TenantException;
use App\DocuHub\Exceptions\ServiceTypeException;
use App\DocuHub\Exceptions\CustomerException;
use App\DocuHub\Exceptions\InvoiceException;
use App\DocuHub\Exceptions\GeneralException;
/**
* Define la interfaz para los conectores de DocuHub
*/
interface ConnectorInterface
{
// ...
}
// DocuHub/Connector/ConnectorB2Brouter.php
namespace App\DocuHub\Connector;
use App\DocuHub\DTO\TenantDTO;
use App\DocuHub\DTO\ServiceTypeDTO;
use App\DocuHub\DTO\CustomerDTO;
use App\DocuHub\DTO\InvoiceDTO;
use App\DocuHub\Exceptions\TenantException;
use App\DocuHub\Exceptions\ServiceTypeException;
use App\DocuHub\Exceptions\CustomerException;
use App\DocuHub\Exceptions\InvoiceException;
use App\DocuHub\Exceptions\GeneralException;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
/**
* Conector para la API de B2Brouter.
*/
class ConnectorB2Brouter implements ConnectorInterface
{
// ...
}
4. Carga de Clases (Autoloading)
A diferencia de un proyecto estándar de PHP, no se debe utilizar composer.json ni Composer para la carga de clases (autoloading). El sistema de Quartup se encarga de interpretar los namespaces que has definido y de cargar las clases automáticamente. Esta es una particularidad del entorno de Quartup que simplifica el proceso, evita conflictos, y se integra totalmente con el sistema Legacy de autoloading de Quartup.
5. Nomenclatura y Carga de Clases en la Extensión
Dado que cada extensión se desarrolla dentro de su propio namespace raíz, puedes nombrar tus clases con nomenclaturas estándar (por ejemplo, Service, Adapter, DTO) sin preocuparte por colisiones con clases de otras extensiones. El uso de namespaces garantiza que cada clase tenga un identificador único en todo el proyecto.
Cómo Instanciar una Clase
Para instanciar una clase de la extensión desde cualquier otro lugar del proyecto (tanto dentro como fuera de la propia extensión), debes usar su namespace completo.
Ejemplo:
Si tu extensión es DocuHub y tienes una clase ClientTenant en el subdirectorio Clients, la instanciación se haría de la siguiente manera:
use \App\DocuHub\Clients\ClientTenant;
...
$oClient = new ClientTenant(1);
Este enfoque asegura que el autoloader de Quartup pueda localizar y cargar correctamente la clase, manteniendo la organización y la claridad del código.
6. Normalización de Datos con DTOs
Cuando la extensión se conecta a sistemas externos (como una API de terceros), es crucial utilizar Data Transfer Objects (DTOs). Los DTOs son objetos simples, sin lógica de negocio, cuya única función es transportar datos de un punto a otro de la aplicación. Actúan como un puente que permite convertir los datos recibidos del sistema externo a un formato estandarizado y coherente con la lógica de Quartup.
Ventajas de usar DTOs:
- Aislamiento (
Decoupling): La lógica de negocio de Quartup queda completamente aislada de los detalles de implementación del sistema externo. Si la API de un tercero cambia su estructura de datos, solo necesitas modificar el conector y el DTO, sin tener que tocar el resto de la aplicación. - Consistencia y Validación: Los DTOs aseguran que los datos que circulan por tu extensión siempre tengan una estructura predefinida. Esto simplifica la validación y previene errores al trabajar con datos de diferentes fuentes.
Ejemplo de Flujo de Datos con DTOs
Imagina que la aplicación p_cial necesita interactuar con la API de B2Brouter para procesar facturas. El flujo de datos se ejecutaría de la siguiente manera:
- Activación del Proceso: Los servicios principales de la extensión,
Client*, inician la operación de recuperar una entidad de B2Brouter. - Llamada al Conector:
ClientTenant(por ejemplo) no se comunica directamente con la API externa. En su lugar, llama al método correspondiente del conectorConnectorB2Brouter, que es la única clase que "sabe" cómo interactuar con B2Brouter. - Consumo de la API: El
ConnectorB2Brouterrealiza la llamada HTTP a la API de B2Brouter. La respuesta de la API (por ejemplo, un JSON con datos de factura) tiene un formato específico de B2Brouter. - Transformación a DTO: En lugar de pasar los datos de la respuesta directamente, el conector toma esos datos y los "mapea" o los transforma en un objeto DTO de la aplicación, como
TenantDTO. Este DTO contiene solo los campos necesarios y en el formato que Quartup espera. - Procesamiento Interno: El
ClientTenantrecibe el objetoTenantDTOdel conector. Ahora puede trabajar con un objeto normalizado y seguro, sin preocuparse por la complejidad o el formato original de la API de B2Brouter.