Sviluppare un software domain first

Molto spesso ci troviamo davanti a software complicati – per non dire complessi – nei quali risiede un gran valore, spesso incastrato nei meandri del framework e quindi nascosto.
Per creare un software efficiente ed efficace occorre individuare due punti chiave iniziali:

  • Ubiquitous Language
  • Rendere indipendente la logica dal framework (disaccoppiamento)

Ubiquitous Language

L’Ubiquitous Language è un set di termini creato attorno al modello di dominio utilizzato dal team per indirizzare e contestualizzare i problemi da risolvere durante l’implementazione. (Wikipedia)

Ogni contesto ha un suo linguaggio consono e inerente, che dovrebbe essere comune al team e divulgato dal cliente che ha richiesto il software da realizzare.
Il business evolve e con esso i sistemi che lo supportano: l’Ubiquitous Language non è un qualcosa di predefinito ed immutabile, ma al contrario va scoperto e rifinito nel tempo.
Questa scoperta del linguaggio corretto nel dominio va affrontata piano piano con chi conosce esattamente il contesto.
Ad esempio, fare event storming o un design system sono alcune delle attività utili a scoprire e allinearsi sul linguaggio corretto. Ma spesso questo genere di attività viene sottovalutato e si pensa subito alla soluzione e all’implementazione di quello che dobbiamo creare.

Ecco perché solitamente ci ritroviamo con implementazioni che non parlano il linguaggio del dominio in cui operano e quindi risultano non efficienti e distanti dalla corretta implementazione. Bisogna ricordarsi che, quando nel software c’è da fare una modifica, implementare una nuova funzionalità o dei fix di alcuni bug, più il codice sarà vicino al linguaggio di dominio più sarà semplice fare queste attività.

Disaccoppiamento della logica dal framework

L’implementazione della logica di dominio, oltre che dover esprimere esattamente il linguaggio corretto e i comportamenti delineati a priori, deve essere anche disaccoppiata dal framework.
Per disaccoppiamento, intendo fare sì che il dominio sia indipendente dal framework e che possa evolvere autonomamente.
Per riuscire ad avere un codice disaccoppiato ci vengono in aiuto anche i principi SOLID.

Faccio un esempio pratico per rendere meglio l’idea:

class Payment {
     public function pay(Request $request): void
     {
         $gateway = new YourtBankGateway();
         $gateway->pay($request->get('amount'));
     }
 }

Questa classe è un esempio di accoppiamento in quanto utilizza all’interno di essa l’implementazione diretta della classe YourBankGateway.
Ora se dovessimo sostituire la classe YourBankGateway con un’altra dovremmo andare a modificare tutte le classi che la implementano come quella sopra e quindi modificare tutte le parti di dominio in molti punti.
Oltre al fatto che stiamo passando direttamente la Request, che è un oggetto di Symfony che rappresenta una richiesta web.
Questo fa sì che se dovessimo usare questo servizio per un comando CLI non potremmo farlo.

Questi concetti li possiamo trovare in queste tecniche e definizioni:

Un esempio di come poter disaccoppiare questa classe potrebbe essere il seguente.

interface GatewayProvider {
     public function pay(Money $amount): void
 }
 class YourBankGateway implements GatewayProvider {
     public function pay(Money $amount): void
     {
         //do stuff..
     }
 }
 class Payment {
     private GatewayProvider $gateway;
     public function __construct(GatewayProvider $gateway)
     {
         $this->gateway = $gateway;
     }
     public function payThroughGateway(Money $amount): void
     {
         $this->gateway->pay($amount));
     }
 }

Nel codice descritto sopra abbiamo creato una piccola interfaccia implementata dalla classe YourBankGateway.
Ora utilizziamo l’interfaccia grazie alla dependency injection per poter fare un’astrazione nella nostra classe Payment.
Quindi se volessimo cambiare la classe YourBankGateway dovremmo semplicemente cambiare cosa iniettiamo nella classe Payment, che deve in ogni caso rispettare l’interfaccia GatewayProvider.

