Il presente articolo è stato tradotto automaticamente.

Modelli e procedure Microsoft

Inserimento delle dipendenze nelle librerie

Chris Tavares

Microsoft Enterprise Library di refactoring

All'introduzione di dipendenza (DI) è un motivo che è stata per essere traction nella comunità degli sviluppatori .NET negli ultimi anni. Blogger prominente hanno già in accennato i vantaggi di per un determinato periodo di tempo. Diversi articoli sull'argomento sono stati pubblicati in di MSDN Magazine. .NET 4.0 verrà essere spedizione alcune funzionalità simile a quello DI, con piani a crescere in un sistema DI completo in futuro.

Durante la lettura del post di blog e articoli su DI, ho notato una distorsione piccole ma significativo nella copertura dell'argomento. Comunicare con i processi di scrittura sull'utilizzo DI nel contesto di un'intera applicazione. Ma cosa succede se si desidera scrivere una raccolta o un framework che utilizza DI? Questa modifica lo stato attivo influisce in qualche modo l'utilizzo del motivo? Questo era qualcosa che abbiamo (il team Enterprise Library patterns & practices) immediato raggiunto alcuni mesi fa come Microsoft avremmo lavorando sull'architettura di Enterprise Library 5.0.

Background

Microsoft Enterprise Library (Entlib) è una versione molto conosciuta dal gruppo Microsoft patterns & practices. Con più di 2 milioni di download per data, viene utilizzato in quasi ogni nicchia imaginable dall'istituto finanziario e governativo per ristoranti e produttori di apparecchiature medicali. Entlib è, come suggerisce il nome, una libreria che consente di uno sviluppatore di alcune problematiche comuni condivise da molti sviluppatori dell'organizzazione. Se non si ha familiarità con Entlib, eseguire un aspetto sul nostro sito durante il p & p dev centro per ulteriori informazioni.

Entlib altamente è basato sulla configurazione. Gran parte del relativo codice è dedicata per la lettura della configurazione e assemblaggio oggetti grafici in base a tale configurazione. Per ottenere oggetti Entlib molto complessi. La maggior parte dei blocchi contengono molte funzionalità facoltative di. Inoltre, è inoltre disponibile una grande quantità di infrastruttura sottostante per il supporto di elementi quali strumentazione, è inoltre necessario ottenere cablata alto. Poiché non si desidera apportare manualmente nostri utenti creare i provider di strumentazione, leggere la configurazione e così via semplicemente utilizzare Entlib, creazione di un oggetto è incapsulata dietro gli oggetti di factory e facciate statico.

Il nucleo centrale di Entlib versione 2 tramite versione 4 è un framework di piccole dimensioni denominato ObjectBuilder. ObjectBuilder è descritta da relativi autori come “ un framework per la creazione di contenitori injection dipendenza. ” Enterprise Library è solo uno del p & p progetti che utilizza ObjectBuilder; altri includono il composito dell'interfaccia utente Application Block, smart client software factory e Web Client Software Factory. In particolare, Entlib ha richiesto la parte “ framework ” della descrizione a cuore e creato un insieme di personalizzazioni per ObjectBuilder di grandi dimensioni. Queste personalizzazioni fornito le funzionalità necessarie per leggere la configurazione di Entlib e assemblare oggetti grafici. Sono stati inoltre necessari, in molti casi, per migliorare le prestazioni su diritti d ' implementazione ObjectBuilder.

Lo svantaggio è stato impiegato dal tempo a comprendere entrambi ObjectBuilder stesso (un estremamente astratta progettazione più completa assenza di documentazione assegnato ObjectBuilder una reputazione deserved per complessità) e il Entlib personalizzazioni. Di conseguenza, persone che desiderano scrivere blocchi personalizzati hook nella strategia di creazione dell'Entlib oggetto sono stati spesso stymied dalla curva di apprendimento enorme che dovevano climb semplicemente per iniziare.

