Logiche di business
Services
I servizi sono collocati nel namespace \App\{module}\Services.
Ogni service è una classe istanziabile per consentire l'utilizzo del service container del framework e la dependency injection.
L'architettura dei service segue un approccio a più livelli di astrazione:
- Service atomici: implementano logiche molto specifiche e circoscritte
- Service intermedi: orchestrano più service atomici per implementare logiche di medio livello
- Service di alto livello: coordinano service intermedi per implementare flussi complessi
Questo approccio garantisce:
- Riutilizzabilità del codice in qualsiasi parte dell'applicazione
- Facilità di testing automatico
- Basso accoppiamento tra componenti
- Chiara separazione delle responsabilità
I service accettano come input e restituiscono come output solo:
- Tipi primitivi (int, string, bool)
- Classi e DTO per strutture dati complesse
Un service può contenere più metodi correlati purché:
- Siano utilizzati internamente dal service stesso
- Mantengano coerenza con il modulo, l'entità e l'intento del service
- ✅ PropertyAddressService con metodi per formattare indirizzi in modi diversi
- ❌ ProjectService con metodi non correlati che condividono solo modulo ed entità
Per i service di alto livello è necessario:
- ✅ Implementare la validazione degli input per garantire la coerenza dei dati
- 🧪 Sviluppare unit test completi per verificarne il corretto funzionamento nel tempo
Controllers
I Controllers in questa architettura non eseguono direttamente logiche di business ma fungono da orchestratori, limitandosi a contenere esclusivamente le seguenti cose:
- Vaildations (tramite i DTO usati come parametri delle action)
- Authorizations (tramite il modulo di autorizzazione)
- Services
- Events
Nessuna logica di business dev'essere scritta direttamente nei controller.
I controller dovrebbero contenere le action necessarie a svolgere un unico flusso le cui azioni sono sequenziali o interdipendenti, ad eccezione per i controller di risorsa che custodiranno esclusivamente le azioni per il CRUD.
Comando per la creazione di un nuovo Controller
php artisan bb-make:controller {module} {controller}
Per esempio
php artisan bb-make:controller inv invoice
creerà
App/InvModule/Http/Controllers/InvInvoiceController.php
Events and Listeners
Gli eventi (Events) sono un meccanismo fondamentale per notificare in modo trasversale gli avvenimenti all'interno dell'applicazione.
Naming Conventions
Per gli eventi:
- Il nome deve descrivere chiaramente l'evento avvenuto (non le conseguenze)
- Deve seguire il pattern:
{Modulo}{EventoAvvenuto}Event - ✅ Esempio corretto:
McMetricCalculationStoredEvent - ❌ Esempio errato:
GenerateSomethingEvent
Per i Listeners:
- Il nome deve descrivere l'azione che eseguono
- Deve seguire il pattern:
{Modulo}{AzioneDaEseguire}Listener - Esempio:
McGeneratePdfListener
Binding automatico degli Eventi
A partire da Laravel 12, il binding tra eventi e listeners può essere gestito automaticamente dal framework attraverso la type-hint dell'evento nel metodo handle del listener.
Non è più necessario registrare manualmente il binding nel service provider - è sufficiente dichiarare il tipo dell'evento come parametro del metodo handle:
<?php
namespace App\Shared\Listeners;
use App\Shared\Events\SharedFooEvent;
class SharedBarListener
{
public function __construct()
{
//
}
public function handle(SharedFooEvent $event): void
{
//
}
}
Best Practices
- I Listeners devono essere il più possibile atomici
- È preferibile associare più listeners ad un evento piuttosto che avere un listener monolitico
- I Listeners, come i controllers, non devono contenere logica di business ma solo utilizzare i services
Quando Usare gli Eventi
Gli eventi sono indicati quando:
- Servono operazioni collaterali non direttamente legate al flusso principale
- È necessario disaccoppiare azioni secondarie dal flusso sincrono
- Si vuole mantenere il codice modulare e facilmente estendibile
Comando per la creazione di nuovi Event e Listeners
php artisan bb-make:event {module} {event}
php artisan bb-make:listener {module} {listener}
Per esempio
php artisan bb-make:event cont contractSigned
creerà
App/ContModule/Events/ContContractSignedEvent.php
e
php artisan bb-make:listener cont notifyCustomer
creerà
App/ContModule/Listeners/ContNotifyCustomerListener.php
Jobs
I Jobs sono componenti fondamentali per la gestione di operazioni asincrone e dovrebbero essere utilizzati principalmente all'interno dei Listeners.
Questo approccio mantiene gli eventi come sistema principale per la gestione delle operazioni asincrone, offrendo la flessibilità di aggiungere nuovi Jobs a un evento senza modificare il controller originale.
Per i Jobs richiamati direttamente da comandi CLI, non è necessario implementare il pattern Event/Listener.
Come per i controllers, un Job non deve contenere logica di business ma deve limitarsi a orchestrare i services necessari per completare l'operazione.
Quando utilizzare un Job?
- Per operazioni che richiedono tempi di elaborazione significativi
- Per task che possono essere eseguiti in modo asincrono rispetto al flusso principale
- Per operazioni complesse con possibilità di fallimento, dove è necessario implementare meccanismi di retry
- Quando è importante monitorare l'esecuzione dell'operazione (attraverso Laravel Horizon)
Decision Tree: Service vs Event/Listener vs Job
📌 1. È un'operazione non estraibile dal flusso del controller?
│
├── ✅ Sì → Service nel Controller
│
└── ❌ No
│
▼
📌 2. È un'operazione veloce con risultato certo?
│
├── ✅ Sì → Event + Listener
│ (Service nel Listener)
│
└── ❌ No
│
▼
📌 3. È un'operazione lunga o soggetta a errori?
│
└── ✅ Sì → Event + Listener + Job
(Service nel Job)