2011-07-26 12 views
8

Bueno, en una aplicación web, una unidad de trabajo es responsable de la gestión de transacciones.Nhibernate: ¿Quién es responsable de la gestión de transacciones en una aplicación no web?

¿Pero qué pasa con una aplicación de Windows?

Hasta donde sé, el repositorio es el conector entre mi capa de acceso a datos y mi capa de negocios. Oculta todo el material de acceso a datos de mi capa empresarial.

Usando este hecho permítanme pensar en llevar todo el material de transacción al repositorio.

Pero he leído que tener los métodos Commit/RollBack en el repositorio está violando la intención del repositorio.

Me pregunto quién es el responsable de la gestión de transacciones en una aplicación no web y cómo oculto la transacción/material de Nhibernate de la capa empresarial.

+3

Ayende tiene una aplicación de ejemplo https://github.com/ayende/Effectus –

+0

ok que podría ser una solución para formularios win ... ¿qué pasa con los servicios de Windows? – Rookian

+0

Puedo describir un patrón con el que he tenido éxito. Se aplica si tiene un modelo simple de "una transacción por operación" (no se requieren transacciones anidadas) y está utilizando un contenedor IoC. ¿Esto satisfaría tus necesidades? Básicamente, cuando el código de la capa de servicio decide que es hora de "hacer trabajo de dominio", usa el patrón de comando y un invocador para el comando (el IoC le da el invocador al código de servicio) –

Respuesta

4

La respuesta general es "Quien crea la instancia ISession debe deshacerse de ella. Si la transacción no se ha confirmado, esto es efectivamente una reversión".

He tenido éxito usando el patrón de comando para definir una operación que quiero realizar en una unidad de trabajo. Digamos que tenemos una entidad Person y una de las cosas que podemos hacer es cambiar el nombre de una persona. Vamos a empezar con la entidad:

public class Person 
{ 
    public virtual int Id { get; private set; } 
    public virtual string Name { get; private set; } 

    public virtual void ChangeName(string newName) 
    { 
     if (string.IsNullOrWhiteSpace(newName)) 
     { 
      throw new DomainException("Name cannot be empty"); 
     } 

     if (newName.Length > 20) 
     { 
      throw new DomainException("Name cannot exceed 20 characters"); 
     } 

     this.Name = newName; 
    } 
} 

Definir un simple comando POCO así:

public class ChangeNameCommand : IDomainCommand 
{ 
    public ChangeNameCommand(int personId, string newName) 
    { 
     this.PersonId = personId; 
     this.NewName = newName; 
    } 

    public int PersonId { get; set; } 
    public string NewName { get; set; } 
} 

... y un controlador para el comando:

public class ChangeNameCommandHandler : IHandle<ChangeNameCommand> 
{ 
    ISession session; 

    public ChangeNameCommandHandler(ISession session) 
    { 
     // You could demand an IPersonRepository instead of using the session directly. 
     this.session = session; 
    } 

    public void Handle(ChangeNameCommand command) 
    { 
     var person = session.Load<Person>(command.PersonId); 
     person.ChangeName(command.NewName); 
    } 
} 

El objetivo es que el código que existe fuera de un alcance de sesión/trabajo puede hacer algo como esto:

public class SomeClass 
{ 
    ICommandInvoker invoker; 

    public SomeClass(ICommandInvoker invoker) 
    { 
     this.invoker = invoker; 
    } 

    public void DoSomething() 
    { 
     var command = new ChangeNameCommand(1, "asdf"); 
     invoker.Invoke(command); 
    } 
} 

La invocación del comando implica "hacer este comando en una unidad de trabajo"."Esto es lo que queremos que suceda cuando se invoca el comando:

  1. Comenzar un ámbito anidado COI (la 'unidad de trabajo' alcance)
  2. inicio de una ISession y transacción (esto probablemente se da a entender en el marco de paso 3)
  3. resolver un IHandle<ChangeNameCommand> del ámbito
  4. COI Pasar el comando al controlador (el dominio hace su trabajo)
  5. confirmar la transacción
  6. Terminar el alcance de la COI (la unidad de trabajo)

