Perché le decisioni architetturali sembrano molto più difficili di quanto non siano
Un paio di anni fa, ho trascorso tre sere cercando di aggiungere quella che sembrava una funzione “semplice” a OpenClaw: un limitatore di frequenza per progetti per i webhook. Tre sere. Nulla di ciò che provavo si integrava in modo pulito. Ogni cambiamento toccava cinque file. Ogni “aggiustamento” rompeva qualche integrazione di cui avevamo mezzo dimenticato l’esistenza dal 2021.
Quello è stato il momento in cui ho capito che il nostro problema non era la mancanza di test o refactor pigri. Era che non eravamo stati abbastanza deliberati nelle decisioni architetturali. Avevamo codice che funzionava, ma non gli piaceva essere cambiato.
Se contribuisci a OpenClaw (o ci stai pensando), voglio guidarti attraverso come realmente prendiamo decisioni architetturali adesso. Non la versione idealizzata “abbiamo disegnato un diagramma a blocchi”. Le cose reali: i compromessi, il “ci pentiremo di questo ma ne vale la pena,” e i posti dove teniamo intenzionalmente le cose noiose.
Il vero lavoro dell’architettura: rendere i cambiamenti economici
Non mi interessa quanto siano belli i diagrammi. Se apportare una modifica costa un intero weekend e un’emicrania, la tua architettura ti sta mentendo.
In OpenClaw, abbiamo iniziato a usare un test molto semplice per le decisioni architetturali:
- Questo rende il prossimo cambiamento più economico?
- Questo rende il debug più veloce di quanto non sia oggi?
Ecco tutto. Non “questo modello è corretto” o “questo scalerà a 10 milioni di utenti.” Non abbiamo 10 milioni di utenti. Abbiamo mantenitori che si esauriscono quando aggiungere una piccola funzione significa leggere 2.000 righe di codice non correlate.
Esempio: nell’agosto del 2024 abbiamo introdotto l’astrazione ExecutionPlan nel job runner. Prima di ciò, aggiungere un nuovo passo di esecuzione significava:
- Modificare il core scheduler (cattiva idea #1)
- Toccare 3 enum diversi (cattiva idea #2)
- Aggiornare due posti che costruivano manualmente query SQL (cattiva idea #3)
Abbiamo ingoiato il rospo e creato ExecutionPlan come modulo separato. Sì, è stata una grande differenza (circa 1.200 righe cambiate). Sì, ha rotto un sacco di script interni. Ma ora è un file da comprendere, un posto dove inserire un nuovo passo, e il scheduler non conosce né si preoccupa dei dettagli.
Era “un’architettura perfetta”? Assolutamente no. Abbiamo già rielaborato parti di essa due volte. Ma ogni cambiamento da allora è stato più piccolo, più sicuro e più facile da rivedere. Quello è l’unico punteggio a cui sto davvero prestando attenzione.
Come decidiamo dove mettere una funzione (e quando dire di no)
L’architettura nel software open source è particolarmente strana perché non stai solo combattendo contro la complessità, stai anche combattendo contro le aspettative. Le richieste di funzionalità arrivano con forti opinioni su dove le cose “dovrebbero” vivere.
Quindi in OpenClaw, ci basiamo su tre semplici domande quando decidiamo dove appartiene una nuova funzionalità:
- Chi possiede effettivamente i dati? (il codice dovrebbe vivere vicino ai propri dati)
- Chi fa il debug quando si rompe? (tieni a mente il modello mentale di quella persona)
- Qualcuno può cancellare questo tra un anno senza leggere l’intero repo?
Lasciami mostrarti un esempio concreto.
Nel marzo 2025, qualcuno ha aperto un’issue chiedendo “script Lua inline all’interno delle definizioni di pipeline.” Molto bella come idea. Molto pericolosa per il codice sorgente. C’erano tre modi ovvi per farlo:
- Script inline all’interno del parser YAML (allettante, ma maledetto)
- Aggiungere uno strato di scripting all’interno del core engine (molto maledetto)
- Trattare gli script come plugin con un’interfaccia chiara (più noioso, più lavoro)
Se lo avessimo incollato nel parser YAML, sarebbe arrivato più velocemente, ma ogni piccolo cambiamento nella sintassi di configurazione rischierebbe di rompere gli script dei clienti in modi strani. Questo è una bomba a orologeria per i futuri mantenitori.
Abbiamo optato per l’interfaccia in stile plugin sovrapposta al motore di esecuzione della pipeline. Questo significava:
- Un piccolo modulo runtime Lua, senza alcuna conoscenza di YAML
- Un confine chiaro:
Config → Engine → ScriptAdapter - Due flag di configurazione per disabilitare la funzione nelle distribuzioni che non la vogliono
Ha richiesto più tempo. La PR iniziale è stata #1897, unita il 9 aprile 2025, dopo quattro round di revisione. Ma ora se qualcuno vuole aggiungere “JS inline” o “WASM inline,” c’è un posto molto ovvio dove collegarsi. Abbiamo pagato per una giunzione pulita una volta; possiamo riutilizzarla più e più volte.
Dire no è anche una decisione architetturale. Abbiamo chiuso issue con “questo appartiene a un servizio sidecar, non in OpenClaw” più di una volta. Non è testardaggine da parte nostra; è proteggere il core affinché rimanga comprensibile.
Modelli che usiamo intenzionalmente (e quelli che evitiamo)
C’è un museo di modelli che puoi trascinare in un progetto. La maggior parte di essi non appartiene a OpenClaw.
I modelli su cui ci basiamo effettivamente, ripetutamente:
- Porti e adattatori per integrazioni e IO
- Interni guidati dagli eventi dove sappiamo che aggiungeremo più listener in seguito
- “Configurazione ai bordi” con codice tipizzato nel mezzo
Porti/adattatori si presentano ovunque nel nostro codice ora:
- Storage:
StoragePortcon adattatori Postgres, S3 e filesystem - Messaging:
QueuePortcon adattatori Redis e NATS - Auth:
AuthPortcon adattatori OIDC e token statici
Il vantaggio è semplice: quando qualcuno è arrivato a fine 2025 e voleva supporto per MinIO, è stato un adattatore di ~180 righe invece di “riscrivere ogni sito di chiamata che tocca lo storage.” Questo è il tipo di compromesso che farò volentieri.
Cose che evitiamo principalmente:
- Ereditarietà profonda. Favoriamo dati + funzioni.
- Astrazioni eccessivamente generiche. Se il nome generico è più difficile da comprendere di “PostgresStorage,” l’abbiamo resa troppo astuta.
- Singleton globali. Configurazione e servizi vengono passati esplicitamente la maggior parte delle volte. È leggermente fastidioso, ma il debug è molto più facile.
Un esempio specifico di “troppo astuto” che abbiamo eliminato: il vecchio “UniversalBackendManager” di inizio 2023. Avvolgeva:
- Storage
- Queue
- Auth
- Caching
Tutto dietro un’unica mega-interfaccia. Sembrava bello all’inizio. Poi abbiamo provato a cambiare solo l’implementazione della cache. Questo ha richiesto di modificare il manager, il cablaggio DI e metà dei test. A metà del 2024 l’abbiamo cancellato e sostituito con quattro piccole interfacce. Più boilerplate, vita migliore.
Come registriamo le decisioni (senza annegare nei processi)
I documenti di architettura possono marcire più velocemente del codice che descrivono. Quindi li manteniamo intenzionalmente leggeri. Vedrai tre cose principali nel repo di OpenClaw:
- ADR (Record delle Decisioni Architetturali) in
docs/adr/ - Commenti “Perché” sopra percorsi del codice che sembrano strani
- Descrizioni PR che parlano di compromessi, non solo di “cosa è cambiato”
I nostri ADR sono brevi. Quello per il nuovo scheduler (ADR-0007, datato 2024-11-02) è fondamentalmente:
- Contesto: vecchio scheduler troppo legato all’layer HTTP
- Decisione: estrarre la pianificazione in un modulo di servizio separato
- Alternativa: mantenere com’è, o spostarsi completamente verso una coda esterna
- Conseguenze: leggermente più configurazione, ma migliore isolamento e scalabilità più facile
Sono circa una pagina di testo. Puoi leggerlo in meno di due minuti. Ma quando un nuovo collaboratore entra e chiede “perché lo scheduler è una cosa a sé stante?”, abbiamo una risposta che non è solo “perché a Kai sembrava giusto.”
Stessa cosa per le PR: se stai cambiando qualcosa di architetturale, vogliamo davvero vedere:
- Cosa hai considerato ma non hai fatto
- Cosa sarà più facile o più difficile dopo che questo è stato implementato
- Eventuali scelte “strane” che hai fatto intenzionalmente
Non hai bisogno di un documento di design di 10 pagine. Poche frasi oneste sono sufficienti.
FAQ
Q: Sono un nuovo collaboratore. Come posso evitare di fare una modifica architetturale “cattiva”?
Inizia in piccolo e in modo chiaro. Apri una draft PR o una Discussione su GitHub con la tua idea prima di toccare troppi file. Mostra un piccolo prototipo, spiega i compromessi che vedi e chiedi “dove dovrebbe appartenere questo?” Riceverai feedback più velocemente e non trascorrerai un weekend a costruire qualcosa che comunque suggeriremo di spostare.
Q: È corretto aggiungere una nuova dipendenza o servizio per supportare una funzionalità?
Sì, ma siamo esigenti. Le nuove dipendenze dovrebbero semplificare drasticamente le cose o implementare qualcosa che assolutamente non dovremmo gestire noi (criptografia, parsing serio, ecc.). I nuovi servizi vanno bene se hanno un confine API chiaro e non dipendono segretamente dall’interazione con gli interni di OpenClaw. Se sembra “solo un altro aiuto,” probabilmente appartiene piuttosto ai moduli esistenti.
Q: Devo scrivere un ADR per ogni cambiamento non banale?
No. Gli ADR sono per decisioni che cambiano come le persone pensano al codice: nuovi moduli, nuovi modelli di cross-cutting, deprecazione di vecchi approcci. Se stai riorganizzando il codice interno ma il modello mentale rimane lo stesso, una descrizione dettagliata della PR è sufficiente. In caso di dubbi, chiedi nel canale #dev-architecture e ti diremo se merita un ADR.
🕒 Published: