Stamattina, durante l'avvio del mio computer, ho affrontato il problema esatto per un progetto su cui sto lavorando. Ho avuto alcune idee che hanno portato al seguente design - e i commenti sarebbero stati più che fantastici. Sfortunatamente il design suggerito da Josh non è possibile, poiché devo lavorare con un server SQL remoto e non posso abilitare il servizio Distribute Transaction Coordinator su cui si basa.
La mia soluzione si basa su alcune modifiche ancora semplici al mio codice esistente.
In primo luogo, ho tutti i miei repository implementano una semplice interfaccia marcatore:
/// <summary>
/// A base interface for all repositories to implement.
/// </summary>
public interface IRepository
{ }
In secondo luogo, ho lasciato tutti i miei operazione ha permesso repository implementare la seguente interfaccia:
/// <summary>
/// Provides methods to enable transaction support.
/// </summary>
public interface IHasTransactions : IRepository
{
/// <summary>
/// Initiates a transaction scope.
/// </summary>
void BeginTransaction();
/// <summary>
/// Executes the transaction.
/// </summary>
void CommitTransaction();
}
L'idea è che in tutti i miei repository implemento questa interfaccia e aggiungo codice che introduce la transazione direttamente a seconda del fornitore effettivo (per i repository fasulli ho creato un elenco di delegati che viene eseguito su commit). Per LINQ to SQL che sarebbe stato facile fare implementazioni quali:
#region IHasTransactions Members
public void BeginTransaction()
{
_db.Transaction = _db.Connection.BeginTransaction();
}
public void CommitTransaction()
{
_db.Transaction.Commit();
}
#endregion
Questo naturalmente richiede che una nuova classe repository viene creato per ogni thread, ma questo è ragionevole per il mio progetto.
Ogni metodo che utilizza il repository deve richiamare lo BeginTransaction()
e lo EndTransaction()
, se il repository implementa IHasTransactions
. Per rendere questa chiamata ancora più semplice, ho trovato le seguenti estensioni:
/// <summary>
/// Extensions for spawning and subsequently executing a transaction.
/// </summary>
public static class TransactionExtensions
{
/// <summary>
/// Begins a transaction if the repository implements <see cref="IHasTransactions"/>.
/// </summary>
/// <param name="repository"></param>
public static void BeginTransaction(this IRepository repository)
{
var transactionSupport = repository as IHasTransactions;
if (transactionSupport != null)
{
transactionSupport.BeginTransaction();
}
}
public static void CommitTransaction(this IRepository repository)
{
var transactionSupport = repository as IHasTransactions;
if (transactionSupport != null)
{
transactionSupport.CommitTransaction();
}
}
}
I commenti sono apprezzati!
Non penso che questa sia una soluzione in spirito di DDD. Fondamentalmente hai creato uno script di transazione che fa il lavoro di Domain Model. Il servizio non dovrebbe cambiare lo stato del cliente, ad esempio. –
Qualcosa nel codice deve gestire questa regola aziendale, sia a questo livello che a un livello più alto il punto stava facendo le modifiche all'interno di un singolo TransactionScope consentendo transazioni locali o transazioni distribuite per gestire la transazione.Se la regola aziendale dice aggiornare il cliente ogni volta che viene effettuato un ordine, questo è un buon posto per gestirlo poiché tutti gli ordini passano qui. – JoshBerke