laravel pattern observer

Observer di Laravel ti consente di creare applicazioni disaccoppiate e di facile manutenzione.

Elegante gestione degli eventi, flussi di lavoro automatizzati e architettura scalabile.

In questa guida puoi trovare esempi pratici che ti aiuteranno a padroneggiare il pattern Observer.

Introduzione

Il pattern Observer è uno dei pattern di progettazione comportamentale più potenti disponibili in Laravel, che consente di costruire applicazioni a basso accoppiamento e di facile manutenzione. Questo pattern permette agli observer di monitorare e reagire agli eventi che accadono ad altri oggetti (soggetti) senza creare strette dipendenze tra di essi.

Nell’ecosistema di Laravel, il pattern Observer si manifesta principalmente attraverso gli Eloquent Model Observer e il sistema Event/Listener, fornendo soluzioni eleganti a problematiche trasversali come la registrazione dei log, le notifiche, l’invalidazione della cache e la sincronizzazione dei dati.

Tempo di lettura: 7 minuti

Modello Observer

Il pattern Observer definisce una dipendenza uno-a-molti tra oggetti. Quando lo stato del soggetto cambia, tutti gli observer registrati vengono notificati e aggiornati automaticamente. Questo crea un meccanismo publish-subscribe in cui:

  • Soggetto (osservabile) : l’oggetto osservato
  • Observer: Oggetti che reagiscono ai cambiamenti del soggetto
  • Meccanismo di notifica : il sistema che attiva gli aggiornamenti dell’observer

Perché utilizzare il pattern Observer?

Questo modello risolve diversi problemi critici nello sviluppo di applicazioni:

Separazione delle responsabilità : la logica di business rimane separata dagli effetti collaterali come notifiche, registrazione o analisi. Il modello utente non ha bisogno di conoscere i servizi di posta elettronica, i gestori di cache o i registri di controllo.

Principio aperto/chiuso : puoi aggiungere nuovi observer senza modificare il codice esistente. Hai bisogno di aggiungere notifiche Slack quando gli utenti si registrano? Crea un nuovo observer senza toccare la logica di registrazione.

Manutenibilità : i comportamenti correlati sono raggruppati in classi di observer anziché essere sparsi tra controller e modelli.

Riutilizzabilità : gli observer possono essere riutilizzati in diverse parti dell’applicazione o persino in progetti diversi.

Implementazione del pattern Observer in Laravel

Laravel offre due implementazioni principali del pattern Observer:

1. Observer dei modelli Eloquent

Gli observer dei modelli sono costantemente in ascolto di eventi scatenati dai modelli Eloquent come creatingcreatedupdatingupdateddeletingdeletedsavingsavedrestoringrestoredretrievedreplicating, e forceDeleted.

2. Sistema di event/listener

Il sistema di eventi di Laravel offre un’implementazione più ampia che consente di definire eventi personalizzati e più listener, offrendo maggiore flessibilità rispetto agli observer di un modello Eloquent.

Creazione e utilizzo di observer di modello Eloquent

Creazione di un observer

Con l’artisan possiamo creare un observer, nell’esempio seguente creiamo UserObserver operativo sul modello User:

php artisan make:observer UserObserver --model=User

Qui di seguito la classe observer con i metodi (modificati rispetto il default) che verranno invocati al verificarsi di specifici eventi:

  • creating: invocato prima che un nuovo modello venga salvato nel database;
  • created: invocato dopo che un nuovo modello venga salvato nel database;
  • updating: invocato prima che un modello venga modificato nel database;
  • updated: invocato dopo che un modello venga aggiornato nel database;
  • deleting: invocato prima che un modello venga cancellato nel database;
  • deleted: invocato dopo che un modello venga aggiornato nel database;
  • restored: invocato dopo che “soft-deleted data” vengono ripristinati;
  • force deleted: invocato dopo che “soft-deleted data” vengono definitivamente cancellati;
<?php

namespace App\Observers;

