design-patterns - first - gof design patterns
Transacciones en el patrĂ³n de repositorio (5)
¿Cómo encapsulo el ahorro de más de una entidad de forma transaccional utilizando el patrón de repositorio? Por ejemplo, ¿qué sucede si deseo agregar un pedido y actualizar el estado del cliente en función de la creación de ese pedido, pero solo lo hago si el pedido se completó correctamente? Tenga en cuenta que para este ejemplo, los pedidos no son una colección dentro del cliente. Ellos son su propia entidad.
No es una responsabilidad del repositorio, generalmente es algo hecho a un nivel superior. Aunque dijiste que no te interesaban las tecnologías específicas, creo que vale la pena vincularlas, por ejemplo, cuando utilizas NHibernate con una aplicación web, probablemente consideres utilizar sesiones por solicitud .
Entonces, si puede administrar transacciones en un nivel superior, mis dos opciones serían:
- Comprobación anticipada : por ejemplo, en un servicio que coordina el comportamiento, decida si desea continuar preguntando al Pedido / Cliente, si alguno de ellos dice que no lo hace, ni siquiera intenta actualizar ninguno de ellos.
- Revertir - Simplemente continúe actualizando el Cliente / Pedido y si las cosas fallan en parte a través de la reversión de la transacción de la base de datos.
Si opta por la segunda opción, entonces la pregunta es qué ocurre con los objetos en la memoria, es posible que su Cliente quede en un estado incoherente. Si eso es importante, y trabajo en escenarios en los que no lo hace, ya que el objeto solo se cargó para esa solicitud, entonces consideraría la verificación inicial si es posible porque es mucho más fácil que las alternativas (revertir el -memory cambia o recarga los objetos).
¿Cómo encapsulo el ahorro de más de una entidad de forma transaccional utilizando el patrón de repositorio? Por ejemplo, ¿qué sucede si deseo agregar un pedido y actualizar el estado del cliente en función de la creación de ese pedido, pero solo lo hago si el pedido se completó correctamente? Tenga en cuenta que para este ejemplo, los pedidos no son una colección dentro del cliente. Ellos son su propia entidad.
Este es solo un ejemplo artificial, por lo que realmente no me importa si los pedidos deben o no estar dentro del objeto del cliente o incluso en el mismo contexto delimitado. Realmente no me importa qué tecnología subyacente se utilizará (nHibernate, EF, ADO.Net, Linq, etc.) Solo quiero ver cómo se vería un código de llamada en este ejemplo claramente inventado de una operación de todo o nada.
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 algunos cambios simples a mi código existente.
Primero, tengo todos mis repositorios implementando una interfaz de marcador simple:
/// <summary>
/// A base interface for all repositories to implement.
/// </summary>
public interface IRepository
{ }
En segundo lugar, dejo que todos mis repositorios habilitados para transacciones implementen la siguiente interfaz:
/// <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 implemente esta interfaz y agregue un código que introduzca 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 hacer implementaciones tales como:
#region IHasTransactions Members
public void BeginTransaction()
{
_db.Transaction = _db.Connection.BeginTransaction();
}
public void CommitTransaction()
{
_db.Transaction.Commit();
}
#endregion
Esto, por supuesto, requiere que se cree 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!
Con Spring.NET AOP + NHibernate puede escribir su clase de repositorio de la forma habitual y configurar sus transacciones en un archivo XML personalizado:
public class CustomerService : ICustomerService
{
private readonly ICustomerRepository _customerRepository;
private readonly IOrderRepository _orderRepository;
public CustomerService(
ICustomerRepository customerRepository,
IOrderRepository orderRepository)
{
_customerRepository = customerRepository;
_orderRepository = orderRepository;
}
public int CreateOrder(Order o, Customer c)
{
// Do something with _customerRepository and _orderRepository
}
}
En el archivo XML, seleccione los métodos que desea que se ejecuten dentro de una transacción:
<object id="TxProxyConfigurationTemplate"
abstract="true"
type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">
<property name="PlatformTransactionManager" ref="HibernateTransactionManager"/>
<property name="TransactionAttributes">
<name-values>
<add key="Create*" value="PROPAGATION_REQUIRED"/>
</name-values>
</property>
</object>
<object id="customerService" parent="TxProxyConfigurationTemplate">
<property name="Target">
<object type="MyNamespace.CustomerService, HibernateTest">
<constructor-arg name="customerRepository" ref="customerRepository" />
<constructor-arg name="orderRepository" ref="orderRepository" />
</object>
</property>
</object>
Y en su código, obtiene una instancia de la clase CustomerService así:
ICustomerService customerService = (ICustomerService)ContextRegistry
.GetContent()
.GetObject("customerService");
Spring.NET le devolverá un proxy de la clase CustomerService que aplicará una transacción cuando llame al método CreateOrder. De esta forma, no hay código específico de transacción dentro de sus clases de servicio. AOP se ocupa de eso. Para más detalles, puede echar un vistazo a la documentación de Spring.NET .
Desea ver implementar el patrón de unidad de trabajo. Hay implementaciones para NHibernate. Uno está en el proyecto de Rhino Commons, también está el Machine.UoW.
Me gustaría ver el uso de algún tipo de sistema de Contexto / Alcance de la transacción. Por lo tanto, es posible que tenga el siguiente código, que se basa aproximadamente en .Net & C #.
public class OrderService
{
public void CreateNewOrder(Order order, Customer customer)
{
//Set up our transactional boundary.
using (TransactionScope ts=new TransactionScope())
{
IOrderRepository orderRepos=GetOrderRespository();
orderRepos.SaveNew(order);
customer.Status=CustomerStatus.OrderPlaced;
ICustomerRepository customerRepository=GetCustomerRepository();
customerRepository.Save(customer)
ts.Commit();
}
}
}
TransactionScope puede anidar, así que supongamos que tuvo una acción que cruzó varios servicios; su aplicación también crearía un TransactionScope. Ahora en el .net actual, si usa TransactionScope, corre el riesgo de escalar a un DTC, pero esto se resolverá en el futuro.
Creamos nuestra propia clase TransactionScope que básicamente gestionaba nuestras conexiones de bases de datos y usaba transacciones SQL locales.