Introduzione
Quando ho iniziato a lavorare come freelance, il mio approccio a Laravel era semplice: Controller → Model → View, e via. Funzionava. Finché non ho preso in carico un progetto di gestione ordini per un e-commerce B2B e mi sono trovato con controller da 400 righe e logica di business sparsa ovunque.
Quel progetto mi ha convinto a provare Clean Architecture. Questo articolo è il resoconto onesto di come è andata.
Il problema di partenza
Il cliente aveva un gestionale custom in Laravel 9. Ogni volta che chiedeva una modifica — anche piccola, tipo cambiare le regole di sconto — dovevo toccare tre file diversi, rischiare di rompere qualcosa e fare il deploy con l'ansia.
Il sintomo classico: logica di business dentro i controller.
// OrderController.php — quello che NON si fa
public function store(Request $request)
{
$total = 0;
foreach ($request->items as $item) {
$product = Product::find($item['id']);
$total += $product->price * $item['qty'];
}
if ($total > 500) {
$total *= 0.9; // sconto 10%
}
Order::create([...]);
// email, log, notifiche... tutto qui dentro
}
La struttura che ho adottato
Ho separato il progetto in tre layer principali:
app/
├── Domain/ # Entità e regole di business pure
├── Application/ # Use case (orchestrano il dominio)
└── Infrastructure/ # Laravel, DB, email, API esterne
Il controller diventa sottilissimo — il suo unico compito è ricevere la request e chiamare il use case giusto:
// OrderController.php — dopo il refactor
public function store(StoreOrderRequest $request)
{
$order = $this->createOrder->execute(
CreateOrderDTO::fromRequest($request)
);
return OrderResource::make($order);
}
Tutta la logica degli sconti vive nel dominio, testabile in isolamento, senza toccare Laravel.
Cosa ha funzionato davvero
I test sono diventati semplici. Testare un use case non richiede HTTP, database o factory. Passi i dati, verifichi l'output. Fine.
Le modifiche sono localizzate. Quando il cliente ha cambiato la logica degli sconti (tre volte in due mesi), ho toccato un solo file nel dominio. Zero regressioni.
Un vantaggio inaspettato: i pannelli di amministrazione
Separare la business logic in use case dedicati ha reso banale un'altra cosa che spesso si sottovaluta: costruire interfacce di amministrazione per i parametri configurabili.
Nel progetto dell'e-commerce, la percentuale di sconto era hardcodata. Ogni modifica richiedeva un deploy. Avendo la logica isolata in una classe CalculateDiscount, è bastato aggiungere una tabella settings e iniettare il valore da lì:
// Domain/Services/CalculateDiscount.php
class CalculateDiscount
{
public function __construct(
private DiscountSettingsRepository $settings
) {}
public function execute(float $total): float
{
$threshold = $this->settings->get('discount_threshold');
$rate = $this->settings->get('discount_rate');
return $total >= $threshold
? $total * (1 - $rate)
: $total;
}
}
A quel punto costruire un pannello Laravel Nova (o anche una semplice Blade view) per far gestire quei valori al cliente è stato questione di un pomeriggio. Zero deploy per cambiare uno sconto.
Quando un'attività diventa di routine per il cliente, trasformarla in configurazione self-service è uno dei modi più rapidi per aumentare il valore percepito del tuo lavoro — senza aggiungere complessità al codice.
I trade-off onesti
Non è gratis. In un progetto piccolo o con deadline stretta, questa struttura è overkill. Ho più cartelle, più file, più interfacce. Per un CRUD da tre endpoint non ha senso.
La mia regola pratica: se il cliente prevede di evolvere la business logic nel tempo, vale l'investimento. Altrimenti, Laravel MVC classico è la scelta giusta.
Conclusione
Il vero valore di questa architettura per un freelance non è tecnico, è commerciale. Quando un cliente torna con una modifica, la faccio in un'ora invece che in una giornata. Questo mi permette di avere prezzi competitivi sui cambi e margini migliori sul mio tempo.
Se stai valutando di adottarla, parti da un solo use case nel tuo prossimo progetto. Non serve riscrivere tutto — basta iniziare.