StructureMap è un maturo (apparentemente il più vecchio) IoC tool per .Net. Non è mia intenzione in questo post dimostrare come usarlo o spiegarne le funzionalità e neppure parlare di IoC o DI…per quello c’è Google. Piuttosto, mi interessa analizzare l’implementazione di un suo componente, il Registry DSL – riporto dal sito ufficiale:

“The Registry DSL is the recommended way to configure StructureMap, and creating Registry classes is the recommended way of using the Registry DSL.  The Registry DSL is mostly a Fluent Interface with some Nested Closure usage.  The intent of the Registry DSL is to make the configuration process as error free as possible by using "compiler safe" expressions and defensive programming to point out missing data.  With the advent of .Net 3.5, the Registry DSL is now making use of Expressions as well.”

Perché il DSL Registry

La prima cosa che mi sono chiesto è se era necessario il Registry DSL. StructureMap funzionava anche prima che il DSL Registry fosse introdotto. Il suo core da IoC container era già presente e non è stato modificato con l’introduzione del DSL. Quello che il DSL ha introdotto, però, è un nuovo modo per me (utilizzatore) per configurare il Container. Per esempio, prima dovevo usare un file di configurazione o una sintassi fortemente legata all’implementazione interna del container (una command query api). Adesso, invece, posso usare un linguaggio molto più naturale, intuitivo e costruito nel mio linguaggio di sviluppo. Quindi, in altre parole, il nuovo DSL Registry rende la mia vita più facile. 

Ok, tutto molto interessante, ma vediamo un po’ il codice.

Come punto di partenza, prendo questo snippet usato per registrare/configurare un classico repository:

public class DomainRegistry : Registry
{
public DomainRegistry()
{
        ...
ForRequestedType<IDiaryRepository>().TheDefault.Is.OfConcreteType<SqlDiaryRepository>()
.WithCtorArg("connectionString").EqualTo(connString);
}
}

Expression Builder

La classe Registry rappresenta la root del DSL; ne espone infatti tutta la grammatica.

ForRequestedType<T> è definito così in IRegistry:

// Registering Types -- BuildInstancesOf() and ForRequestedType() are Synonyms
CreatePluginFamilyExpression<PLUGINTYPE> ForRequestedType<PLUGINTYPE>();

 

CreatePluginFamilyExpression (che ha un nome che non mi piace per essere una classe) è uno dei diversi Expression Builders utilizzati dal DSL per configurare un mio servizio/componente che voglio registrare. Ho detto prima che questo DSL rende la vita di un utilizzatore più semplice, ma come? Attraverso a fluent interface over a normal command-query API, che è messa a disposizione proprio da un Expression Builder.

Un Expression Builder, in questo contesto, non ha nulla a che fare con le Linq Expressions. E’ piuttosto un gruppo di oggetti che espone una fluent interface che astrae o incapsula in una sintassi più chiara all’utilizzatore le reali funzionalità interne di una API già esistente.

ForRequestedType<T> è uno degli entry points per la grammatica.

public CreatePluginFamilyExpression<PLUGINTYPE> ForRequestedType<PLUGINTYPE>()
{
return new CreatePluginFamilyExpression<PLUGINTYPE>(this);
}

Come si può notare non fa altro che restituire una nuova di CreatePluginFamilyExpression, nel cui costruttore comincia la vera e propria fase di registrazione del PLUGINTYPE. Non ne riporto il codice ma vorrei far notare (vedi il parametro in input sul costruttore) come l’Expression Builder lavori sull’oggetto Registry e mantenga un suo stato interno che verrà man mano modificato dalla catena di chiamate successive. Questi side effects che in una classica implementazione sarebbero segni di bad design, sono caratteristiche comuni di un DSL come questo. 

L’inizio della catena

“TheDefault” è una proprietà su CreatePluginFamilyExpression che ritorna un altro tipo di Expression Builder:

/// <summary>
///
Define the Default Instance for this PluginType
/// </summary>
public IsExpression<PLUGINTYPE> TheDefault
{
get { return new InstanceExpression<PLUGINTYPE>(i => registerDefault(i)); }
}

“IsExpression<PLUGINTYPE>” espone la grammatica per definire l’istanza di default del mio PLUGINTYPE ed inizia la catena di chiamate “fluent”.

