TL;DR: le tue classi model sono un insieme di proprietà private e metodi getter e setter? Probabilmente stai implementando degli degli Anemic Domain Model. Questo modo di modellare la realtà si scontra con i principi base della programmazione ad oggetti ed è considerato un anti-pattern. In questo articolo ti spiegherò il perché e ti fornirò una valida alternativa.

Sono ormai diversi anni che mi occupo di sviluppo software e una delle cose in cui mi sono imbattuto in tantissimi progetti è stato l’Anemic Domain Model. Stranamente questo modo di strutturare il Model è presente in libri, tutorial e sembra essere considerato come l’unica scelta da molti sviluppatori. Personalmente non mi ha mai convinto e ho sempre cercato un’alternativa. La svolta è arrivata quando ho iniziato ad interessarmi di Domain Driven Design (da qui in avanti DDD) e sono venuto a conoscenza del fatto che all’Anemic Domain Model si contrappone il Rich Domain Model. Ma di cosa si tratta? Cosa si intende per Model? Come si fa a riconoscere un Anemic Domain Model e perché è bene valutare la possibilità di evolverlo verso un Rich Domain Model?

Disclamer

In questo articolo farò riferimento ad alcuni aspetti tattici del DDD ma non parlerò esplicitamente di DDD e dei suo aspetti strategici. L’obbiettivo è quello di utilizzare alcune tecniche alla base del DDD per realizzare un design migliore del Model delle nostre applicazioni.

Domain e Domain Model

Per prima cosa definiamo cosa si intende per Domain e per Domain Model.Il Domain è il business su cui stiamo lavorando (e-commerce, banking, insurance, ecc…). Questo in genere include regole, processi e sistemi esistenti che devono essere integrati come parte della soluzione. Il Domain è quindi il problema che vogliamo risolvere. Il Model è una soluzione al problema. Si tratta di una rappresentazione, spesso semplificata, della realtà. Quando progettiamo e scriviamo del software non facciamo altro che analizzare il Domain per estrarne una semplificazione, ovvero un Model che implementeremo nella nostra applicazione attraverso un insieme di classi e di relazioni tra di esse.

Anemic Domain Model

Un Anemic Domain Model è generalmente composto da molti oggetti, molti dei quali prendono il nome dal Domain, i quali sono interconnessi tra di loro secondo le relazioni presenti nel Domain. La caratteristica di un Anemic Domain Model è che gli oggetti che rappresentano le entità del dominio (entity) non hanno un comportamento (behaviour) ben definito ma sono composti da proprietà private e metodi pubblici che fungono da getter e setter. La logica viene invece implementata in dei servizi appositi che si occupano di eseguire le varie elaborazioni e di modificare come conseguenza lo stato. Quello che segue è un classico esempio di classe appartenente ad un Anemic Domain Model dove la Entity ha solo dati membro privati e metodi getter e setter pubblici:

La classe Customer, come si evince dal codice di esempio, non implementa alcun comportamento, pertanto è necessario implementare uno o più servizi client della classe che si occupano di aggiornarne i dati, come nel seguente esempio:

La classe CustomerService può essere utilizzata in molteplici contesti quali l’aggiornamento dell’indirizzo, il cambio del numero di telefono o per registrare i dati di un nuovo Customer. Quello che potrebbe sembrare un buon design nell’ottica del riutilizzo del codice tuttavia nasconde dei difetti:

  • il metodo saveCustomer può essere richiamato in vari modi che potrebbero facilmente portare a alla creazione di istanze della classe Customer in uno stato non valido;
  • il metodo saveCustomer non fornisce alcuna informazione in merito al quando e perché deve essere richiamato (non è intention-revealing);
  • l’oggetto Customer è semplicemente un contenitore di dati ma non ha alcun comportamento (behavior) quindi non è un vero oggetto nel senso della programmazione orientata agli oggetti;
  • anche se il servizio in questione contenesse della logica di validazione (ad esempio per validare l’indirizzo email o il numero di telefono) non sarebbe possibile garantire che altri servizi mal implementati non possano corrompere il model e creare istanze della classe Customer non valide;

Alla base di questi problemi vi è un cattivo design che non tiene conto delle proprietà basilari della programmazione orientata agli oggetti. Wikipedia definisce la programmazione orientata agli oggetti come segue:

“La programmazione ad oggetti prevede di raggruppare in una zona circoscritta del codice sorgente (chiamata classe), la dichiarazione delle strutture dati e delle procedure che operano su di esse.”