E per aggiungere un ulteriore complicazione in Entlib 4.0 sono stati rilasciati Unity dipendenza injection contenitore. Con molti vantaggi di volevamo assicurarsi che i clienti che sono stati, per qualsiasi motivo, Impossibile utilizzare uno dei molti contenitori di fine open source aveva una buona opzione per DI da Microsoft. E, naturalmente, in modo da facilitarne la ottenere oggetti Entlib funziona quando si utilizza anche Unity volevamo. In Entlib 4.0, l'integrazione Unity terminato alto da un sistema di creazione oggetto parallela accanto all'infrastruttura esistente di ObjectBuilder. Ora gli autori di blocco era necessario conoscere non solo ObjectBuilder e le estensioni di Entlib, ma anche dei meccanismi interni Unity più alcune estensioni Entlib non esiste. Non è un passaggio nella giusta direzione.

Lo spostamento per semplicità

Abbiamo avviato lavoro in Entlib 5.0 in aprile di 2009. Un tema principale per questa versione è stata “ semplicità per la vittoria. ” Questo incluso semplicità non solo per l'utente finale (sviluppatori chiamata Entlib) ma nel codice di Entlib se stesso. Miglioramenti qui dovrebbe rendere più semplice per noi mantenere Entlib in futuro e rendere più semplice comprendere, personalizzare ed estendere le funzionalità dei nostri clienti.

Uno degli aspetti principali che sapevamo lavoro necessario era la pipeline di creazione oggetto, oppure è necessario pronunciare condutture? Due insiemi diversi ma parallele di codice per la stessa funzionalità è mantenere ricetta per d'emergenza. Qualcosa doveva essere eseguita.

Impostiamo out questi obiettivi per il refactoring:

  • Codice client esistente non dovrebbe essere necessario cambiare a causa delle modifiche apportate all'architettura. Che richiedono una ricompilazione è OK, ma che richiede modifiche del codice sorgente non è (Naturalmente, il client API potrebbero cambiare per altri motivi ). Internal o API di estendibilità sono gioco fiera.
  • Rimuovere le pipeline di creazione oggetto ridondanti. Opportuno disponiamo di un solo modo per creare gli oggetti non due (o più).
  • I clienti che non interessano DI non dovrebbero essere interessati da Entlib utilizzando internamente.
  • I clienti che si interessano DI è possono scegliere il contenitore da utilizzare e ottenere i relativi oggetti e di oggetti di Entlib di esso.

Questi obiettivi aveva alcune implicazioni, sia separatamente che in combinazione. È stato l'obiettivo “ uno oggetto creazione pipeline ” sulla relativa superficie piuttosto semplice. Si è deciso di rimuovere completamente il sistema di ObjectBuilder e passare a un contenitore DI come il motore di creazione oggetto internamente. Ma quindi arriveremo “ codice client esistente non dovrebbe cambiare. ” L'API di Entlib classico è un insieme di facciate statico e Factory. Ad esempio, viene eseguita la registrazione di un messaggio utilizzando il blocco di registrazione come illustrato:

Logger.Write("My Message");

Sotto le quinte facciata registratore utilizza un'istanza di un oggetto per la scrittura nel log per eseguire il lavoro effettivo. Dunque come facciata logger ottenere il per la scrittura nel log? Per la scrittura nel log è una classe piuttosto complessa con numerose dipendenze, in modo che non è possibile semplicemente nuovi alto e prevede che la configurazione per cablate in modo appropriato. Abbiamo trovato per la conclusione che è stato necessario un'istanza contenitore globale per il logger e tutte le altre classi statiche nell'API. Abbiamo appena possibile mantenere un contenitore Unity globale, ma quindi eseguiamo in “ i clienti possono ottenere per scegliere il contenitore che desiderano. ”

Desideriamo che la combinazione di Unity e Entlib sia un'esperienza di prim'ordine. Si desidera anche fornire tale esperienza di prima classe con anche altri contenitori. Mentre le funzionalità generali di contenitori DI sono il consiglio comuni, la modalità di accesso di tali funzionalità variare notevolmente. In effetti, i generatori di molti dei contenitori di prendere in considerazione relativa configurazione API essere loro principale vantaggio competitivo. In che modo vengono mappate nostra configurazione Entlib nel contenitore notevolmente diverso API