Focus sulla sintassi

E’ interessante notare come “Is” si limiti ad arricchire la sintassi, non modificando affatto lo stato stato dell’expression.

IInstanceExpression<T> IsExpression<T>.Is
{
get { return this; }
}

Progressive Interface

La chiamata a “OfConcreteType<SqlDiaryRepository>()” finalmente mi permette di definire e configurare il tipo concreto che voglio mi sia restituito dal container quando viene richiesta l’interfaccia IDiaryRepository. Tale tipo è rappresentato dalla classe (questo nome mi piace) SmartInstance<T>. Questa classe è “smart” perchè mi permette di configurare il mio tipo in diversi modi (EnrichWith, WithCtorArg, WithProperty, …).

Ognuno di questi metodi è parte di una progressive interface, cioè una fluent interface che ritorna sempre lo stesso oggetto ad ogni passo delle catena, ma incapsulato in interfacce diverse. In questo modo la sequenza delle chiamate viene imposta dall’API e l’utilizzatore viene “forzato” ad effettuare solo chiamate legali, anche grazie all’Intellisense (defensive programming).

public PropertyExpression<SmartInstance<T>> WithCtorArg(string argumentName)
{
return new PropertyExpression<SmartInstance<T>>(this, argumentName);
}

Nel mio esempio configuro la connection string e l’ultima chiamata è a “EqualTo” che, intelligentemente restituisce di nuovo la SmartInstance<T> di prima, cosi che possa continuare a configurarla se necessario. 

public T EqualTo(object propertyValue)
{
_instance.SetProperty(_propertyName, propertyValue.ToString());
return (T) _instance;
}

Il resto

Finora ho analizzato una semplice sequenza di chiamate. Il Registry DSL offre molto di più, ma in fondo i concetti usati sono quelli già visti. Degno di nota è l’utilizzo, in certi casi, di lambda expressions per effettuare static reflection, piuttosto che dinamyc reflection. 

L’autore, nell’introduzione, menziona anche “nested closure”. Il termine è accattivante, ma, a meno che non mi sia sfuggito qualcosa, non sono altro che lambda functions intelligentemente utilizzate per isolare e ritardare certe esecuzioni. 

Off Topic

La classe SpecificationExtensions non è direttamente legata al Registry DSL, ma credo sia interessante. Contiene tutta una serie di extensions methods che rendono la fase di Assert negli unit test molto leggibile a parer mio. Per esempio:

theClass.Services.Length.ShouldEqual(3);

Dove

public static object ShouldEqual(this object actual, object expected)
{
Assert.AreEqual(expected, actual);
return expected;
}

Elegante. Ancora una volta il “fluent pattern” che torna utile...in questo caso come si chiama però?? Fluent Extension? :)

Conclusione

Devo ammettere che appena ho aperto la solution di StructureMap ho preso un po’ paura. Il progetto è piuttosto maturo e il code base lo dimostra, certamente molto piu’ complesso di Funq che avevo analizzato nel post precedente. Anche per questo mi sono concentrato su un singolo componente.

Ho scelto il Registry DSL perché è basato su concetti che generalmente non uso, specifici dei DSL (dovrei chiarire che sono specifici degli internal DSL). Ne avevo sentito parlare e avevo letto qualcosa a proposito, ma vagamente, senza troppo impegno. E’ stato illuminante vederne alcuni applicati nella pratica.

Per l’utilizzatore, credo sia tangibile il vantaggio di avere a disposizione un DSL così, ma tutto ha un costo. IMHO, il costo più alto di una tale API è la perdita di chiarezza nell’implementazione dell’API stessa. Il codice non è facile da leggere e da seguire, visti anche i diversi side effects, la nomenclatura è strana quando la si guarda da dentro (cioè dal punto di vista dell’implementatore). Con questo non voglio dire che sia stata mal disegnata, credo piuttosto che sia una sua caratteristica (o un prezzo da pagare) difficile da eliminare.   

Essendo questo semplicemente un post su un semplice blog, non ha nessuna pretesa di essere esaustivo sull’argomento, ma probabilmente, a qualcuno verrà la voglia di approfondire.