Inoltre una delle proprietà fondamentali della programmazione ad oggetti è l’incapsulamento (encapsulation) che Wikipedia definisce così:

“L’incapsulamento è la proprietà per cui i dati che definiscono lo stato interno di un oggetto e i metodi che ne definiscono la logica sono accessibili ai metodi dell’oggetto stesso, mentre non sono visibili ai client. Per alterare lo stato interno dell’oggetto, è necessario invocarne i metodi pubblici.”

In altre parole per programmare veramente in maniera object oriented non basta dichiarare delle classi qua e la all’interno della nostra code base, bensì è necessario che tali classi rispettino la definizione di cui sopra e che quindi contengano oltre ai dati anche delle procedure che operano su di essi, le quali si occupano di definire la logica e di alterare lo stato interno. Di certo i metodi getter e i setter non sono metodi che possono essere considerati tali poiché non contengono alcuna logica al di fuori della semplice associazione tra una proprietà della classe e un valore passato come argomento. Un Anemic Domain Model, essendo composto prevalentemente da questo tipo di oggetti porta quindi alla stesura di codice per lo più procedurale, mascherato da una parvenza di object orientation.

Nonostante ciò l’Anemic Domain Model è largamente diffuso. Una possibile spiegazione potrebbe essere il fatto che molti programmatori hanno iniziato a scrivere codice quando ancora la programmazione orientata agli oggetti era agli albori e quindi ragionano ancora in termini di strutture, funzioni, tabelle e colonne. Un’altra giustificazione plausibile alla sua diffusione potrebbe essere il fatto che molti sviluppatori non hanno mai sentito parlare di alternative a questo modo di programmare. Altro fattore da tenere in considerazione è il fatto che alcune tecnologie (tra cui framework MVC e ORM) nonché molti libri e tutorial incoraggiano questo stile di programmazione. Vediamo come possiamo porre rimedio a questi problemi evolvendo il nostro design verso un Rich Domain Model.

Rich Domain Model

Rifattorizziamo l’esempio precedente in modo da rendere le nostre classi intention-revealing e più aderenti ai canoni della programmazione orientata agli oggetti. Per prima cosa rinominiamo i metodi della classe Customer prendendo spunto dal concetto di Ubiquitous Language, inoltre applichiamo il Value Object Pattern nella modellazione degli attributi dell’entità Customer:

I metodi setter sono spariti e sono stati rimpiazzati da metodi che hanno dei nomi intention-rivelaing. Grazie al costruttore la classe sarà sempre creata in uno stato valido, con tutte le proprietà fondamentali valorizzate. Inoltre l’utilizzo di value object al posto delle semplici stringhe permette di validare i valori dei vari attributi. Ad esempio consideriamo la classe Telephone:

L’unico modo per instanziare questa classe è passando dal metodo fromString. Tale metodo verifica che il valore passato per argomento sia un numero di telefono valido. In caso contrario solleva una eccezione. In questo modo possiamo essere certi che tutti i numeri di telefono all’interno della nostra applicazione saranno creati, elaborati e salvati in uno stato consistente!La classe Customer può essere testata mediante test unitari, i quali serviranno a verificare che la classe si comporti in maniera coerente con le regole di business tra cui le regole di validazione.

Ci sono sicuramente delle ulteriori migliorie che potremmo apportare a questa classe, tuttavia il risultato di questo primo step di refactoring è già qualcosa di gran lunga migliore della classe anemica che abbiamo considerato in principio. Rifattoriziamo adesso il CustomerService:

Adesso il nostro servizio definisce un metodo relativo ad uno use-case della nostra applicazione. Chiunque leggendo la firma del metodo changeCustomerName è in grado di capire quando e perché richiamarlo. Essendo il nome del metodo derivato dall’Ubiquitous Language, esso sarà condiviso tra i membri del team, programmatori e non, facendo si che vi sia una bassissima probabilità che qualcuno ne fraintenda lo scopo.

Conclusione

Abbiamo rifattorizzato un semplicissimo Anemic Domain Model evolvendone il design verso un Rich Domain Model. Il risultato di questo processo è del codice che non si limita semplicemente a funzionare ma che riflette la terminologia e le regole di business del dominio che intende modellare.

Come possiamo applicare questi concetti ad un’applicazione basata su Symfony e Doctrine? In un prossimo articolo vedremo come, grazie alla potenza e alla flessibilità di questi due strumenti, possiamo sviluppare un Rich Domain Model perfettamente funzionante e integrato con il resto delle funzionalità offerte da questi ottimi framework.