Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nhibernate: Who is responsible of transaction management in a non web application

Well in a web application a unit of work is responsible for the transaction management.

But what about a windows application?

As far as I know the repository is the connector between my data access layer and my business layer. It hides all the data access stuff from my business layer.

Using this fact let me think of taking all the transaction stuff into the repository.

But I read that having Commit/RollBack methods on the repository is violating the repository's intent.

I ask myself who is responsible for transaction management in a non web application and how do I hide the transaction/Nhibernate stuff from the business layer?

like image 904
Rookian Avatar asked Jul 26 '11 19:07

Rookian


2 Answers

The general answer is "Whoever instantiates the ISession should dispose of it. If the transaction has not been committed, this is effectively a rollback."

I've had success by using the command pattern to define an operation that I want to perform on a unit of work. Say we have a Person entity and one of the things we can do is change a person's name. Let's start with the entity:

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;
    }
}

Define a simple POCO Command like this:

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; }
}

...and a Handler for the command:

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);
    }
}

The goal is that code that exists outside of a Session/Work scope can do something like this:

public class SomeClass
{
    ICommandInvoker invoker;

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

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

The invocation of the command implies "do this command on a unit of work." This is what we want to happen when we invoke the command:

  1. Begin an IoC nested scope (the "Unit of Work" scope)
  2. Start an ISession and Transaction (this is probably implied as part of step 3)
  3. Resolve an IHandle<ChangeNameCommand> from the IoC scope
  4. Pass the command to the handler (the domain does its work)
  5. Commit the transaction
  6. End the IoC scope (the Unit of Work)

So here's an example using Autofac as the IoC container:

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.
    }
}

Note: The UnitOfWorkInvoker I've shown here is violating SRP - it is a UnitOfWorkFactory, a UnitOfWork, and an Invoker all in one. In my actual implementation, I broke them out.

like image 91
default.kramer Avatar answered Oct 23 '22 15:10

default.kramer


When I use repositories, they are contained within a unit of work. The unit of work tracks changes to the repositories and handles transaction management.

Why would it be valid to use a unit of work to handle transaction management in a web application and not in a windows application? If it's an N-Tier application, your business layer would actually be shared between both.

like image 22
Joel C Avatar answered Oct 23 '22 16:10

Joel C