Al arrancar mi computadora esta mañana me enfrenté al problema exacto de un proyecto en el que estoy trabajando. Tenía algunas ideas que me llevan al siguiente diseño, y los comentarios serían más que increíbles. Desafortunadamente, el diseño sugerido por Josh no es posible, ya que tengo que trabajar con un servidor SQL remoto y no puedo habilitar el servicio Distribuir coordinador de transacciones en el que confía.
Mi solución se basa en unos pocos cambios simples a mi código existente.
En primer lugar, tengo todos mis repositorios implementar una sencilla interfaz de marcador:
/// <summary>
/// A base interface for all repositories to implement.
/// </summary>
public interface IRepository
{ }
En segundo lugar, dejar que todos mis repositorios habilitados transacciones implementan la interfaz siguiente:
/// <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();
}
La idea es que en todos mis repositorios Implemento esta interfaz y agrego un código que introduce la transacción directamente dependiendo del proveedor real (para repositorios falsos he hecho una lista de delegados que se ejecuta en la confirmación). Para LINQ to SQL sería fácil de hacer implementaciones tales como:
#region IHasTransactions Members
public void BeginTransaction()
{
_db.Transaction = _db.Connection.BeginTransaction();
}
public void CommitTransaction()
{
_db.Transaction.Commit();
}
#endregion
Por supuesto, esto requiere que se crea una nueva clase de repositorio para cada hilo, pero esto es razonable para mi proyecto.
Cada método que utiliza el repositorio necesita invocar el BeginTransaction()
y el EndTransaction()
, si el repositorio implementa IHasTransactions
. Para hacer esta llamada aún más fácil, se me ocurrieron las siguientes extensiones:
/// <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();
}
}
}
¡Los comentarios son apreciados!
No creo que esta sea una solución en espíritu de DDD. Básicamente, ha creado un script de transacción que hace el trabajo del Modelo de Dominio. El servicio no debería cambiar el estado del cliente, por ejemplo. –
Algo en el código tiene que manejar esta regla comercial, ya sea en este nivel o en un nivel más alto, el punto era hacer los cambios dentro de un único TransactionScope que permite transacciones locales o transacciones distribuidas para manejar la transacción.Si la regla comercial dice actualizar al cliente cada vez que se realiza un pedido, este es un buen lugar para manejarlo ya que todos los pedidos se procesan aquí. – JoshBerke