En una aplicación PHP modular, una frontera no es solo una separación de carpetas, clases o namespaces. También determina qué datos puede aceptar un módulo, qué operaciones puede ejecutar y de quién es la responsabilidad de comprobar cada condición. Si esas decisiones quedan implícitas, los módulos pueden confiar demasiado unos en otros, duplicar validaciones o exponer datos que no deberían controlar.
Diseñar límites de confianza entre módulos PHP consiste en hacer explícitas esas responsabilidades y mantenerlas verificables a medida que cambia el sistema. No implica tratar cada llamada interna como tráfico hostil ni repetir todas las reglas en cada capa. Implica identificar dónde cambia la autoridad, definir contratos claros y validar cada operación en el módulo que tiene competencia para ejecutarla.
Por qué una frontera entre módulos también es una frontera de confianza

Un módulo puede consumir datos de otro a través de una interfaz interna, un evento, una API o una consulta compartida. En todos los casos conviene preguntar qué garantías ofrece el emisor y cuáles necesita comprobar el receptor. La ubicación del código no basta para responder: un método PHP llamado directamente puede recibir datos derivados de una petición externa, de una tarea en segundo plano o de otro módulo que evoluciona por separado.
Distinga entre validación de formato, invariantes de negocio y autorización. El formato comprueba que un identificador o una fecha tengan una representación aceptable. Una invariante asegura, por ejemplo, que una operación no lleve una cantidad negativa. La autorización determina si el actor puede realizar esa operación sobre ese recurso. Son controles relacionados, pero no intercambiables.
El límite de confianza aparece especialmente cuando cambia el actor, la propiedad del dato o el canal de entrada. Por ejemplo, un módulo de facturación puede recibir una orden de otro módulo, pero no debería deducir que la persona que inició la petición tiene permiso para modificar cualquier factura. La identidad y el contexto deben llegar de forma explícita y verificable.
Inventariar los datos y operaciones que cruzan cada frontera
Empiece por mapear las interacciones relevantes, no por rediseñar toda la aplicación. Para cada frontera, documente quién llama, quién es dueño de la operación, qué datos se intercambian y si la llamada es síncrona o asíncrona. Incluya los caminos menos visibles: comandos de consola, procesos programados, consumidores de colas y tareas de administración.
- Datos: identifique los campos transferidos, su origen y si contienen información personal, sensible o de otro dominio.
- Operaciones: registre qué cambios se solicitan y qué módulo debe aplicarlos.
- Contexto: determine qué actor, organización, tenant o propósito debe acompañar la petición.
- Resultados: especifique qué información puede devolver el módulo y qué errores puede comunicar.
Este inventario ayuda a detectar señales de riesgo: un módulo consulta tablas ajenas para evitar una interfaz; recibe un identificador de recurso sin comprobar su pertenencia; o acepta un objeto de dominio mutable creado por otro módulo. También revela duplicación: varios consumidores implementan su propia interpretación de una regla cuya autoridad corresponde a un solo dominio.
Definir contratos explícitos: formato, invariantes y compatibilidad
Un contrato describe lo que el receptor necesita para ejecutar una operación y las garantías que ofrece al responder. En PHP, puede expresarse mediante DTO inmutables, interfaces, tipos bien definidos o esquemas para mensajes, según la arquitectura. La forma concreta importa menos que la claridad: evite pasar entidades completas cuando el consumidor solo necesita unos pocos valores.
El contrato debe precisar campos obligatorios, límites y significado. Un customerId, por ejemplo, no dice por sí solo si identifica un cliente global, uno perteneciente a una organización o uno visible para un usuario concreto. Si el contexto condiciona la operación, inclúyalo o defina cómo lo obtiene el módulo responsable.
También acuerde errores distinguibles. Una entrada mal formada no es lo mismo que una operación no autorizada, un recurso inexistente o un conflicto con el estado actual. Los detalles expuestos a quien llama deben ser suficientes para manejar el resultado, sin revelar datos que faciliten enumerar recursos o conocer información de terceros.
Los contratos deben evolucionar con compatibilidad deliberada. Añadir un campo opcional puede ser menos disruptivo que cambiar el significado de uno existente, pero aun así requiere revisar productores y consumidores. En mensajes asíncronos, considere que una versión antigua puede seguir circulando después de desplegar código nuevo. No confunda el despliegue del código con la activación de una nueva conducta: use una transición controlada cuando productores y consumidores no cambien al mismo tiempo.
Validar autorización y propiedad donde se ejecuta la operación
La validación sintáctica puede ocurrir cerca de la entrada, pero la autorización debe comprobarse en el contexto que conoce el recurso y gobierna la acción. El módulo propietario de una factura, por ejemplo, tiene la información para decidir si se puede emitir o modificar. No debería delegar esa decisión en un módulo llamador que solo conoce la identidad del usuario.
Una comprobación útil enlaza actor, acción, recurso y contexto: quién solicita qué operación, sobre cuál recurso y dentro de qué organización o tenant. No suponga que un identificador recibido equivale a permiso. Compruebe la pertenencia en el límite que ejecuta la operación, incluso si la petición viene de código interno considerado fiable. Esto evita que una ruta alternativa o un cambio posterior de arquitectura eluda una autorización aplicada únicamente en la interfaz web.
Evite, sin embargo, replicar la política completa en todos los módulos. Centralice la decisión en el módulo competente y exponga una operación o servicio con un contrato claro. Los módulos consumidores pueden hacer comprobaciones de conveniencia para mejorar la experiencia, pero no deben convertirse en la única barrera de seguridad.
Evitar que las entidades compartidas creen acceso privilegiado
Compartir entidades ORM entre módulos parece práctico, pero puede crear dependencias ocultas. Si varios módulos cargan y modifican la misma entidad, cualquiera puede saltarse las operaciones que deberían proteger sus invariantes. Además, los cambios de esquema o comportamiento afectan a consumidores que no controlan el modelo.
Prefiera transferir los datos necesarios mediante comandos, consultas o DTO. El módulo propietario conserva la responsabilidad de persistir y validar sus invariantes; el consumidor no necesita acceso general a sus repositorios o tablas. Si se requiere una lectura, diseñe una consulta que devuelva el conjunto mínimo de datos y respete los permisos y el alcance definidos.
Una base de datos compartida no obliga a compartir autoridad. Aun antes de separar físicamente los datos, puede establecerse que solo un módulo escribe determinadas tablas y que los demás utilizan interfaces documentadas. Esa regla reduce el coste de una futura separación y facilita detectar accesos fuera de contrato.
Probar que el aislamiento se mantiene al cambiar el sistema
Las pruebas deben cubrir tanto el camino válido como los intentos de cruzar una frontera sin permiso. Las pruebas unitarias comprueban reglas del módulo propietario; las pruebas de integración verifican contratos, persistencia y configuración de autorización entre componentes. Incluya casos con un actor válido que intenta operar sobre un recurso de otro tenant, un recurso inexistente y un contexto incompleto.
- Compruebe que un identificador válido no concede acceso por sí solo.
- Verifique que errores de autorización no devuelven datos del recurso protegido.
- Pruebe que los consumidores no pueden modificar invariantes mediante entidades compartidas o acceso directo a repositorios.
- Ejecute pruebas de contrato cuando productores y consumidores se desplieguen de forma independiente.
Las pruebas deben reflejar la política real, no solo comprobar que una excepción se lanza. Asegure que el cambio no se persiste, que no se publica un evento indebido y que una repetición de la operación no produce efectos parciales. Para flujos con varios pasos, defina qué ocurre si una etapa falla y cómo se reconcilia el estado.
Revisar las fronteras al extraer un servicio o cambiar la propiedad

Una extracción a un servicio separado vuelve visibles supuestos que antes quedaban ocultos en memoria: identidad, permisos, latencia, reintentos, duplicados y disponibilidad. El contrato debe transportar contexto suficiente y contemplar fallos de red. No convierta una llamada remota en una copia de una llamada local sin revisar tiempos de espera, idempotencia y comportamiento ante errores.
Revise también las fronteras cuando cambia quién es responsable de un dato, se incorpora un tenant, se añade un nuevo canal o aparecen nuevos consumidores. Pregunte quién puede escribir, quién puede leer, qué módulo decide la autorización y qué garantías se mantienen durante la migración. Mantenga estas respuestas junto a la documentación del contrato y las pruebas correspondientes.
Un límite de confianza útil no depende de que todos los módulos permanezcan en el mismo proceso ni de que el equipo recuerde convenciones informales. Se reconoce porque la autoridad está asignada, los datos que cruzan están acotados, los permisos se comprueban donde se ejecuta la operación y las pruebas detectan accesos no autorizados. Así se protege el aislamiento sin convertir cada módulo en una réplica de las reglas de los demás.