use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class UserObserver
{
    /**
     * Gestione dell'evento "creating" sul modello User.
     * Si verifica prima che un nuovo modello venga salvato nel database.
     */
    public function creating(User $user): void
    {
        // Imposta i valori di default
        if (empty($user->uuid)) {
            $user->uuid = (string) Str::uuid();
        }
        
        // normalizza, portando le lettere al minuscolo, l'email
        $user->email = strtolower($user->email);
    }

    /**
     * Gestione dell'evento "created" sul modello User.
     * Viene invocato dopo che un nuovo modello venga salvato nel database.
     */
    public function created(User $user): void
    {
        // Invio della notifica di benvenuto
        Mail::to($user->email)->send(new WelcomeEmail($user));
        
        // Scrive nel Log la registrazione dell'utente
        Log::info('Registrato il nuovo utente ', [
            'user_id' => $user->id,
            'email' => $user->email
        ]);
        
        // Pulisce la cache
        Cache::tags(['users'])->flush();
    }

    /**
     * Gestione dell'evento "updating" sul modello User.
     */
    public function updating(User $user): void
    {
        // Traccia quali record sono cambiati
        if ($user->isDirty('email')) {
            $user->email_verified_at = null;
        }
    }

    /**
     * Gestione dell'evento "updated" sul modello User.
     */
    public function updated(User $user): void
    {
        // Invalida, cancella una specifica cache
        Cache::forget("user.{$user->id}");
        
        // Sincronizza dei cambiamenti con specifici servizi esterni
        if ($user->wasChanged('email')) {
            // mette in coda il job per aggiornare l'email nel CRM esterno
            UpdateCRMEmail::dispatch($user);
        }
    }

    /**
     * Gestione dell'evento "deleting" sul modello User.
     */
    public function deleting(User $user): void
    {
        // Previene la cancellazione se un utente ha attive le sottoscrizioni
        if ($user->subscriptions()->active()->exists()) {
            throw new \Exception('Non è possibile cancellare l\'utente perchè attive delle sottoscrizioni.');
        }
    }

    /**
     * Gestione dell'evento "deleted" sul modello User.
     */
    public function deleted(User $user): void
    {
        // Pulisce i dati corrispondenti
        $user->posts()->delete();
        $user->comments()->delete();
        
        // Log deletion
        Log::warning('Utente eliminato', ['user_id' => $user->id]);
    }

    /**
     * Gestione dell'evento "restored" sul modello User.
     */
    public function restored(User $user): void
    {
        // Ripristina i soft-deleted data
        $user->posts()->restore();
        
        Log::info('Utente ripristinato', ['user_id' => $user->id]);
    }

    /**
     * Gestione dell'evento "force deleted" sul modello User.
     */
    public function forceDeleted(User $user): void
    {
        // Cancella definitivamente i record corrispondenti
        Storage::deleteDirectory("users/{$user->id}");
        
        Log::alert('Utente cancellato definitivamente ', ['user_id' => $user->id]);
    }
}

Registrazione di un observer

Gli observer devono essere registrati nel provider EventService: App\Providers\EventServiceProvider:

<?php

namespace App\Providers;

use App\Models\User;
use App\Models\Post;
use App\Models\Order;
use App\Observers\UserObserver;
use App\Observers\PostObserver;
use App\Observers\OrderObserver;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    /**
     * Gli observer dell'applicazione.
     *
     * @var array
     */
    protected $observers = [
        User::class => [UserObserver::class],
        Post::class => [PostObserver::class],
        Order::class => [OrderObserver::class],
    ];

    /**
     * Registrazione degli eventi del progetto.
     */
    public function boot(): void
    {
        //
    }
}

In alternativa, è possibile registrarsi secondo le modalità previste dal fornitore di servizi boot:

public function boot(): void
{
    User::observe(UserObserver::class);
}

Modelli di observer avanzati

Esecuzione dell’observer condizionale

Gli observer possono includere una logica condizionale per essere eseguiti solo in determinate circostanze:

public function created(User $user): void
{
    // Invia l'email solo se sei in ambiente di produzione
    if (app()->environment('production')) {
        Mail::to($user->email)->send(new WelcomeEmail($user));
    }
    
    // Solo oer utenti specifici premium
    if ($user->type === 'premium') {
        $this->setupPremiumFeatures($user);
    }
}

Interruzione dell’esecuzione dell’observer

Se restituisci false dai metodi creatingupdatingdeleting, o saving allora puoi interrompere l’operazione in corso, vietandone il completamento. Ad esempio nel creating possiamo annullare la creazione di un nuovo utente con i permessi di amministratore, mentre nella cancellazione possiamo prevenire la cancellazione di un utente amministratore:

public function creating(User $user): bool
{
    // Validazione della regola di business
    if ($this->emailAlreadyExists($user->email)) {
        return false; // Previene la creazione dell'utente
    }
    
    return true;
}

public function deleting(User $user): bool
{
    // Previene la cancellazione di utenti admin
    if ($user->hasRole('admin')) {
        Log::warning('Tentata cancellazione di utente admin ', ['user_id' => $user->id]);
        return false;
    }
    
    return true;
}

Observer basati su code Queue

Per le operazioni che richiedono molto tempo, implementare l’interfaccia ShouldQueue:

<?php

namespace App\Observers;

use App\Models\Order;
use Illuminate\Contracts\Queue\ShouldQueue;

class OrderObserver implements ShouldQueue
{
    /**
     * Il nome della connessione della coda da utilizzare.
     */
    public $connection = 'redis';

    /**
     * Il nome della coda a cui inviare i Jobs.
     */
    public $queue = 'observers';

    /**
     * Gestisci l'evento Created
     */
    public function created(Order $order): void
    {
        // Questo verrà eseguito in coda
        $this->generateInvoicePDF($order);
        $this->notifyWarehouse($order);
        $this->updateInventory($order);
    }
}

Observer con dipendenze

Inietta le dipendenze tramite il costruttore:

<?php

namespace App\Observers;

use App\Models\Post;
use App\Services\SearchIndexer;
use App\Services\CacheManager;
use Illuminate\Support\Facades\Log;

class PostObserver
{
    public function __construct(
        protected SearchIndexer $searchIndexer,
        protected CacheManager $cacheManager
    ) {}

    public function created(Post $post): void
    {
        $this->searchIndexer->index($post);
        $this->cacheManager->invalidatePostCache();
    }

    public function updated(Post $post): void
    {
        $this->searchIndexer->update($post);
        $this->cacheManager->invalidate("post.{$post->id}");
    }

    public function deleted(Post $post): void
    {
        $this->searchIndexer->remove($post->id);
        $this->cacheManager->invalidate("post.{$post->id}");
    }
}

Problemi nell’uso degli Observer e soluzioni

Vediamo ora alcuni problemi che potrebbero nascere dall’uso scorretto degli Observer

Caso 1: Cicli infiniti

Problema : gli observer che aggiornano i modelli possono attivarsi ricorsivamente.

// PERICOLOSO: Questa impostazione genera un loop infinito
public function updated(User $user): void
{
    $user->update(['last_activity' => now()]); // Triggers updated again!
}

Soluzioni :

// Soluzione 1: Utilizzare saveQuietly() per ignorare gli observer
public function updated(User $user): void
{
    $user->saveQuietly(['last_activity' => now()]);
}

// Soluzione 2: Verificare se un attributo specifico è cambiato 
public function updated(User $user): void
{
    if (!$user->wasChanged('last_activity')) {
        $user->saveQuietly(['last_activity' => now()]);
    }
}

// Soluzione 3: Utilizzare withoutEvents() per operazioni in blocco 
User::withoutEvents(function () use ($user) {
    $user->update(['last_activity' => now()]);
});

Caso 2: Ordine di esecuzione dell’observer

Problema : più observer sullo stesso modello possono avere dipendenze dall’ordine di esecuzione.

Soluzione : utilizzare un singolo observer con metodi ordinati oppure utilizzare le priorità degli eventi:

// Crea un observer coordinato 
class UserObserver
{
    public function created(User $user): void
    {
        // Passaggio 1: Operazioni critiche per prime 
        $this->assignDefaultRole($user);
        
        // Passaggio 2: Genera dati derivati 
        $this->createUserProfile($user);
        
        // Passaggio 3: Notifiche non critiche per ultime 
        $this->sendWelcomeEmail($user);
    }
}

Caso 3: Complicazioni nei test

Problema : Gli observer vengono eseguiti durante i test, causando effetti collaterali imprevisti.

Soluzione : Disabilitare gli observer nei test o utilizzare il mocking:

// Nel tuo test 
use Illuminate\Support\Facades\Event;

public function test_user_creation(): void
{
    // Disabilita tutti gli observer
    Event::fake();
    
    $user = User::factory()->create();
    
    // Asserzioni senza effetti collaterali degli observer 
    $this->assertDatabaseHas('users', ['email' => $user->email]);
}