Classica computer Science Solution

È un truism Scienza computer: Ogni problema in informatica può essere risolto aggiungendo un livello di riferimento indiretto. E che è esattamente come abbiamo risolto il problema di indipendenza dal contenitore. Il livello di riferimento indiretto è un contenitore Configuratore chiamiamo. Essenzialmente, è il ruolo del Configuratore per leggere la configurazione del Entlib e configurare un contenitore in modo che corrispondano.

Purtroppo, la lettura della configurazione di se stesso non sufficiente. Formato di file di configurazione del Entlib è molto utente finale concentrato. È possibile configurare le categorie di registrazione, i criteri di eccezione e cache gli archivi di backup. Esso non pronunciare nulla su oggetti da cui sono effettivamente necessari per implementare tale funzionalità, o ciò che i valori da passare ai costruttori o le proprietà per impostare. D'altra parte, DI contenitore di configurazione, è tutte sulle “ mappare questa interfaccia per questo tipo ” “ chiamare questo costruttore ” e “ impostare questa proprietà. ” È stato necessario un ulteriore livello di riferimento indiretto mappato la configurazione di un blocco per gli oggetti effettivi necessari per implementare tale blocco. L'alternativa è stato affinché ogni Configuratore (occorre uno Configuratore per ogni contenitore) conoscere i dettagli di ciascuno dei blocchi. Ciò è potere immediatamente, ogni modifica apportata al codice di un blocco sarebbe increspatura attraverso tutti configurators. E cosa succede quando qualcuno scrive un blocco personalizzato?

Abbiamo terminato con un insieme di oggetti ci stiamo chiamata TypeRegistrations. Le varie sezioni di configurazione sono responsabile della generazione di un di tipo registrazione modello; una sequenza di oggetti TypeRegistration. In di Figura 1 è illustrata l'interfaccia per TypeRegistration.

Figura 1 di Classi di TypeRegistration

public class TypeRegistration
    {

        public TypeRegistration(LambdaExpression expression);
        public TypeRegistration(LambdaExpression expression, Type serviceType);

        public Type ImplementationType { get; }
        public NewExpression NewExpressionBody { get; }
        public Type ServiceType { get; private set; }
        public string Name { get; set; }

        public static string DefaultName(Type serviceType);
        public static string DefaultName<TServiceType>();

        public LambdaExpression LambdaExpression { get; private set; }

         public bool IsDefault { get; set; }

         public TypeRegistrationLifetime Lifetime { get; set; }

         public IEnumerable<ParameterValue> ConstructorParameters { get; }

         public IEnumerable<InjectedProperty> InjectedProperties { get; }
    }

Una quantità consistente, ma la struttura di base è piuttosto semplice. Questa classe descrive la configurazione necessaria per un solo tipo. Il ServiceType è l'interfaccia utente richiederà dal contenitore, mentre il ImplementationType è il tipo che implementa effettivamente l'interfaccia. È il nome che del servizio deve essere registrato nella sezione. Durata determina singleton (reso la stessa istanza ogni volta) o transitorio (creare una nuova istanza ogni volta) comportamento di creazione. E così via. Si è scelto di utilizzare un'espressione lambda per creare l'oggetto TypeRegistration perché rende molto semplice specificare queste informazioni in un unico, compact campione. Ecco un esempio di creazione di una registrazione di tipo dal blocco di accesso ai dati:

yield return new TypeRegistration<Database>(
       () => new SqlDatabase(
           ConnectionString,
           Container.Resolved<IDataInstrumentationProvider>(Name)))
       {
           Name = Name,
           Lifetime = TypeRegistrationLifetime.Transient
       };

