papo we(b)log

software engineering slave!
posts - 29, comments - 49, trackbacks - 26

del pattern Command, validazione e altre amenità

Finalmente trovo il tempo e l'occasione per iniziare a raccontare alcune delle soluzioni che ho utilizzato nei miei progetti reali. Mi piacerebbe pensare ad una serie di post con questo "tema portante", ma per ora mi limito a trovare le energie per focalizzarmi su un solo argomento specifico: la validazione delle regole di "univocità".

Questo è il racconto delle ultime giornate di lavoro, caratterizzate da un continuo refactoring: tanta "irruenza" e  "coraggio" nel modificare quanto appena raggiunto, se in mente c'è un'idea nuova e affascinante. Tutto questo reso possibile soltanto grazie ad una serie di test case che in ogni momento mi raccontano dove ho introdotto anomalie o mi confermano che tutto prosegue bene!

Ecco lo scenario: operazione di dominio per l'assegnamento ad un account di parametri univoci, come nome o mobile. La struttura che ho realizzato è composta così:

  • layer dominio (core)
  • layer business (application)
  • layer presentation (ui)

Non introduco troppo dettaglio, visto che in questo post non voglio parlare della mia architettura (argomento solo rimandato!) ma di una soluzione di object design.

Ho introdotto due livelli di validazione:

  • a livello di oggetto del dominio (regole logiche generiche, poco stringenti, come not null, stringhe senza spazi e l'ambito di univocità), valide cioè per il dominio applicativo in questione
  • a livello di controllori dell'applicazione (regole dettagliate, come espressioni regolari sulle stringhe, etc.), relative quindi ad una singola applicazione (come un intero sito web)

La suddivisione che ho scelto è stata dettata dalla necessità di riusare gli oggetti del dominio in più contesti, mentre la realizzazione di un caso d'uso ha le sue regole specifiche che ho accentrato nei controllori dello strato business.

CHE NOIAAA!! Sì sì lo so, sembra di leggere un manuale di ingegneria del software! Pare tutto così ovvio! Certo, ma mi interessava spiegare "il perchè" ho messo regole di validazione nel dominio, altrimenti più che stimolarvi a discutere con me della soluzione di cui ora voglio parlare, probabilmente avrei fatto nascere solo dubbi sul fatto che queste regole fossero proprio lì! No, per ora non mi occupo di questo!

[tante parole e ancora non ho detto nulla!]

Allora, eccomi al sodo!

Voglio descrivere una soluzione che ho utilizzato per questo problema: partire da una classe base astratta per gli account, ed estenderla per strutturare le figure specifiche del dominio, come i vari gradi di amministratori, i clienti, e così via, implementando in modo particolare alcune regole, senza però dover fornire alle singole classi metodi "setter" sulle proprietà comuni. Ad esempio, appunto far dichiarare alle singole classi figlie "a che livello" verificare l'univocità di un attributo.

La prima soluzione che avevo adottato consisteva in un metodo set non-virtuale sulla classe base, per la validazione delle regole condivise ma anche delle regole specifiche della classe figlia. Questo possibile poichè la logica di validazione specifica era concentrata in un metodo virtuale (o astratto), il metodo hook ("gancio") della GangOfFour, ridefinito da ogni figlia. In sostanza, una cosa così:

//Account.cs
protected abstract IUniqueChecker UsernameChecker { get;}
public string Username
{
    
set
    
{
        ...
        
        
//username univoco. non specifico "a che livello"
        
if (UsernameChecker.UsernameExist(value))
        {
            
throw new ArgumentException(...);
        }
    }
}

//User.cs, estende Account
protected override IUniqueChecker UsernameChecker
{
    
get
    
{
        
// ritorna l'azienda corrente. in questo modo ho ottenuto
        // il controllo dell'univocità username a "livello di azienda"
    
}
}

La soluzione cioè prevede un'interfaccia IUniqueChecker per la dichiarazione dei metodi di verifica della univocità:

public interface IUniqueChecker
{
    
bool UsernameExist(string username);
    
bool MobileExist(string mobile);
}

L'idea di fondo è buona, e infatti è rimasta invariata nella sostanza. Quello che non mi piaceva era dover implementare negli oggetti "ambito" di univocità (le aziende, i gruppi, i servizi, cioè tutti gli oggetti del dominio che "aggregano" utenti) una serie di metodi, quelli dichiarati in IUniqueChecker, perchè mi pareva che andassero ad inquinare la loro interfaccia. Discutibile...

Però così ho colto l'occasione per applicare un bel pattern: il pattern Command . L'anima di questa soluzione è quella di raggruppare tutte le operazioni che devono essere svolte da un comando, e rappresentarle con un oggetto, invece che con un metodo. Per eseguire poi il comando, si invoca un metodo specifico sull'oggetto Command, ad esempio Execute().

Quello che ho fatto perciò è stato convertire ogni metodo dell'interfaccia di validazione in una classe Command. Ad esempio: UniqueAccountUsername per verificare l'univocità della username di un account, UniqueAccountMobile invece per il mobile. Entrambe le classi implementano l'interfaccia IUniqueCommand, che definisce un solo metodo, Execute(), per avviare l'operazione di convalida.

public interface IUniqueCommand
{
    
bool Execute(string name); //verifica unicità di name
}

public class UniqueAccountUsername : IUniqueCommand
{
    
private IList _list; //ambito in cui operare la verifica

    
public UniqueAccountUsername(IList list) { ... }

    
public bool Execute(string username)
    {

        
...
        
return (!found); //univoco se non trovato
    }
}

public class UniqueAccountMobile : IUniqueCommand
{
    ...
    
public bool Execute(string mobile) { ... }
}

Quello che manca per chiudere il ciclo è vedere come creare e usare un oggetto Command. Ecco ad esempio come risulta la validazione nella classe Account (vedi l'esempio a metà post):

Ovviamente non ho resistito! Ho dovuto farlo! Anch'io ho la mia Validation Library! Ed eccola in tutto il suo (mediocre) splendore!

//regola, semplice o composta.
//applicabile ad un solo elemento
IConstraint rule = Is.AtLeastLong(1)
                    .Or(Is.Null())
                    .And(HasNot.String(" "))
                    .And(Is.UniqueIn(aUniqueChecker));

//validazione
if (!rule.Eval(aString))
{
    ...
}

//contesto di validazione.
//convalido regole su elementi diversi
IValidationContext context;
context = Verify.That(aProperty, IsNot.Null(), "property is null")
            .And(anotherProperty, rule, "broken rule 1")
            .And(aField, anotherRule, 
new ArgumentException("broken rule 2"));

//validazione
context.Validate();
if (!context.IsValid)
{
    ...
}

L'ho fatta in una giornatina di lavoro, con tanto di dormita di riflessione in mezzo (la notte, santa cosa, offre sempre ottimi consigli...), ma non è solo farina del mio sacco. Infatti mi è bastato decorare con una serie di Factory la parte delle Constraints di NMock (God save opensource!). E il risultato è una delle cose che più mi hanno divertito e soddifatto negli ultimi tempi. Adoro le fluent Api , se non si fosse capito!

Per oggi è davvero tutto.
Non esistate a commentare! Adoro i confronti costruttivi!

Ciao, a presto.
-papo-

Print | posted on giovedì 13 aprile 2006 13:10 |

Powered by:
Powered By Subtext Powered By ASP.NET