The general answer is: "Whoever creates the ISession instance must get rid of it. If the transaction has not been completed, this is really a rollback."
I had success using the command template to determine the operation I want to perform on a unit of work. Say we have a Person object, and one of the things we can do is change the person’s name. Start with the object:
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 as follows:
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) {
The goal is that code that exists outside the scope of the session / work 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); } }
Calling a command means "execute this command on a unit of work." This is what we want to do when we invoke the command:
- Start nested IoC scope ("Unit of work" scope)
- Run ISession and Transaction (this is probably implied as part of step 3)
- Allow
IHandle<ChangeNameCommand> from IoC scope - Pass the command to the handler (the domain does its job)
- Commit transaction
- Complete IoC scope (unit of work)
So here is an example of using Autofac as an 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"))
Note. The UnitOfWorkInvoker shown here violates SRP - these are UnitOfWorkFactory , a UnitOfWork and a Invoker all in one. In my real implementation, I violated them.
default.kramer
source share