Registrazione di questo tipo è che comunica “ quando richiede un database denominato nome, restituire un nuovo oggetto SqlDatabase, viene costruito con ConnectionString e un IDataInstrumentationProvider. ” La cosa interessante sull'utilizzo della lambda qui è che, quando si scrivono i blocchi, abbiamo che era possibile creare queste espressioni come se non è stato direttamente newing alto gli oggetti. Il compilatore verrà digitato verificare che l'espressione in modo che abbiamo accidentalmente non tenti di chiamare un costruttore che non esiste. Per impostare le proprietà, sufficiente utilizzare la sintassi C# oggetto inizializzatore all'interno di un'espressione lambda. La classe TypeRegistration si occupa dei dettagli di prelievo tramite la lambda e l'estrazione la firma del costruttore, parametri, tipi e così via, in modo che non è necessario che l'autore Configuratore.

Un trucco utile che abbiamo utilizzato è tale chiamata a “ Container.Resolved. ” Tale metodo non viene effettivamente eseguire alcuna operazione; in realtà, questa implementazione semplicemente è questa:

public static T Resolved<T>(string name)
        {
            return default(T);
        }

Perché è presente?Tenere presente che questa espressione lambda viene mai effettivamente eseguita.Invece, abbiamo dettagliatamente la struttura dell'espressione in fase di esecuzione per estrarre le informazioni di registrazione.Questo metodo è semplicemente un indicatore conosciuto.Quando si trova una chiamata a Container.Resolved come parametro, abbiamo interpretare che come “ risolvere questo parametro tramite il contenitore. ” Abbiamo trovato questa tecnica indicatore metodo a essere utile in numerose posizioni quando eseguendo lavoro avanzata con le strutture ad albero dell'espressione.

Nella fine del flusso di file di configurazione per configurato contenitore aspetto di Figura 2.

Nella figura 2 configurazione Container

È importante spiegare qui abbiamo fatto una decisione di progettazione.Configurare TypeRegistration sistema non è e non potranno mai essere, un interesse generale-tutto ciò che per qualsiasi contenitore DI astrazione.È stato progettato specificamente per soddisfare le esigenze del progetto Enterprise Library.Il team di patterns & practices viene non posizionamento questo come Guida basata sul codice.Mentre il concetto di base (estrarre la configurazione in un modello astratta) è in genere applicabili implementazione specifica è per Entlib solo.

Durante il recupero di oggetti all'esterno del contenitore

Abbiamo abbiamo così ottenuta nostro contenitore configurato.Che è metà della battaglia.Ma come ottenere gli oggetti Indietro?Le interfacce del contenitore variano in ciò anche, sebbene Fortunatamente non come eseguire le relative interfacce di configurazione.

Fortunatamente, non abbiamo inventare un'astrazione di nuova.Ispirato da di un post del blog di Jeremy Miller nell'estate del 2008, gli schemi di & gruppo di procedure consigliate, team MEF presso Microsoft e gli autori di molti contenitori DI ha lavorati insieme per definire un minimo comune denominatore per la risoluzione di oggetti all'esterno di un contenitore diverso.Questo è stato pubblicato in CodePlex e MSDN come progetto Common Service Locator .Questa interfaccia ha assegnato noi esattamente ciò che è stato necessario; da all'interno di Enterprise Library ogni volta che è stato necessario per ottenere un oggetto all'esterno del contenitore, si potrebbe chiamare attraverso questa interfaccia e isolate dal contenitore specifico utilizzato.Naturalmente, la domanda successiva è la seguente: dove è il contenitore?

Enterprise Library non dispone di alcun tipo di requisito bootstrap.Quando si utilizza facciate statico, non è necessario chiamare una funzione di inizializzazione in un punto qualsiasi.La libreria originale lavorata da prelevare configurazione quando è necessario innanzitutto.È stato necessario replicare questo comportamento in modo che la libreria sarebbe pronta per passare quando viene chiamato.

