Vai al contenuto
DedicatedPHP Contatto

Come progettare i confini di fiducia tra moduli PHP senza duplicare le regole

Definisca cosa può presupporre ogni modulo PHP, cosa deve validare quando riceve dati e come verificare che autorizzazione e titolarità siano mantenute.

Schema concettuale di moduli PHP separati da contratti e controlli di autorizzazione

In un’applicazione PHP modulare, un confine non è soltanto una separazione di cartelle, classi o namespace. Determina anche quali dati un modulo può accettare, quali operazioni può eseguire e a chi spetta verificare ogni condizione. Se queste decisioni restano implicite, i moduli possono fidarsi troppo l’uno dell’altro, duplicare le validazioni o esporre dati che non dovrebbero controllare.

Progettare confini di fiducia tra moduli PHP significa rendere esplicite queste responsabilità e mantenerle verificabili man mano che il sistema cambia. Non significa trattare ogni chiamata interna come traffico ostile né ripetere tutte le regole in ogni livello. Significa individuare dove cambia l’autorità, definire contratti chiari e convalidare ogni operazione nel modulo competente a eseguirla.

Perché un confine tra moduli è anche un confine di fiducia

Perché un confine tra moduli è anche un confine di fiducia — guía visual de DedicatedPHP

Un modulo può utilizzare i dati di un altro tramite un’interfaccia interna, un evento, un’API o una query condivisa. In tutti i casi è utile chiedersi quali garanzie offre il mittente e quali deve verificare il destinatario. La posizione del codice non basta a rispondere: un metodo PHP chiamato direttamente può ricevere dati derivati da una richiesta esterna, da un’attività in background o da un altro modulo che evolve separatamente.

Distinga tra validazione del formato, invarianti di business e autorizzazione. Il formato verifica che un identificatore o una data abbiano una rappresentazione accettabile. Un’invariante garantisce, per esempio, che un’operazione non abbia una quantità negativa. L’autorizzazione determina se l’attore può eseguire quell’operazione su quella risorsa. Sono controlli correlati, ma non intercambiabili.

Il confine di fiducia emerge soprattutto quando cambiano l’attore, la titolarità del dato o il canale di ingresso. Per esempio, un modulo di fatturazione può ricevere un ordine da un altro modulo, ma non dovrebbe dedurre che la persona che ha avviato la richiesta abbia il permesso di modificare qualsiasi fattura. Identità e contesto devono essere trasmessi in modo esplicito e verificabile.

Inventariare i dati e le operazioni che attraversano ogni confine

Inizi mappando le interazioni rilevanti, senza riprogettare l’intera applicazione. Per ogni confine, documenti chi chiama, chi è titolare dell’operazione, quali dati vengono scambiati e se la chiamata è sincrona o asincrona. Includa anche i percorsi meno visibili: comandi da console, processi pianificati, consumer di code e attività di amministrazione.

  • Dati: identifichi i campi trasferiti, la loro origine e se contengono informazioni personali, sensibili o appartenenti a un altro dominio.
  • Operazioni: registri quali modifiche vengono richieste e quale modulo deve applicarle.
  • Contesto: determini quale attore, organizzazione, tenant o finalità deve accompagnare la richiesta.
  • Risultati: specifichi quali informazioni il modulo può restituire e quali errori può comunicare.

Questo inventario aiuta a individuare segnali di rischio: un modulo interroga tabelle altrui per evitare un’interfaccia; riceve un identificatore di risorsa senza verificarne la titolarità; oppure accetta un oggetto di dominio mutabile creato da un altro modulo. Rivela anche le duplicazioni: più consumer implementano una propria interpretazione di una regola la cui autorità spetta a un solo dominio.

Definire contratti espliciti: formato, invarianti e compatibilità

Un contratto descrive ciò di cui il destinatario ha bisogno per eseguire un’operazione e le garanzie offerte nella risposta. In PHP, può essere espresso tramite DTO immutabili, interfacce, tipi ben definiti o schemi per i messaggi, a seconda dell’architettura. La forma concreta conta meno della chiarezza: eviti di passare entità complete quando al consumer bastano pochi valori.

Il contratto deve precisare i campi obbligatori, i limiti e il significato. Un customerId, per esempio, non indica di per sé se identifica un cliente globale, uno appartenente a un’organizzazione o uno visibile a un determinato utente. Se il contesto condiziona l’operazione, lo includa oppure definisca come lo ottiene il modulo responsabile.

Concordi anche errori distinguibili. Un input malformato non equivale a un’operazione non autorizzata, a una risorsa inesistente o a un conflitto con lo stato corrente. I dettagli esposti al chiamante devono essere sufficienti per gestire il risultato, senza rivelare dati che facilitino l’enumerazione delle risorse o la conoscenza di informazioni di terzi.

I contratti devono evolvere con una compatibilità intenzionale. Aggiungere un campo opzionale può essere meno problematico che cambiare il significato di uno esistente, ma richiede comunque di esaminare produttori e consumer. Nei messaggi asincroni, consideri che una versione precedente può continuare a circolare dopo il deploy del nuovo codice. Non confonda il deploy del codice con l’attivazione di un nuovo comportamento: usi una transizione controllata quando produttori e consumer non cambiano nello stesso momento.

Convalidare autorizzazione e titolarità dove viene eseguita l’operazione

La validazione sintattica può avvenire vicino al punto di ingresso, ma l’autorizzazione deve essere verificata nel contesto che conosce la risorsa e governa l’azione. Il modulo titolare di una fattura, per esempio, dispone delle informazioni per decidere se la si può emettere o modificare. Non dovrebbe delegare questa decisione a un modulo chiamante che conosce soltanto l’identità dell’utente.

Un controllo efficace mette in relazione attore, azione, risorsa e contesto: chi richiede quale operazione, su quale risorsa e all’interno di quale organizzazione o tenant. Non presuma che un identificatore ricevuto equivalga a un permesso. Verifichi la titolarità al confine in cui viene eseguita l’operazione, anche se la richiesta proviene da codice interno considerato affidabile. Questo evita che un percorso alternativo o un successivo cambiamento architetturale aggirino un’autorizzazione applicata solo nell’interfaccia web.

Eviti, tuttavia, di replicare l’intera policy in tutti i moduli. Centralizzi la decisione nel modulo competente ed esponga un’operazione o un servizio con un contratto chiaro. I moduli consumer possono eseguire controlli di comodità per migliorare l’esperienza, ma non devono diventare l’unica barriera di sicurezza.

Evitare che le entità condivise creino accessi privilegiati

Condividere entità ORM tra moduli sembra pratico, ma può creare dipendenze nascoste. Se più moduli caricano e modificano la stessa entità, ognuno può aggirare le operazioni che dovrebbero proteggere le invarianti. Inoltre, le modifiche allo schema o al comportamento influiscono su consumer che non controllano il modello.

Preferisca trasferire i dati necessari tramite comandi, query o DTO. Il modulo titolare mantiene la responsabilità della persistenza e della validazione delle proprie invarianti; il consumer non ha bisogno di un accesso generale ai suoi repository o alle sue tabelle. Se serve una lettura, progetti una query che restituisca il minimo insieme di dati e rispetti i permessi e l’ambito definiti.

Un database condiviso non obbliga a condividere l’autorità. Anche prima di separare fisicamente i dati, si può stabilire che solo un modulo scriva determinate tabelle e che gli altri utilizzino interfacce documentate. Questa regola riduce il costo di una futura separazione e facilita l’individuazione degli accessi fuori contratto.

Verificare che l’isolamento si mantenga quando il sistema cambia

I test devono coprire sia il percorso valido sia i tentativi di oltrepassare un confine senza permesso. I test unitari verificano le regole del modulo titolare; i test di integrazione verificano contratti, persistenza e configurazione dell’autorizzazione tra componenti. Includa casi in cui un attore valido tenta di operare su una risorsa appartenente a un altro tenant, una risorsa inesistente e un contesto incompleto.

  • Verifichi che un identificatore valido non conceda accesso da solo.
  • Si assicuri che gli errori di autorizzazione non restituiscano dati della risorsa protetta.
  • Verifichi che i consumer non possano modificare le invarianti tramite entità condivise o accesso diretto ai repository.
  • Esegua test di contratto quando produttori e consumer vengono distribuiti in modo indipendente.

I test devono riflettere la policy reale, non limitarsi a verificare che venga lanciata un’eccezione. Si assicuri che la modifica non venga salvata, che non venga pubblicato un evento improprio e che la ripetizione dell’operazione non produca effetti parziali. Per i flussi con più passaggi, definisca cosa accade se una fase fallisce e come viene riconciliato lo stato.

Riesaminare i confini quando si estrae un servizio o cambia la titolarità

Riesaminare i confini quando si estrae un servizio o cambia la titolarità — guía visual de DedicatedPHP

L’estrazione in un servizio separato rende visibili presupposti che prima restavano nascosti in memoria: identità, permessi, latenza, retry, duplicati e disponibilità. Il contratto deve trasportare un contesto sufficiente e considerare i guasti di rete. Non trasformi una chiamata remota in una copia di una chiamata locale senza rivedere timeout, idempotenza e comportamento in caso di errore.

Riesamini i confini anche quando cambia chi è responsabile di un dato, viene introdotto un tenant, si aggiunge un nuovo canale o compaiono nuovi consumer. Si chieda chi può scrivere, chi può leggere, quale modulo decide l’autorizzazione e quali garanzie restano valide durante la migrazione. Mantenga queste risposte insieme alla documentazione del contratto e ai test corrispondenti.

Un confine di fiducia efficace non dipende dal fatto che tutti i moduli restino nello stesso processo o che il team ricordi convenzioni informali. È riconoscibile perché l’autorità è assegnata, i dati che attraversano il confine sono circoscritti, i permessi vengono verificati dove si esegue l’operazione e i test rilevano gli accessi non autorizzati. In questo modo si protegge l’isolamento senza trasformare ogni modulo in una replica delle regole degli altri.

Vuoi applicare queste idee al tuo progetto?Parliamo della tua piattaforma PHP.
Visualizza il servizio correlato