Macchine a stati finiti (FSM) e il plugin acts_as_state_machine
Una macchina a stati finiti (FSM) è un sistema dinamico costituito da un insieme finito di stati e da una funzione di trasferimento che ne specifica il comportamento, ovvero definisce le regole che determinano i passaggi della macchina da uno stato all’altro.
La macchina può essere rappresentata tramite un grafo diretto i cui nodi rappresentano gli stati e gli archi rappresentano le transizioni di stato delle macchina.

Sono molteplici i problemi che possono essere modellati con una FSM e non poteva certo mancare un plugin Rails che ne rendesse agile la codifica: si tratta di acts_as_state_machine, una manciata di righe di codice sicuramente migliorabili ma che tutto sommato assolvono bene al loro compito. Una volta installato il plugin, occorrerà soltanto aggiungere un campo state di tipo string all’oggetto cui vogliamo assegnare le caratteristiche di FSM.
Facciamo un semplice esempio: vogliamo creare un’applicazione web accessibile attraverso iscrizioni a pagamento. Dopo la registrazione, l’iscrizione sarà attiva e, ogni volta che riceveremo un pagamento, dovrà essere generata la fattura commerciale per il cliente. In caso di mancato pagamento l’iscrizione verrà sospesa mentre, se il cliente deciderà di cancellare la sua registrazione, l’iscrizione verrà eliminata. Ad ogni passaggio di stato dovrà essere inviata una notifica e-mail al cliente.
La prima cosa da fare è modellare gli stati e gli eventi che determinano le transizioni di stato: niente di meglio che usare la vecchia accoppiata carta/penna, creando un diagramma simile a quello della figura precedente.
Utilzzando il plugin risulta estremamente semplice tradurre il diagramma nel seguente modello Rails:
1 2 3 acts_as_state_machine :initial => :active 4 5 state :active 6 state :suspended 7 8 event :payment do 9 10 transitions :from => :active, 11 :to => :active, 12 :guard => :get_payment 13 14 transitions :from => :suspended, 15 :to => :active, 16 :guard => :notify_reactivation 17 end 18 19 event :payment_failure do 20 21 transitions :from => :active, 22 :to => :suspended, 23 :guard => :notify_suspension 24 25 transitions :from => :suspended, 26 :to => :suspended, 27 :guard => :notify_new_payment_failure 28 end 29 30 private 31 32 # make payment invoice 33 # send payment email 34 end 35 36 37 # send reactivation email 38 end 39 40 41 # send suspension email 42 end 43 44 45 # send alert for new payment failure 46 end 47 end
Nell’esempio, gli eventi possono essere chiamati attraverso i metodi:
- @subscription.payment!
- @subscription.payment_failure!
Gli eventi signup e cancel del diagramma coincidono invece con l’effettiva creazione e distruzione della subscription e non con delle transizioni di stato.
Il modello è molto semplificato rispetto ad un caso reale, dove dovrebbero essere tenuti in considerazione altri stati ed eventi come la modifica dell’iscrizione o la sospensione definitiva in seguito ad un numero massimo di pagamenti falliti. Quello che conta è l’approccio con cui viene affrontato il problema e la “robustezza” della soluzione che ne scaturisce: un sistema ben modellato è un’ottima base per la scrittura di codice elegante e flessibile e le macchine a stati sono, a mio avviso, uno degli esempi più calzanti di come una buona progettazione porti vantaggi concreti, attribuendo un significato tangibile a quanto raccomandato in tutti i migliori testi di programmazione.