È stato necessario era uno standard, ben noto per il recupero di un contenitore correttamente configurato.La libreria Common Service Locator effettivamente dispone di uno di questi: la proprietà statica di ServiceLocator.Current.Si è deciso di non utilizzare questa opzione per un paio di ragioni.Il motivo principale è stato che il ServiceLocator.Current potrebbe essere utilizzato da altre librerie o persino dall'applicazione stessa.È stato necessario essere in grado di impostare il contenitore al primo accesso di qualsiasi oggetto Entlib; qualsiasi altro elemento era una ricetta per capelli perdita come persone è cercato di capire perché i relativi contenitori appositamente costruiti è scomparso o per questo motivo Entlib lavorato alla prima chiamata, ma non sono state successivamente.Il secondo motivo ha a che fare con un difetto nell'interfaccia stessa.Non è possibile eseguire la query della proprietà per sapere se è stata impostata.Che reso difficile stabilire quando impostare i il contenitore.

In questo modo, abbiamo creato nostra proprietà statica: EnterpriseLibraryContainer.Current.È possibile impostare questa proprietà anche codice utente, ma fa specificamente parte di Enterprise Library in modo che vi sono meno probabilità di conflitti con altre librerie o dell'applicazione principale.Nella prima chiamata a una facciata statica, è necessario verificare EnterpriseLibraryContainer.Current.Se è impostata, è possibile utilizzare qualsiasi elemento non esiste.In caso contrario, creare un oggetto UnityContainer, configurarlo un Configuratore e impostato come valore della proprietà Current.

Il risultato è che sono ora disponibili tre metodi diversi per accedere alla funzionalità di Enterprise Library.Se si utilizza l'API classico, tutto semplicemente funziona.Verrà creato e utilizzato un contenitore Unity sotto le quinte.Se si utilizza un contenitore DI diverso nell'applicazione e non si desidera Unity nel proprio processo ma sta ancora utilizzando l'API classico, configurare il contenitore utilizzando un Configuratore, racchiudere in un IServiceLocator, attenersi in EnterpriseLibraryContainer.Current e quindi le facciate continueranno a funzionare.Essi solo ora sta utilizzando il contenitore di scelta sotto le quinte.Abbiamo non viene effettivamente fornita configurators qualsiasi contenitore nel progetto principale Entlib diverso da quello per Unity; nostra speranza è che la comunità verrà implementarle per altri contenitori.

Una seconda opzione consiste nell'utilizzare direttamente EnterpriseLibraryContainer.Current.È possibile chiamare GetInstance < T > () per ottenere qualsiasi oggetto di Enterprise Library ed esso verrà offrono uno.E anche in questo caso, applicare un diverso contenitore dietro di esso se si desidera.

Infine, è sufficiente utilizzare il contenitore di scelta direttamente.Sarà necessario bootstrap configurazione Entlib in un contenitore utilizzando un Configuratore, ma se si utilizza un contenitore, è necessario impostare a prescindere dal fatto che, in modo che questo non è un nuovo requisito.Da qui, è semplicemente inserire la Entlib gli oggetti si desidera utilizzare come dipendenze e si è disattivata e in esecuzione.

Come sarebbe necessario We eseguire?

Let’s esaminare nuovamente nostra serie di obiettivi e osservare come questa progettazione stacks.

  1. Codice client esistente non dovrebbe essere necessario cambiare a causa delle modifiche apportate all'architettura.Che richiedono una ricompilazione, ma che richiede modifiche del codice sorgente non (Naturalmente, il client API potrebbero cambiare per altri motivi ).Internal o API di estendibilità sono gioco fiera.

    SODDISFATTI. L'API originale ancora funziona invariato.Se non esistono esigenze particolari sull'inserimento delle dipendenze, nessuno dei due occorre conoscere né interessano il modo in cui gli oggetti sono cablati alto sotto le quinte.

  2. Rimuovere le pipeline di creazione oggetto ridondanti.Opportuno disponiamo di un solo modo per creare gli oggetti non due (o più).

    SODDISFATTI. Lo stack di ObjectBuilder è sparito dalla base di codice, tutto ciò che è ora incorporata tramite i meccanismi di TypeRegistration e Configuratore.È necessario uno Configuratore per ogni contenitore.

  3. I clienti che non interessano DI non dovrebbero essere interessati da Entlib utilizzando internamente.

    SODDISFATTI. DI non presentare a meno che non si desidera visualizzarla.

  4. I clienti che si interessano DI è possono scegliere il contenitore da utilizzare e ottenere i relativi oggetti e di oggetti di Entlib di esso.

    SODDISFATTI. È possibile utilizzare che il contenitore della scelta direttamente o si può avere viene utilizzato dietro le quinte dietro facciate statico.

Abbiamo terminato con alcuni ulteriori vantaggi.La base di codice di Entlib ha ricevuto più semplice.Abbiamo terminato di eliminazione di circa 200 classi dall'implementazione originale.Dopo aver aggiunto i pezzi di registrazione del tipo, non è stato premuto circa 80 classi totale dopo che è stato eseguito il refactoring.Inoltre, le classi che sono stati aggiunti erano più semplice rispetto a quelli che sono stati rimossi e la struttura globale è stato significativamente più coerenza, con un numero minore di parti in movimento o casi speciali.

Un ulteriore vantaggio era che la versione refactored è scoperto per risultare leggermente più veloce rispetto all'originale, con alcune misurazioni iniziali, informale che mostra un miglioramento delle prestazioni del 10 %.Dopo che abbiamo visto che i numeri, si tratta effettivamente reso senso per noi.La maggior parte delle complessità nel codice originale è stato da una serie di ottimizzazione delle prestazioni che funzionava intorno implementazione lenta dell'ObjectBuilder.La maggior parte dei contenitori DI hanno avuto un notevole lavoro svolto per le prestazioni generali.Ricostruendo Entlib all'inizio di un contenitore, è possibile sfruttare i vantaggi di tale lavoro sulle prestazioni e non è necessario eseguire questa operazione molto noi.Come Unity e altro contenitore si evolvono e ulteriormente ottimizzate, Entlib dovrebbe ottenere più velocemente senza molta parte nostra sforzo.

Lezioni acquisite per altre librerie

Enterprise Library è un buon esempio di una libreria che effettivamente si avvale di un contenitore injection dipendenza senza essere rigido correlata a uno.Se si desidera scrivere una libreria che utilizza un contenitore DI ma non imporre la scelta nel consumer, ci auguriamo che è possibile trovare alcune ispirazione progettazione dal nostro esempio.Penso che nostri obiettivi per la modifica, in particolare questi ultimi due sono rilevanti per qualsiasi libreria autore, non solo di Entlib:

  • I clienti che non interessano DI non dovrebbero essere interessati da Entlib utilizzando internamente.
  • I clienti che si interessano DI è possono scegliere il contenitore da utilizzare e ottenere i relativi oggetti e di oggetti di Entlib di esso.

Quando si progetta il catalogo multimediale, esistono diverse domande, che è necessario prendere in considerazione.Assicurarsi di considerare questi:

  • Modalità di avvio è il catalogo multimedialeI client sono necessario eseguire un'operazione specifica per ottenere il codice per impostare o hanno un punto di ingresso statico che dovrebbe funzionare esattamente?
  • Come è modello di oggetti grafici in modo che un contenitore può essere configurato senza dover eseguire chiamate a livello di codice in tale contenitore?Dare un'occhiata al nostro sistema TypeRegistration per ispirazione.
  • Come verrà gestito il contenitore che si sta utilizzando?È intenzione di gestita internamente o i chiamanti gestirlo?Come il chiamante stabilire si quale contenitore da utilizzare?

Abbiamo trovato con una buona serie di risposte a queste domande per il nostro progetto.Spero che questo esempio è in grado di fornire ispirazione durante la progettazione di uso.

Chris Tavares è uno sviluppatore di Microsoft patterns & practices team, in cui è il responsabile di sviluppo per Enterprise Library e Unity.Precedenti a Microsoft, ha lavorato in consulenza compattazione wrap software incorporate e sistemi.Egli blog relativi Entlib, p & p e gli argomenti relativi allo sviluppo generale in tavaresstudios.com .