// Disabilita eventi specifici del modello 
public function test_without_specific_events(): void
{
    User::withoutEvents(function () {
        $user = User::factory()->create();
        // Test senza attivare gli observer
    });
}

// Simula selettivamente gli eventi 
public function test_with_some_events(): void
{
    Event::fake([
        UserCreated::class, // Solo questo evento viene simulato 
    ]);
    
    $user = User::factory()->create();
    // Gli altri observer vengono comunque eseguiti
}

Caso 4: Problemi di prestazioni

Problema : gli observer che vengono eseguiti in modo sincrono rallentano le richieste.

Soluzioni :

// Soluzione 1: Implementare ShouldQueue
class UserObserver implements ShouldQueue
{
    public function created(User $user): void
    {
        // Operazioni pesanti spostate in coda 
        $this->processUserAnalytics($user);
        $this->syncToExternalServices($user);
    }
}

// Soluzione 2: Inviare job invece di eseguirli direttamente 
public function created(User $user): void
{
    // Le operazioni rapide avvengono immediatamente 
    Cache::forget('users.count');
    
    // Operazioni pesanti inviate in coda 
    ProcessNewUser::dispatch($user);
    SendWelcomeEmail::dispatch($user);
    SyncToAnalytics::dispatch($user);
}

// Soluzione 3: Utilizzare le transazioni del database in modo efficiente 
public function created(User $user): void
{
    DB::transaction(function () use ($user) {
        // Raggruppa  le operazioni del database correlate
        $user->profile()->create([...]);
        $user->settings()->create([...]);
    });
}

Caso 5: Gestione delle operazioni di massa

Problema : Gli observer si attivano per ogni modello nelle operazioni in blocco, causando problemi di prestazioni.

Soluzioni :

// Soluzione 1: Disabilita gli eventi per le operazioni bulk
User::withoutEvents(function () {
    User::where('status', 'pending')->update(['status' => 'active']);
});

// Soluzione 2: Utilizza query raw che bypassano Eloquent
DB::table('users')
    ->where('status', 'pending')
    ->update(['status' => 'active']);

// Soluzione 3: Gestisci le operazioni bulk nella classe
class UserObserver
{
    protected static bool $bulkOperation = false;
    
    public static function startBulkOperation(): void
    {
        static::$bulkOperation = true;
    }
    
    public static function endBulkOperation(): void
    {
        static::$bulkOperation = false;
        // Esegui la pulizia dopo l'operazione bulk
        Cache::tags(['users'])->flush();
    }
    
    public function updated(User $user): void
    {
        if (static::$bulkOperation) {
            return; // Salta l'observer durante l'operazione bulk
        }
        
        // Logica di aggiornamento normale 
        Cache::forget("user.{$user->id}");
    }
}

// Utilizzo
UserObserver::startBulkOperation();
User::where('active', true)->update(['last_checked' => now()]);
UserObserver::endBulkOperation();

Caso 6: Annullamento delle transazioni

Problema : gli observer vengono eseguiti anche se la transazione viene annullata.

Soluzione : utilizzare transazioni di database con hook after commit:

public function created(User $user): void
{
    // Attendi fino al commit della transazione
    DB::afterCommit(function () use ($user) {
        Mail::to($user->email)->send(new WelcomeEmail($user));
        Cache::tags(['users'])->flush();
    });
}

// Oppure in Laravel 10+, usa l'attributo AfterCommit 
use Illuminate\Queue\Attributes\AfterCommit;

#[AfterCommit]
class UserObserver
{
    public function created(User $user): void
    {
        // Attendi automaticamente il commit della transazione 
        Mail::to($user->email)->send(new WelcomeEmail($user));
    }
}

migliori pratiche

1. Mantenere gli observer snelli

Ciascun observer dovrà assumersi una singola responsabilità:

// BENE: CSemplifica gli Observer
class UserNotificationObserver { /* gestisci notifiche */ }
class UserCacheObserver { /* gestisce la cache */ }
class UserAuditObserver { /* gestisce i log */ }

// DA EVITARE: observer onnipotente che fa tutto 
class UserObserver
{
    public function created(User $user): void
    {
        // Troppe responsabilità
        $this->sendEmail($user);
        $this->clearCache($user);
        $this->logAudit($user);
        $this->syncCRM($user);
        $this->updateAnalytics($user);
    }
}

2. Utilizzare i suggerimenti di tipo

Per chiarezza e supporto IDE, inserisci sempre suggerimenti di tipo per i parametri del modello:

public function created(User $user): void // GOOD
{
    //
}

public function created($user) // AVOID
{
    //
}

3. Tipi di rendimento con leva finanziaria

Utilizzare i tipi di ritorno per rendere esplicito il comportamento di arresto:

public function creating(User $user): bool
{
    if ($this->isDuplicate($user)) {
        return false; // Explicit halt
    }
    
    return true;
}

4. Documentare il comportamento dell’observer

Utilizza i docblock per spiegare i comportamenti non ovvi:

/** 
* Gestisce l'evento "Created" dell'utente.
*
* Questo osservatore:
* - Invia l'email di benvenuto (in coda)
* - Crea le impostazioni utente predefinite
* - Invalida la cache del conteggio utenti
* - Sincronizza con il CRM (in coda, può riprovare fino a 3 volte)
*
* @param User $user
* @return void
*/
public function created ( User $user ): void
{
//
}

5. Gestire i fallimenti con eleganza

Non permettere che i guasti degli observer compromettano l’operazione principale:

public  function  created ( User $user ): void
{
try {
Mail :: to ( $user ->email)-> send ( new WelcomeEmail ( $user ));
} catch (\ Exception $e ) {
Log :: error ( 'Impossibile inviare l'email di benvenuto' , [
'user_id' => $user ->id,
'error' => $e -> getMessage (),
]);
// Continua l'esecuzione, non generare un'eccezione
}

// Le altre operazioni continuano anche se l'email fallisce
Cache :: tags ([ 'users' ])-> flush ();
}

6. Utilizzare i flag di funzionalità

Consenti di attivare/disattivare gli observer senza modifiche al codice:

public  function  created ( User $user ): void
{
if ( config ( 'features.user_welcome_email' )) {
Mail :: to ( $user ->email)-> send ( new WelcomeEmail ( $user ));
}

if ( config ( 'features.crm_sync' )) {
SyncToCRM:: dispatch ( $user );
}
}

Test degli observer

Test unitari

Testare i metodi di osservazione in modo isolato:

<?php 

namespace Tests \ Unit \ Observers ;

use Tests \ TestCase ;
use App \ Models \ User ;
use App \ Observers \ UserObserver ;
use Illuminate \ Support \ Facades \ Mail ;
use App \ Mail \ WelcomeEmail ;

class UserObserverTest extends TestCase
{
public function test_sets_uuid_on_creating ( ): void
{
$observer = new UserObserver ();
$user = new User ([ 'email' => 'test@example.com' ]);

$observer -> creating ( $user );

$this -> assertNotNull ( $user ->uuid);
}

public function test_sends_welcome_email_on_created ( ): void
{
Mail :: fake ();

$observer = new UserObserver ();
$user = User :: factory ()-> create ();

$observer -> created ( $user );

Mail :: assertSent ( WelcomeEmail :: class , function ( $mail ) use ($ user ) {
return $ mail -> hasTo ($ user -> email );
});
}

public function test_prevents_deletion_with_active_subscriptions ( ): void
{
$user = User :: factory ()-> create ();
$user -> subscriptions ()-> create ([ 'status' => 'active' ]);

$observer =nuovo UserObserver ();

$this- > expectException ( \Exception :: class );
$observer- > deleting ( $user );
}
}

Test di integrazione

Observer di prova nel contesto delle operazioni del modello:

public  function  test_user_creation_triggers_observer ( ): void
{
Mail :: fake ();
Cache :: spy ();

$user = User :: create ([
'name' => 'John Doe' ,
'email' => 'john@example.com' ,
'password' => bcrypt ( 'password' ),
]);

// Assert observer side effects
$this -> assertNotNull ( $user -> fresh ()->uuid);
Mail :: assertSent ( WelcomeEmail :: class );
Cache :: shouldHaveReceived ( 'tags' )-> with ([ 'users' ]);
}

Test senza observer

Testare il comportamento del modello senza effetti collaterali dovuti all’observer:

public  function  test_user_attributes_without_observers ( ): void
{
Event :: fake ();

$user = User :: factory ()-> create ([
'email' => 'TEST@EXAMPLE.COM' ,
]);

// L'email non verrà convertita in minuscolo perché l'observer non è stato eseguito
$this -> assertEquals ( 'TEST@EXAMPLE.COM' , $user ->email);
}

Observer vs. Event/Listener

Comprendere quando utilizzare ciascun approccio:

Utilizzare gli observer quando:

  • Stai reagendo agli eventi del ciclo di vita del modello Eloquent
  • La logica è strettamente legata a un modello specifico
  • Hai bisogno di una gestione degli eventi semplice e diretta
  • Si desidera mantenere insieme i comportamenti del modello correlati

Utilizzare l’event/listener quando:

  • Hai bisogno di eventi personalizzati che vadano oltre il ciclo di vita del modello.
  • Più modelli dovrebbero attivare lo stesso listener
  • È necessaria una logica complessa per la gestione degli eventi.
  • Desideri la massima flessibilità e testabilità.
  • Stai costruendo un sistema con molti componenti disaccoppiati

Esempio di confronto:

// Approccio Observer (strettamente accoppiato al modello User) 
class UserObserver
{
public function created ( User $user ): void
{
event ( new UserRegistered ( $user ));
}
}

// Approccio Event/Listener (flessibile, riutilizzabile)
class UserRegistered
{
public function __construct ( public User $user ) {}
}

class SendWelcomeEmail
{
public function handle ( UserRegistered $event ): void
{
Mail :: to ( $event ->user)-> send ( new WelcomeEmail ( $event ->user));
}
}

class UpdateAnalytics
{
public function handle ( UserRegistered $event ): void
{
Analytics :: track ( 'user.registered' , $event ->user);
}
}

Uso dell’Observer per ottimizzare le prestazioni

1. Operazioni batch

Elabora più record in modo efficiente:

public function created(Order $order): void
{
    // Invece di elaborare ogni ordine singolarmente
    static $orders = [];
    $orders[] = $order;
    
    // Elabora gli ordini in batch, cioè a gruppi di 100
    if (count($orders) >= 100) {
        $this->processBatch($orders);
        $orders = [];
    }
}

2. Prevenzione del caricamento differito (Lazy Loading)

Evitare query N+1 negli Observer:

public function created(Order $order): void
{
    // EVITARE: Ciò causa N+1 se si elaborano più ordini
    $customer = $order->customer; // Lazy loads
    
    // OTTIMALE: Caricamento immediato nella query
    // Order::with('customer')->create([...]);
}

3. Rigenerazione della cache

Con l’Observer puoi rigenerare la cache nel modo seguente:

public function updated(Product $product): void
{
    // Clear cache immediately
    Cache::forget("product.{$product->id}");
    
    // Warm cache in background
    WarmProductCache::dispatch($product);
}

Conclusione

Il pattern Observer in Laravel fornisce un potente meccanismo per la creazione di applicazioni disaccoppiate e di facile manutenzione.

Implementando correttamente gli observer, è possibile:

  • Mantieni i tuoi modelli puliti e focalizzati
  • Gestire con eleganza le problematiche trasversali
  • Creare applicazioni scalabili e di facile manutenzione
  • Reagire ai cambiamenti senza un accoppiamento stretto

Ricorda questi principi fondamentali:

  1. Mantenere gli Observer concentrati su singole responsabilità.
  2. Gestire gli errori con eleganza per evitare problemi a cascata
  3. Utilizzare le code per le operazioni che richiedono molto tempo.
  4. Eseguire test approfonditi per garantire che gli observer si comportino correttamente.
  5. Documentare il comportamento in modo che gli altri sviluppatori possano comprendere il flusso
  6. Monitorare le prestazioni e ottimizzarle secondo necessità.
  7. Evita i cicli infiniti utilizzando saveQuietly() o controllando gli attributi modificati

Il pattern Observer non è una soluzione miracolosa. Se implementati con attenzione, gli observer diventano uno strumento prezioso nel vostro kit di sviluppo Laravel.

Di Ercole Palmeri

Digital Innovator, Executive MBA, InsurTech Specialist, tecnologie innovative: clean code, intelligenza artificiale, analisi big data, blockchain e machine learning. .