Así que aquí es un ejemplo usando Autofac como el contenedor IoC:

public class UnitOfWorkInvoker : ICommandInvoker 
{ 
    Autofac.ILifetimeScope scope; 

    public UnitOfWorkInvoker(Autofac.ILifetimeScope scope) 
    { 
     this.scope = scope; 
    } 

    public void Invoke<TCommand>(TCommand command) where TCommand : IDomainCommand 
    { 
     using (var workScope = scope.BeginLifetimeScope("UnitOfWork")) // step 1 
     { 
      var handler = workScope.Resolve<IHandle<TCommand>>(); // step 3 (implies step 2) 
      handler.Handle(command); // step 4 

      var session = workScope.Resolve<NHibernate.ISession>(); 
      session.Transaction.Commit(); // step 5 

     } // step 6 - When the "workScope" is disposed, Autofac will dispose the ISession. 
      // If an exception was thrown before the commit, the transaction is rolled back. 
    } 
} 

Nota: El UnitOfWorkInvoker que he mostrado aquí está violando SRP - es un UnitOfWorkFactory, un UnitOfWork, y un Invoker todo en uno . En mi implementación real, los eché.

+0

¿Quién fue el responsable de llamar a session.SaveOrUpdate? ¿Y quién es responsable de llamar a session.Save o session.Update en caso de que haya asignado Ids? Tampoco sé si es mejor comprometer la transacción para cada comando. En una aplicación web, es común tener una única sesión y transacción por solicitud (preferiblemente cargada/iniciada de forma lenta) y se compromete al final de la solicitud o se revierte si se produce algún error. Dado que la transacción la inicia quien creó la sesión, también es su responsabilidad comprometerse o revertirse, ¿o estoy equivocado? – Loudenvier

+0

@Loudenvier: dependiendo de la aplicación, un patrón de "una transacción por comando" puede no ser apropiado. Los comandos son solo una forma posible de delinear los límites de las transacciones. En mi ejemplo, UnitOfWorkInvoker crea implícitamente la sesión y la transacción al llamar a 'Resolve', por lo que está bien que lo comprometa. Además, la pereza se puede agregar fácilmente (asumiendo Autofac) preguntando al contenedor DI por 'Lazy '. –

+0

Estoy usando también Lazy pero con un enfoque más simple ya que está hecho a medida para aplicaciones web. Creo que funciona bastante parecido al tuyo, pero es menos sofisticado ya que no lo necesito en este momento. Lo que realmente no entendí en su muestra era dónde se llamaría Save o SaveOrUpdate. El ChangeNameCommandHandler solo llama a person.ChangeName() pero no lo guarda, actualiza o guarda en la ventana de actualización.Así que me preguntaba dónde hace un seguimiento de las entidades modificadas/creadas y llama al guardado ... Tal vez el comando en sí debería hacerlo, ¿no? – Loudenvier

1

Cuando uso repositorios, están contenidos dentro de una unidad de trabajo. La unidad de trabajo realiza un seguimiento de los cambios en los repositorios y maneja la gestión de transacciones.

¿Por qué sería válido usar una unidad de trabajo para manejar la gestión de transacciones en una aplicación web y no en una aplicación de Windows? Si se trata de una aplicación N-Tier, la capa de su empresa se compartiría entre ambos.

+0

Estoy de acuerdo con Joel aquí. No veo por qué tiene que ser diferente. Actualmente estoy usando repositorios y paso mi IUnitOfWork \ ISession a cada repositorio. –

+0

el IUnitOfWork en el repositorio Oo ?! – Rookian

+0

y el repositorio y la unidad de trabajo dependen de ISession/IStatelessSession. en la web ambos comparten la sesión dentro de HttpContext. En aplicaciones no web, debe colocar la sesión en su propio diccionario. Sí, y ahora no sé nada ... – Rookian

Cuestiones relacionadas