Questa volta parliamo di Claude Code e database. Il contesto del nostro esempio è un'applicazione Laravel in produzione da circa tre anni, con un database PostgreSQL che nel tempo ha accumulato una trentina di tabelle, relazioni non sempre ortogonali, e qualche decisione architetturale presa in fretta che nessuno ha mai avuto il tempo di rivedere.
Il task assegnato a Claude Code
Il progetto gestisce un sistema di ticketing interno per una media impresa: utenti, ticket, commenti, allegati, categorie, assegnazioni. Niente di straordinario nella struttura, ma abbastanza dati reali da rendere ogni migration non banale.
I requisiti nuovi arrivano dal team di prodotto in tre punti distinti. Il primo è l'aggiunta di un sistema di tag polimorfici che possa essere applicato non solo ai ticket ma anche ai commenti e agli allegati — senza replicare tre tabelle pivot separate. Il secondo è la revisione di un indice composito sulla tabella ticket_assignments che nelle ultime settimane ha iniziato a mostrare query lente su grandi volumi, con la necessità di aggiungere una colonna assigned_at come parte dell'indice. Il terzo è l'introduzione di una colonna JSONB sulla tabella tickets per memorizzare metadati arbitrari forniti da integrazioni esterne, con un default non triviale e un constraint di validazione sulla struttura del JSON.
Tre modifiche di complessità crescente, ognuna con le sue insidie specifiche. Il task assegnato a Claude Code era preciso
leggi lo schema esistente, comprendi i requisiti, scrivi le migration Laravel con il relativo SQL, e spiega le decisioni prese
Il vincolo era non alterare i dati esistenti in nessuna delle migration. Solo modifiche strutturali.
La relazione polimorfica per i tag
Le relazioni polimorfiche in Laravel seguono una convenzione consolidata: una tabella pivot con due colonne speciali, taggable_id e taggable_type, dove la seconda contiene il nome della classe Eloquent del modello associato. È un pattern ben documentato, presente in migliaia di progetti open source, e Claude Code lo conosce in modo molto accurato.
La migration generata per i tag è stata questa:
// database/migrations/2024_01_15_000001_create_tags_and_taggables_tables.php
public function up(): void
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name', 100)->unique();
$table->string('slug', 110)->unique();
$table->string('color', 7)->default('#6B7280');
$table->timestamps();
});
Schema::create('taggables', function (Blueprint $table) {
$table->id();
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->morphs('taggable'); // genera taggable_id e taggable_type
$table->timestamps();
$table->unique(['tag_id', 'taggable_id', 'taggable_type']);
});
}
public function down(): void
{
Schema::dropIfExists('taggables');
Schema::dropIfExists('tags');
}
La struttura è corretta e, per certi versi, più completa di quello che ci si aspetterebbe da una generazione automatica. L'agente ha aggiunto autonomamente la colonna slug, non richiesta esplicitamente nei requisiti, ragionando che un sistema di tag esposto via API avrebbe probabilmente bisogno di identificatori URL-friendly oltre al nome leggibile. Ha aggiunto un campo color con un default ragionevole nel formato hex. Ha incluso il constraint di unicità sulla tripla (tag_id, taggable_id, taggable_type), che è esattamente il constraint necessario per evitare tag duplicati sullo stesso oggetto e che nei requisiti non era menzionato esplicitamente.
Questa è la parte in cui lavorare con un agente che conosce bene i pattern del dominio produce un valore che va oltre la semplice trascrizione dei requisiti. Claude Code ha portato nella migration la conoscenza di cosa serve tipicamente in un sistema di tag, non solo di cosa era stato chiesto.
Il problema che non si vede in prima lettura
C'è però un aspetto di questa migration che emerge solo se si conosce bene come PostgreSQL gestisce gli indici sulle colonne che partecipano a una relazione polimorfica. La chiamata $table->morphs('taggable') in Laravel genera automaticamente un indice composito su (taggable_type, taggable_id). Su un database con volumi moderati questo va benissimo.
Su un database con decine di migliaia di record nella tabella taggables e query che filtrano spesso per tipo prima che per ID, quell'indice nell'ordine (taggable_type, taggable_id) è quello corretto. Ma se le query più frequenti filtrassero per ID indipendentemente dal tipo, uno scenario meno comune ma non impossibile, l'ordine delle colonne nell'indice sarebbe subottimale.
Claude Code non ha sollevato questa considerazione. Non ha chiesto quali sarebbero stati i pattern di accesso alla tabella taggables. Ha scelto il default di Laravel senza menzionare che era una scelta, non un'ovvietà. Su un sistema nuovo questo è accettabile e si può sempre aggiungere un indice in seguito. Su un sistema in produzione con query esistenti che potrebbero dipendere da un certo comportamento dell'ottimizzatore, quella scelta silenziosa merita di essere resa esplicita.
Se vuoi aggiornamenti su Assegniamo il nostro task a Claude Code inserisci la tua email nel box qui sotto: