Ho colto la palla al balzo e ho scritto anche la seconda parte
Per chi fosse interessato è disponibile qui
.m
The journey is the most important thing, not the destination. Find your next destination and start travelling again.
Tuesday, August 23, 2005
Layer, il mio personale approccio al problema (Parte II)
In questa seconda parte (qui la prima) completerò la parte applicativa introducendo lo strato
di factory e cercherò di fare un'analisi, spero oggettiva, del laro svolto dei
sui limiti e presenterò alcune soluzioni a questi limiti.
Factory Layer
Nella mia ottica il factory si occupa solo ed esclusivamente della generazione delle istanze delle classi di business, il factory conosce molto bene lo strato di business. Partiamo, come sempre, dal codice che non differisce molto da quello presentato nel precedente articolo riguardo allo strato di persistenza:
Ogni Layer conosce il Layer direttamente sotto di lui ma non conosce in nessun modo il Layer che lo consuma.
Un primo "limite" che è doveroso sottolineare è che per far si che il factory/persistance stiano in assembly diversi da quello del domain, al fine di garantire la sostituzione a caldo, i costruttori delle classi di business devono essere pubblici, cosa che personalmente non mi paice molto, ma tant'è...
Evolvendo la struttura della classe di business presentata, arricchendola quindi con qualche informazione in più, salta subito all'occhio un problema non indifferente:
Seguendo la sua scelta ho reimplementato la proprietà Orders nel seguente modo, trasformandola però in un metodo:
Questo approccio apre lo scenario a svariate possibilità tra cui ad esempio il caricamento asincrono dei dati cosa che con una proprietà sarebbe impossibile, o la possibilità di forzare il reload dei dati senza inquinare la classe di business con strani metodi del tipo "InvalidateOrders()".
Disponibile per qualsiasi chiarimento e/o approfondimento vi invito ad utilizzare il form contact o a lasciare un commento.
.m
Factory Layer
Nella mia ottica il factory si occupa solo ed esclusivamente della generazione delle istanze delle classi di business, il factory conosce molto bene lo strato di business. Partiamo, come sempre, dal codice che non differisce molto da quello presentato nel precedente articolo riguardo allo strato di persistenza:
//
// ASSEMBLY
// Topics.Common.Factory.dll
//
public sealed class FactoryEngine
{
private FactoryEngine()
{
}
private static FactoryEngine _engine;
/*
Singleton
*/
public static FactoryEngine Engine
{
get
{
if( _engine == null )
{
_engine = new FactoryEngine();
}
return _engine;
}
}
private MyCustomerFactory _myCustomers
public MyCustomerFactory MyCustomers
{
get
{
if( this._myCustomers == null )
{
this._myCustomers = MyCustomerFactory.CreateInstance();
}
return this._myCustomers;
}
}
}
public abstract class MyCustomerFactory
{
public static MyCustomerFactory CreateInstance()
{
/*
OMISSIS
Legge da file .config quale tipo di
Factory debba istanziare e da quale
assembly, tramite Activator.CreateInstance( ... )
inizializza la classe "reale" corretta
*/
}
/*
MyCustomerCollection (omissis) è una semplice
collection fortemente tipizzata di MyCustomers
*/
public abstract MyCustomerCollection FindCustomersByCompanyName( string companyName );
public abstract MyCustomer FindByGuid( Guid pk );
}
//
// ASSEMBLY
// Topics.MyApplication.SqlFactory.dll
//
public class MyCustomerSqlFactory : MyCustomerFactory
{
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea la collection passando al costruttore il DataReader
}
public override MyCustomer FindByGuid( Guid pk )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea l'istanza della classe passando al costruttore il DataReader
}
}
Anche qui vale la stessa regola esposta per lo strato di persistenza,
possiamo sostituire il tipo factory a caldo senza che l'applicazione
debba essere ricompilata, un semplicistico esempio potrebbe essere:// ASSEMBLY
// Topics.Common.Factory.dll
//
public sealed class FactoryEngine
{
private FactoryEngine()
{
}
private static FactoryEngine _engine;
/*
Singleton
*/
public static FactoryEngine Engine
{
get
{
if( _engine == null )
{
_engine = new FactoryEngine();
}
return _engine;
}
}
private MyCustomerFactory _myCustomers
public MyCustomerFactory MyCustomers
{
get
{
if( this._myCustomers == null )
{
this._myCustomers = MyCustomerFactory.CreateInstance();
}
return this._myCustomers;
}
}
}
public abstract class MyCustomerFactory
{
public static MyCustomerFactory CreateInstance()
{
/*
OMISSIS
Legge da file .config quale tipo di
Factory debba istanziare e da quale
assembly, tramite Activator.CreateInstance( ... )
inizializza la classe "reale" corretta
*/
}
/*
MyCustomerCollection (omissis) è una semplice
collection fortemente tipizzata di MyCustomers
*/
public abstract MyCustomerCollection FindCustomersByCompanyName( string companyName );
public abstract MyCustomer FindByGuid( Guid pk );
}
//
// ASSEMBLY
// Topics.MyApplication.SqlFactory.dll
//
public class MyCustomerSqlFactory : MyCustomerFactory
{
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea la collection passando al costruttore il DataReader
}
public override MyCustomer FindByGuid( Guid pk )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea l'istanza della classe passando al costruttore il DataReader
}
}
public class MyCustomerRemotingFactory : MyCustomerFactory
{
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
{
/*
Si connette a Server remoto, dove probabilmente
sta girando un servizio che espone un interfaccia
per recuperare gli oggetti necessari
*/
}
public override MyCustomer FindByGuid( Guid pk )
{
//...
}
}
La parte di codice si conclude qui (forse), da quello che abbiamo visto sino
ad ora possiamo cominciare a trarre una prima considerazione, la struttura che
abbiamo realizzato rispetta un sanissimo principio della strutturazione a layer
di un'applicazione:{
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
{
/*
Si connette a Server remoto, dove probabilmente
sta girando un servizio che espone un interfaccia
per recuperare gli oggetti necessari
*/
}
public override MyCustomer FindByGuid( Guid pk )
{
//...
}
}
Ogni Layer conosce il Layer direttamente sotto di lui ma non conosce in nessun modo il Layer che lo consuma.
Un primo "limite" che è doveroso sottolineare è che per far si che il factory/persistance stiano in assembly diversi da quello del domain, al fine di garantire la sostituzione a caldo, i costruttori delle classi di business devono essere pubblici, cosa che personalmente non mi paice molto, ma tant'è...
Evolvendo la struttura della classe di business presentata, arricchendola quindi con qualche informazione in più, salta subito all'occhio un problema non indifferente:
public class MyCustomer : MyObject
{
//...
private MyOrderCollection _orders;
public MyOrderCollection Orders
{
get
{
//LazyLoad
if( this._orders == null )
{
this._orders = ....
}
return this._orders;
}
}
//...
}
Oggiungiamo alla ns classe MyCustomer una proprietà pubblica che rappresenta
gli ordini fatti da quel particolare cliente, salta subito all'occhio che ci
troviamo di fronte a 2 grossi problemi:{
//...
private MyOrderCollection _orders;
public MyOrderCollection Orders
{
get
{
//LazyLoad
if( this._orders == null )
{
this._orders = ....
}
return this._orders;
}
}
//...
}
- Se MyCustomer carica direttamente gli ordini senza passare da un factory vanifichiamo tutto il lavoro fatto per separare i Layer, perchè il cambiamento di Factory ci imporrebbe di riscrivere questa parte di codice per adeguarla al nuovo factory;
- Se diamo a MyCustomer la possibilità di usare il factory contraveniamo innanzitutto al principio esposto in precedenza e soprattutto creiamo un riferimento circolare;
Seguendo la sua scelta ho reimplementato la proprietà Orders nel seguente modo, trasformandola però in un metodo:
public class MyCustomer : MyObject
{
//...
private MyOrderCollection _orders;
public event OrdersLazyLoadEventHandler OrdersLazyLoad = null;
private void OnOrdersLazyLoad( OrdersLazyLoadEventArgs e )
{
if( OrdersLazyLoad != null )
{
OrdersLazyLoad( this, e );
}
}
public MyOrderCollection GetOrders()
{
get
{
//LazyLoad
if( this._orders == null )
{
OrdersLazyLoadEventArgs args = new OrdersLazyLoadEventArgs();
this.OnOrdersLazyLoad( args );
this._orders = args.Result;
}
return this._orders;
}
}
//...
}
public delegate void OrdersLazyLoadEventHandler( object sender, OrdersLazyLoadEventArgs e );
public class OrdersLazyLoadEventArgs : EventArgs
{
public OrdersLazyLoadEventArgs()
{
}
private MyOrderCollection _result;
public MyOrderCollection Result
{
get{ return this._result; }
set{ this._result = value; }
}
}
A questo punto l'unica accortezza è che il factory sottoscriva l'evento per
sapere quando qualcuno cerca di accdere alla collection "Orders", quindi il
factory diventa una cosa del tipo:{
//...
private MyOrderCollection _orders;
public event OrdersLazyLoadEventHandler OrdersLazyLoad = null;
private void OnOrdersLazyLoad( OrdersLazyLoadEventArgs e )
{
if( OrdersLazyLoad != null )
{
OrdersLazyLoad( this, e );
}
}
public MyOrderCollection GetOrders()
{
get
{
//LazyLoad
if( this._orders == null )
{
OrdersLazyLoadEventArgs args = new OrdersLazyLoadEventArgs();
this.OnOrdersLazyLoad( args );
this._orders = args.Result;
}
return this._orders;
}
}
//...
}
public delegate void OrdersLazyLoadEventHandler( object sender, OrdersLazyLoadEventArgs e );
public class OrdersLazyLoadEventArgs : EventArgs
{
public OrdersLazyLoadEventArgs()
{
}
private MyOrderCollection _result;
public MyOrderCollection Result
{
get{ return this._result; }
set{ this._result = value; }
}
}
public class MyCustomerSqlFactory : MyCustomerFactory
{
void Wire( MyCustomer item )
{
item.OrdersLazyLoad += new OrdersLazyLoadEventHandler( Customer_OrdersLazyLoad );
}
void Unwire( MyCustomer item )
{
item.OrdersLazyLoad -= new OrdersLazyLoadEventHandler( Customer_OrdersLazyLoad );
}
void Customer_OrdersLazyLoad( object sender, OrdersLazyLoadEventArgs e )
{
//Chiama il factory degli ordini per ottenere quelli del sender...
e.Result = ...
}
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea la collection passando al costruttore il DataReader
/*
Lo stesso meccanismo è gestito anche dalla collection
che aggancia l'evento per ognuno degli item e a sua volta
lo "rincula" all'eterno
*/
this.Wire( /* loadedInstance */ );
}
public override MyCustomer FindByGuid( Guid pk )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea l'istanza della classe passando al costruttore il DataReader
this.Wire( /* loadedInstance */ );
}
}
{
void Wire( MyCustomer item )
{
item.OrdersLazyLoad += new OrdersLazyLoadEventHandler( Customer_OrdersLazyLoad );
}
void Unwire( MyCustomer item )
{
item.OrdersLazyLoad -= new OrdersLazyLoadEventHandler( Customer_OrdersLazyLoad );
}
void Customer_OrdersLazyLoad( object sender, OrdersLazyLoadEventArgs e )
{
//Chiama il factory degli ordini per ottenere quelli del sender...
e.Result = ...
}
public override MyCustomerCollection FindCustomersByCompanyName( string companyName )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea la collection passando al costruttore il DataReader
/*
Lo stesso meccanismo è gestito anche dalla collection
che aggancia l'evento per ognuno degli item e a sua volta
lo "rincula" all'eterno
*/
this.Wire( /* loadedInstance */ );
}
public override MyCustomer FindByGuid( Guid pk )
{
//Si connette a SQL
//Invoca la SP del caso
//Ottiene un SqlDataReader (IDataReader)
//Crea l'istanza della classe passando al costruttore il DataReader
this.Wire( /* loadedInstance */ );
}
}
Questo approccio apre lo scenario a svariate possibilità tra cui ad esempio il caricamento asincrono dei dati cosa che con una proprietà sarebbe impossibile, o la possibilità di forzare il reload dei dati senza inquinare la classe di business con strani metodi del tipo "InvalidateOrders()".
delegate MyOrderCollection MyOrderCollectionAsyncLoadDelegate( bool invalidate );
public IAsyncResult BeginGetOrders( System.AsyncCallback callback )
{
return this.BeginGetDocuments( false, callback );
}
public IAsyncResult BeginGetOrders( bool invalidate, System.AsyncCallback callback )
{
MyOrderCollectionAsyncLoadDelegate handler = new MyOrderCollectionAsyncLoadDelegate( this.GetOrders );
return handler.BeginInvoke( invalidate, callback, null );
}
public MyOrderCollection EndGetOrders( IAsyncResult result )
{
if( result.IsCompleted )
{
AsyncResult aRes = result as AsyncResult;
MyOrderCollectionAsyncLoadDelegate handler = aRes.AsyncDelegate as MyOrderCollectionAsyncLoadDelegate;
this._myOrders = handler.EndInvoke( result ) as MyOrderCollection;
}
return this._myOrders;
}
public MyOrderCollection GetOrders()
{
return this.GetOrders( false );
}
public MyOrderCollection GetOrders( bool invalidate )
{
//LazyLoad o se c'è richiesta di reload
if( this._orders == null || invalidate )
{
OrdersLazyLoadEventArgs args = new OrdersLazyLoadEventArgs();
this.OnOrdersLazyLoad( args );
this._orders = args.Result;
}
return this._orders;
}
Il codice esposto in questi due articoli è
fondamentalment codice "real world" anche se ampiamente rimaneggiato per adattarlo al
contesto.public IAsyncResult BeginGetOrders( System.AsyncCallback callback )
{
return this.BeginGetDocuments( false, callback );
}
public IAsyncResult BeginGetOrders( bool invalidate, System.AsyncCallback callback )
{
MyOrderCollectionAsyncLoadDelegate handler = new MyOrderCollectionAsyncLoadDelegate( this.GetOrders );
return handler.BeginInvoke( invalidate, callback, null );
}
public MyOrderCollection EndGetOrders( IAsyncResult result )
{
if( result.IsCompleted )
{
AsyncResult aRes = result as AsyncResult;
MyOrderCollectionAsyncLoadDelegate handler = aRes.AsyncDelegate as MyOrderCollectionAsyncLoadDelegate;
this._myOrders = handler.EndInvoke( result ) as MyOrderCollection;
}
return this._myOrders;
}
public MyOrderCollection GetOrders()
{
return this.GetOrders( false );
}
public MyOrderCollection GetOrders( bool invalidate )
{
//LazyLoad o se c'è richiesta di reload
if( this._orders == null || invalidate )
{
OrdersLazyLoadEventArgs args = new OrdersLazyLoadEventArgs();
this.OnOrdersLazyLoad( args );
this._orders = args.Result;
}
return this._orders;
}
Disponibile per qualsiasi chiarimento e/o approfondimento vi invito ad utilizzare il form contact o a lasciare un commento.
.m
Layer (Parte I)
A seguito di un interessate thread su NG microsoft ho deciso di scrivere un articolo (diviso in 2 parti) riguardo il mio personale approccio al problema dei Layer (stratificazione di un'applicazione).
Per chi fosse interessato la prima parte è disponibile qui
Naturalmente ogni commento e suggerimento sarà ben accetto.
.m
Per chi fosse interessato la prima parte è disponibile qui
Naturalmente ogni commento e suggerimento sarà ben accetto.
.m
Layer, il mio personale approccio al problema (Parte I)
Approfitto di questi schermi per...
Ogni tanto (IMHO non abbastanza) sui NewsGroup si parla di design e l'argomento principe per chi si sta approcciando al problema è quasi sempre la stratificazione (suddivisione in Layer) delle applicazioni.
Naturalmente il "problema" l'ho dovuto sviscerare anche io e dopo varie reimplementazioni l'ho "rilsolto" (almeno per ora).
Quando sono di fronte ad applicazioni di un certo "spessore" la struttura è sintetizzabile così:
Domain Layer
Rappresenta lo strato di business e contiene tutte le classi/entities necessarie.
Una esempio tipo di business entity(s) potrebbe essere:
E' lo strato che si occupa effettivamente di persistere ( Insert Update e Delete ) i dati, anche qui un esempio è meglio di mille parole:
"Fine prima parte"
.m
-------------------
Have a nice day
Ogni tanto (IMHO non abbastanza) sui NewsGroup si parla di design e l'argomento principe per chi si sta approcciando al problema è quasi sempre la stratificazione (suddivisione in Layer) delle applicazioni.
Naturalmente il "problema" l'ho dovuto sviscerare anche io e dopo varie reimplementazioni l'ho "rilsolto" (almeno per ora).
Quando sono di fronte ad applicazioni di un certo "spessore" la struttura è sintetizzabile così:
Domain Layer
Rappresenta lo strato di business e contiene tutte le classi/entities necessarie.
Una esempio tipo di business entity(s) potrebbe essere:
//
// ASSEMBLY
// Topics.Common.dll
//
/*
MyObject è la classe di base da cui tutte le
entity del mio framework erditano
*/
public abstract class MyObject
{
protected virtual void Initialize( System.Data.IDataReader reader )
{
if( reader == null )
{
/*
L'istanza rappresenta un nuovo oggetto
quindi inizializziamo i field con i
valori di default
*/
this._uid = Guid.NewGuid();
this._description = String.Empty;
this._isNew = true;
}
else
{
this._uid = reader.GetGuid( reader.GetOrdinal( "UID" ) );
this._description = reader.GetString( reader.GetOrdinal( "Description" ) );
this._isNew = false;
}
}
protected MyObject( System.Data.IDataReader reader )
{
if( reader == null )
{
throw new ArgumentException( "Invalid Source" );
}
this.Initialize( reader );
}
protected MyObject()
{
this.Initialize( null );
}
private Guid _uid;
//La chiave primaria
public Guid UID
{
get{ return this._uid; }
}
private bool _isNew;
/*
La proprietà in sola lettura IsNew
ci permette di sapere se l'istanza
che stiamo maneggiando arriva da un
DataSource o sia una nuovo elemento
*/
public bool IsNew
{
get{ return this._isNew; }
}
private string _description;
public string Description
{
get{ return this._description; }
set
{
if( value == null )
{
value = String.Empty;
}
if( !value.Equals( this._description ) )
{
this._description = value;
}
}
}
/*
ParametersCollection (omessa per semplicità) non è che una
collection di coppia Key (String)/Value (Object) che viene
usata a mo di MOC object al fine di scambiare dati con lo
strato di factory
*/
protected virtual ParametersCollection GetParameters()
{
ParametersCollection parameters = new ParametersCollection();
parameters.Add( "UID", this.UID );
parameters.Add( "Description", this.Description );
return parameters;
}
protected abstract void InsertData( ParametersCollection parameters );
protected abstract void UpdateData( ParametersCollection parameters );
protected abstract void DeleteData( ParametersCollection parameters );
public void Save()
{
/*
Nell'applicazione reale la gestione è decisamente più
complessa e comprende una serie di eventi che permettono
di manipolare dall'esterno alcuni aspetti e consento anche
di interrompere il processo di salvataggio e/o cancellazione
*/
ParametersCollection parameters = this.GetParameters();
/*
Usiamo IsNew per determinare quale sia l'azione
da intraprendere per il salvataggio
*/
if( this.IsNew )
{
this.InsertData( parameters );
}
else
{
this.UpdateData( parameters );
}
}
public void Delete()
{
ParametersCollection parameters = new ParametersCollection();
parameters.Add( "UID", this.UID );
this.DeleteData( parameters );
}
}
//
// ASSEMBLY
// Topics.MyApplication.Domain.dll
//
public class MyCustomer : MyObject
{
protected override void Initialize( System.Data.IDataReader reader )
{
if( reader == null )
{
/*
L'istanza rappresenta un nuovo oggetto
quindi inizializziamo i field con i
valori di default
*/
this._companyName = String.Empty;
}
else
{
this._companyName = reader.GetString( reader.GetOrdinal( "CompanyName" ) );
}
}
public MyCustomer( System.Data.IDataReader reader )
: base( reader )
{
}
public MyCustomer()
: base()
{
}
private string _companyName;
public string CompanyName
{
get{ return this._companyName; }
set
{
if( value == null )
{
value = String.Empty;
}
if( !value.Equals( this._companyName ) )
{
this._companyName = value;
}
}
}
protected override ParametersCollection GetParameters()
{
ParametersCollection parameters = base.GetParameters();
parameters.Add( "CompanyName", this.CompanyName );
return parameters;
}
protected override void InsertData( ParametersCollection parameters )
{
/*
TODO
Solo la classe "reale" Customer conosce il sistema
per persistere/manipolare i propri dati
*/
}
protected override void UpdateData( ParametersCollection parameters )
{
/*
TODO
Solo la classe "reale" Customer conosce il sistema
per persistere/manipolare i propri dati
*/
}
protected override void DeleteData( ParametersCollection parameters )
{
/*
TODO
Solo la classe "reale" Customer conosce il sistema
per persistere/manipolare i propri dati
*/
}
}
Persistence
Layer// ASSEMBLY
// Topics.Common.dll
//
/*
MyObject è la classe di base da cui tutte le
entity del mio framework erditano
*/
public abstract class MyObject
{
protected virtual void Initialize( System.Data.IDataReader reader )
{
if( reader == null )
{
/*
L'istanza rappresenta un nuovo oggetto
quindi inizializziamo i field con i
valori di default
*/
this._uid = Guid.NewGuid();
this._description = String.Empty;
this._isNew = true;
}
else
{
this._uid = reader.GetGuid( reader.GetOrdinal( "UID" ) );
this._description = reader.GetString( reader.GetOrdinal( "Description" ) );
this._isNew = false;
}
}
protected MyObject( System.Data.IDataReader reader )
{
if( reader == null )
{
throw new ArgumentException( "Invalid Source" );
}
this.Initialize( reader );
}
protected MyObject()
{
this.Initialize( null );
}
private Guid _uid;
//La chiave primaria
public Guid UID
{
get{ return this._uid; }
}
private bool _isNew;
/*
La proprietà in sola lettura IsNew
ci permette di sapere se l'istanza
che stiamo maneggiando arriva da un
DataSource o sia una nuovo elemento
*/
public bool IsNew
{
get{ return this._isNew; }
}
private string _description;
public string Description
{
get{ return this._description; }
set
{
if( value == null )
{
value = String.Empty;
}
if( !value.Equals( this._description ) )
{
this._description = value;
}
}
}
/*
ParametersCollection (omessa per semplicità) non è che una
collection di coppia Key (String)/Value (Object) che viene
usata a mo di MOC object al fine di scambiare dati con lo
strato di factory
*/
protected virtual ParametersCollection GetParameters()
{
ParametersCollection parameters = new ParametersCollection();
parameters.Add( "UID", this.UID );
parameters.Add( "Description", this.Description );
return parameters;
}
protected abstract void InsertData( ParametersCollection parameters );
protected abstract void UpdateData( ParametersCollection parameters );
protected abstract void DeleteData( ParametersCollection parameters );
public void Save()
{
/*
Nell'applicazione reale la gestione è decisamente più
complessa e comprende una serie di eventi che permettono
di manipolare dall'esterno alcuni aspetti e consento anche
di interrompere il processo di salvataggio e/o cancellazione
*/
ParametersCollection parameters = this.GetParameters();
/*
Usiamo IsNew per determinare quale sia l'azione
da intraprendere per il salvataggio
*/
if( this.IsNew )
{
this.InsertData( parameters );
}
else
{
this.UpdateData( parameters );
}
}
public void Delete()
{
ParametersCollection parameters = new ParametersCollection();
parameters.Add( "UID", this.UID );
this.DeleteData( parameters );
}
}
//
// ASSEMBLY
// Topics.MyApplication.Domain.dll
//
public class MyCustomer : MyObject
{
protected override void Initialize( System.Data.IDataReader reader )
{
if( reader == null )
{
/*
L'istanza rappresenta un nuovo oggetto
quindi inizializziamo i field con i
valori di default
*/
this._companyName = String.Empty;
}
else
{
this._companyName = reader.GetString( reader.GetOrdinal( "CompanyName" ) );
}
}
public MyCustomer( System.Data.IDataReader reader )
: base( reader )
{
}
public MyCustomer()
: base()
{
}
private string _companyName;
public string CompanyName
{
get{ return this._companyName; }
set
{
if( value == null )
{
value = String.Empty;
}
if( !value.Equals( this._companyName ) )
{
this._companyName = value;
}
}
}
protected override ParametersCollection GetParameters()
{
ParametersCollection parameters = base.GetParameters();
parameters.Add( "CompanyName", this.CompanyName );
return parameters;
}
protected override void InsertData( ParametersCollection parameters )
{
/*
TODO
Solo la classe "reale" Customer conosce il sistema
per persistere/manipolare i propri dati
*/
}
protected override void UpdateData( ParametersCollection parameters )
{
/*
TODO
Solo la classe "reale" Customer conosce il sistema
per persistere/manipolare i propri dati
*/
}
protected override void DeleteData( ParametersCollection parameters )
{
/*
TODO
Solo la classe "reale" Customer conosce il sistema
per persistere/manipolare i propri dati
*/
}
}
E' lo strato che si occupa effettivamente di persistere ( Insert Update e Delete ) i dati, anche qui un esempio è meglio di mille parole:
//
// ASSEMBLY
// Topics.Common.Persistence.dll
//
public sealed class PersistanceEngine
{
private PersistanceEngine()
{
}
private static PersistanceEngine _engine;
/*
Singleton
*/
public static PersistencaEngine Engine
{
get
{
if( _engine == null )
{
_engine = new PersistanceEngine();
}
return _engine;
}
}
private MyCustomerPersistance _myCustomers
public MyCustomerPersistance MyCustomers
{
get
{
if( this._myCustomers == null )
{
this._myCustomers = MyCustomerPersistance.CreateInstance();
}
return this._myCustomers;
}
}
}
public abstract class MyCustomerPersistance
{
public static MyCustomerPersistance CreateInstance()
{
/*
OMISSIS
Legge da file .config quale tipo di
Persistance debba istanziare e da quale
assembly, trami Activator.CreateInstance( ... )
inizializza la classe "reale" corretta
*/
}
public abstract void Update( ParametersCollection parameters );
public abstract void Insert( ParametersCollection parameters );
public abstract void Delete( ParametersCollection parameters );
}
//
// ASSEMBLY
// Topics.MyApplication.SqlPersistence.dll
//
public class MyCustomerSqlPersistance : MyCustomerPersistance
{
public override void Update( ParametersCollection parameters )
{
//Salva su db via StoredProcedure
}
public override void Insert( ParametersCollection parameters )
{
//Salva su db via StoredProcedure
}
public override void Delete( ParametersCollection parameters )
{
//Cancella da db via StoredProcedure
}
}
Quindi sistemando la ns classe MyCustomer nello strato di
business i metodi InsertData, UpdateData e DeleteData avranno tutti un semplice
corpo del tipo// ASSEMBLY
// Topics.Common.Persistence.dll
//
public sealed class PersistanceEngine
{
private PersistanceEngine()
{
}
private static PersistanceEngine _engine;
/*
Singleton
*/
public static PersistencaEngine Engine
{
get
{
if( _engine == null )
{
_engine = new PersistanceEngine();
}
return _engine;
}
}
private MyCustomerPersistance _myCustomers
public MyCustomerPersistance MyCustomers
{
get
{
if( this._myCustomers == null )
{
this._myCustomers = MyCustomerPersistance.CreateInstance();
}
return this._myCustomers;
}
}
}
public abstract class MyCustomerPersistance
{
public static MyCustomerPersistance CreateInstance()
{
/*
OMISSIS
Legge da file .config quale tipo di
Persistance debba istanziare e da quale
assembly, trami Activator.CreateInstance( ... )
inizializza la classe "reale" corretta
*/
}
public abstract void Update( ParametersCollection parameters );
public abstract void Insert( ParametersCollection parameters );
public abstract void Delete( ParametersCollection parameters );
}
//
// ASSEMBLY
// Topics.MyApplication.SqlPersistence.dll
//
public class MyCustomerSqlPersistance : MyCustomerPersistance
{
public override void Update( ParametersCollection parameters )
{
//Salva su db via StoredProcedure
}
public override void Insert( ParametersCollection parameters )
{
//Salva su db via StoredProcedure
}
public override void Delete( ParametersCollection parameters )
{
//Cancella da db via StoredProcedure
}
}
protected override void InsertData( ParametersCollection parameters )
{
PersistanceEngine.Engine.MyCustomers.Insert( parameters );
}
protected override void UpdateData( ParametersCollection parameters )
{
PersistencaEngine.Engine.MyCustomers.Update( parameters );
}
protected override void DeleteData( ParametersCollection parameters )
{
PersistanceEngine.Engine.MyCustomers.Delete( parameters );
}
In questo modo il domain layer accede ad un'interfaccia astratta e non ha la
più "pallida idea" (come è giusto che sia) di quale sia la reale
implementazione dello strato di Persistance. Questo porta un ulteriore vantaggio siccome
lo strato di persistenza viene istanziato dinamicamente tramite
informazioni prese da un file di configurazione è possibile creare un nuovo
strato di persistenza in un nuovo assembly copiarlo nella directory
"bin
" dell'applicazione modificare opportunemente il file di configurazione e
cambiare radicalmente il comportamento dell'applicazione senza che quest'ultima
se ne accorga e soprattutto senza ricompilare.{
PersistanceEngine.Engine.MyCustomers.Insert( parameters );
}
protected override void UpdateData( ParametersCollection parameters )
{
PersistencaEngine.Engine.MyCustomers.Update( parameters );
}
protected override void DeleteData( ParametersCollection parameters )
{
PersistanceEngine.Engine.MyCustomers.Delete( parameters );
}
"Fine prima parte"
.m
-------------------
Have a nice day
Labels:
Why not...
Subscribe to:
Posts (Atom)