Abbiamo anche tolto l’oggetto Request passando direttamente un oggetto Money. In questo modo sia un servizio web che un comando CLI potrebbero utilizzare questa classe.

Questo tipo di disaccoppiamento ci permette di avere un codice manutenibile, testabile e con una riduzione di debito tecnico.
L’architettura che ci può aiutare a strutturare meglio il nostro codice disaccoppiandolo è l’architettura esagonale.

L’architettura esagonale

L’architettura esagonale è stata inventata e pubblicata da Alistair Cockburn nel 2005, permette di implementare la logica di business dentro l’esagono ed esporla all’esterno tramite le porte e gli adapter.
Alistair ha scelto l’esagono perché secondo lui è la figura che meglio sottolinea l’interazione tra l’interno e l’esterno piuttosto che tra sopra e sotto oppure tra destra e sinistra: per questo motivo il quadrato non è adatto mentre il pentagono, l’eptagono o l’ottagono sono difficili da disegnare.
Ma il numero di lati è abbastanza indifferente, in quanto ognuno rappresenta una porta che può essere implementata da uno o più adapter.

  • Le porte sono dei contratti rappresentati dalle interfacce, quindi senza logica al loro interno.
  • Gli adapter di conseguenza sono le implementazioni delle porte e sono per definizione disaccoppiati.
  • Le porte risiedono nel dominio mentre gli adapter nella parte infrastrutturale.

Quindi secondo me è bene dividere all’interno di un contesto le parti di dominio dalle parti di infrastruttura.
Nel dominio potremmo mettere:

  • modelli
  • use cases
  • interfacce

Mentre nella parte di infrastruttura:

  • framework
  • implementazioni delle interfacce
  • controllers, CLI commands

Logicamente potremmo avere anche il layer Application dove risiederebbero i nostri use case, per esempio.

Il valore del software

Concludendo, il valore aggiunto del software risiede nella logica che implementiamo per risolvere i problemi del dominio. Dove per dominio si intende il contesto in cui una applicazione software opera.
Questa logica va espressa nel linguaggio di dominio che stiamo sviluppando, disaccoppiato dal framework per:

  • esprimere al meglio i comportamenti chiave
  • creare un software manutenibile
  • ridurre il debito tecnico
  • avere un software testabile

Esprimere al meglio i comportamenti chiave

Come abbiamo visto sopra, esprimere al meglio i comportamenti chiave del dominio ci permette di avere un codice che parla il linguaggio più consono e ci favorisce nella sua scrittura o analisi insieme al cliente per una futura implementazione o modifica.
Quindi se il cliente ci parla con termini di dominio (esempio: bilancio consolidato, pagamento automatico…) sta a noi capire immediatamente a quale parte del codice si sta riferendo, senza bisogno di troppe conversioni di lessico. Se parla di bilancio consolidato noi dobbiamo avere lo stesso linguaggio anche nel codice.

Creare un software manutenibile

Per software manutenibile si intende la facilità di apportare modifiche al sistema realizzato (non solo bug fix).
Quindi serve a ridurre il debito tecnico per poter avere un codice semplice e veloce da poter leggere o aggiungere e modificare nuove parti.

Riduzione del debito tecnico

Il debito tecnico comprende tutte le possibili complicazioni che subentrano in un progetto, qualora non venissero adottate azioni adeguate volte a mantenerne bassa la complessità.
Disaccoppiando il dominio dal framework e cercando di seguire i principi SOLID potremmo ridurlo e quindi abbassarne la complessità.

Avere un software più testabile

Come per la riduzione del debito tecnico, disaccoppiando il dominio dal framework e cercando di seguire i principi SOLID possiamo scrivere molti test unitari e avere una suite di test ampia per i comportamenti del dominio.

Questi sono alcuni principi che risolvono i problemi e le esigenze del dominio utilizzando in modo sano la logica di business in ottica di evoluzioni future: il valore dell’applicativo risiede qui, non nel framework o nelle librerie che sono tool a supporto.

Di questo argomento parlerò al SymfonyDay che si terrà il prossimo 20 novembre.