Disaccoppiare logica di business e framework UI nel vibe coding
Nel vibe coding l'IA può mescolare business logic e UI. Ecco come disaccoppiare core e framework: cartelle, contratti e prompt che tengono i layer separati.
Se chiedi a un assistente IA di aggiungere una funzione all’app, la strada che può scegliere è la più breve: un unico file in cui un componente React/Vue interroga il database, valida i dati e disegna i bottoni.
Va bene per un prototipo di mezza giornata. Appena il progetto cresce, diventa difficile da mantenere e può aprire falle (logica di sicurezza mescolata alla UI). Nel pezzo sull’architettura di un progetto vibe-coded abbiamo già fissato cartelle e confini; qui scendo sul punto concreto: core vs UI.
Nei miei test con Cursor, prevenire costa meno che rifattorizzare. Ti mostro come tracciare il confine, quali prompt usare e come leggere il diff per rifiutare codice accoppiato.
Cosa significa separare logica e UI
Un file di logica non dovrebbe sapere come i dati vengono disegnati. Un file UI non dovrebbe sapere come i dati vengono calcolati o salvati.
| Responsabilità | Core (business) | UI (framework) |
|---|---|---|
| Calcoli e regole Sconti, validazioni, permessi | Sì | No |
| Accesso ai dati SQL, API esterne, storage | Sì | No |
| Eventi visivi Click, hover, CSS | No | Sì |
| Stato di presentazione Modale aperta, spinner | No | Sì |
ℹ️ Nota / Prerequisito operativo
Gli esempi usano un frontend moderno (React/Vue/Svelte) e uno strato TypeScript/Node. Il principio vale anche su altri stack: dipendenze verso l’interno, UI che chiama il core, non il contrario.
Fissa queste regole anche nel manifest / SPEC.md, così non le ripeti a ogni prompt.
Perché i modelli creano «god file»
Un god file fa tutto. I modelli possono generarli perché ottimizzano per una risposta che «funziona» in pochi scambi.
Se chiedi «aggiungi la pagina spese», è più semplice (per loro) un unico Spese.tsx con DB + totale + tabella. Separare tipi, servizi e componenti richiede pianificazione: senza vincoli espliciti, spesso non arriva.
Con Cursor, se il file esiste già, l’assistente può continuare ad appendere lì — e il monolite cresce.
Regola pratica: cartelle e import unidirezionali
1. Separazione nel file system
Fin dal giorno zero:
src/
├── core/ # regole, modelli, servizi (niente React/Vue)
└── ui/ # componenti, layout, stato di presentazione
Non buttare tutto in src/components.
2. Import unidirezionali
Le dipendenze puntano verso l’interno: ui → core. Mai core → ui.
🚨 Avviso critico — Import nel core
Se insrc/core/troviimport React,import { ref } from 'vue',windowo hook di stato, il confine è violato. Rifiuta il diff e chiedi di spostare la logica.
L’UI importa funzioni pure dal core; il core non conosce bottoni né CSS.
Esempio: contratto e prompt a due fasi
Obiettivo: aggiungere una spesa.
Prompt da evitare
Crea un componente React per aggiungere una spesa. Campo importo e categoria, salva su Supabase, errore se importo negativo.
Risultato tipico: query + UI nello stesso file.
Prompt migliore (due fasi)
Fase 1 — Core
Scrivi la business logic in src/core/spese.ts.
Crea l'interfaccia Spesa e una funzione pura
validaAggiungiSpesa(importo, categoria) che valida (importo > 0, categoria non vuota)
e restituisce un oggetto sicuro o solleva un errore.
NESSUN import di React/Vue. Nessuna logica visiva. Nessuna query DB in questo file.
Esempio di output atteso:
// src/core/spese.ts
export interface Spesa {
id: string;
importo: number;
categoria: string;
}
export function validaAggiungiSpesa(
importo: number,
categoria: string,
): Omit<Spesa, "id"> {
if (importo <= 0) {
throw new Error("L'importo deve essere maggiore di zero.");
}
if (!categoria) {
throw new Error("La categoria è obbligatoria.");
}
return { importo, categoria };
}
Fase 2 — UI (solo dopo aver verificato il core)
Crea src/ui/AggiungiSpesa.tsx.
Form + stato locale.
Usa ESCLUSIVAMENTE validaAggiungiSpesa da src/core/spese.ts per la validazione
prima del salvataggio (anche se per ora è stub).
Non duplicare le regole di validazione nel componente.
Checklist sul diff (5 allarmi)
Prima di Accept:
- ORM/SQL nell’UI —
sql, client DB, fetch «grezze» di dominio dentro un.tsx/.vue→ spostale incore(o in un layer API dedicato). - Validazione duplicata —
if (valore < 0)nel componente invece della funzione core → rifiuta. - Trasformazioni pesanti nel render —
map/filterdi dominio lunghi nel JSX → estrai formatter/servizio incore. - Framework nel core —
window,document, hook → violazione. - Nomi bugiardi —
UserServiceche restituisce JSX → design sbagliato.
💡 Consiglio pratico — Guarda il path
Nell’intestazione del diff di Cursor vedi il percorso file. Se la logica finisce sottoui/(o viceversa), fermati prima di Accept.
Tre errori da fretta
- «Tanto è un MVP» — nel vibe coding l’MVP diventa produzione in poche ore; disaccoppiare dopo costa di più.
- UI + DB nello stesso prompt — l’IA può scegliere la scorciatoia e ignorare i confini.
- Testare solo a occhio — se la UI «si vede», il core può comunque essere sporco. Separato, puoi testare le funzioni senza browser.
Domande Frequenti (FAQ)
Cosa significa disaccoppiare business logic e UI?
Mettere regole e trasformazioni dati in file indipendenti da come lo schermo li mostra (form, bottoni, CSS). L’UI chiama il core; non reinventa le regole.
Perché Cursor o ChatGPT mettono tutto in un file?
Spesso è la risposta più corta e «completa» in un solo passo. Senza vincoli di cartelle e import, il monolite è il default pratico.
La logica di business può importare React o Vue?
No. Il core resta agnostico: in teoria potresti cambiare UI senza riscrivere le regole.
In che ordine genero core e UI?
Tipi/contratto → funzioni core → verifica → UI che importa il core. Allineato all’ordine moduli della guida architettura.
Serve sempre un’API HTTP in mezzo?
No. In un monolite locale o con Server Actions, l’UI può importare direttamente il core. Il punto è la responsabilità dei file, non obbligatoriamente REST.
E i React Server Components / Server Actions?
Anche lì conviene estrarre regole e accesso dati in moduli core (o server-only) invece di mescolare query e markup «perché lo stack lo permette». Meno caos quando l’IA modifica il pezzo.