Árvore de páginas

Versões comparadas

Chave

  • Esta linha foi adicionada.
  • Esta linha foi removida.
  • A formatação mudou.

Índice

Introdução

Conexão e seu gerenciamento é um dos conceitos mais importantes de uma aplicação que utiliza um banco de dados relacional. Para disponibilizar essa infraestrutura o TNF utilizamos o pattern Unit Of Work (UoW).

Conexão e Gerenciamento de Transação

O TNF abre uma conexão com o banco de dados e inicia uma transação ao entrar em um método que utiliza o padrão. Assim, você pode usar a conexão com segurança neste método. No final do método, a transação é confirmada e a conexão é descartada. Se o método lançar qualquer exceção, a transação é revertida e a conexão é descartada. Desta forma, uma unidade de trabalho é tida como atômica. o TNF faz tudo isso automaticamente.

Se uma um método que utiliza o UoW chama outro método que também a utiliza, ambos usam mesma conexão e transação. Neste cenário o primeiro método fará o gerenciamento da conexão e da transação.

Uso Convencional

 

Alguns métodos utilizam UoW por default:

 

  • ASP NET Core Controller Actions.
  • Serviços de Aplicação.
  • Todos os Métodos de Repositórios.

 

Vamos utilizar como exemplo o serviço de aplicação abaixo:

 

Bloco de código
languagec#
firstline1
titlePersonAppService.cs
linenumberstrue
public class PersonAppService : IPersonAppService
{
    private readonly IPersonRepository _personRepository;
    private readonly IStatisticsRepository _statisticsRepository;

    public PersonAppService(IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
    {
        _personRepository = personRepository;
        _statisticsRepository = statisticsRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {
        var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
        _personRepository.Insert(person);
        _statisticsRepository.IncrementPeopleCount();
    }
}

No método CreatePerson, estamos inserindo uma pessoa usando o repositório de pessoas IPersonRepository e incrementando a contagem total de pessoas usando o repositório de estatísticas IStatisticsRepository. Ambos os repositórios compartilham mesma conexão e transação neste exemplo, já que o método de serviço de aplicação é a unidade de trabalho por padrão. O TNF abre uma conexão de banco de dados e inicia uma transação ao inserir o método CreatePerson e confirmar a transação no final do método se nenhuma exceção for lançada. Dessa forma, todas as operações de banco de dados no método CreatePerson se tornam atômicas.

Controlando um UoW de Forma Explicita

Utilizamos o UoW implicitamente como vimos acima. Na maioria dos casos, você não precisa controlar o UoW manualmente.

Existem cenários onde você precisa ter o controle do UoW de forma explicita.

 

UnitOfWork Attribute

Bloco de código
languagec#
firstline1
titleCreatePerson Method
linenumberstrue
[UnitOfWork]
public void CreatePerson(CreatePersonInput input)
{
    var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
    _personRepository.Insert(person);
    _statisticsRepository.IncrementPeopleCount();
}

Ao utilizar o atributo acima o controle fica ao critério da sua necessidade em utiliza-lo.

IUnitOfWorkManager

Uma segunda abordagem é a utilização da interface IUnitOfWorkManager, injetando ela em seu serviço:

Bloco de código
languagec#
firstline1
titleMyService.cs
linenumberstrue
public class MyService
{
    private readonly IUnitOfWorkManager _unitOfWorkManager;
    private readonly IPersonRepository _personRepository;
    private readonly IStatisticsRepository _statisticsRepository;

    public MyService(IUnitOfWorkManager unitOfWorkManager, IPersonRepository personRepository, IStatisticsRepository statisticsRepository)
    {
        _unitOfWorkManager = unitOfWorkManager;
        _personRepository = personRepository;
        _statisticsRepository = statisticsRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {
        var person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };

        using (var unitOfWork = _unitOfWorkManager.Begin())
        {
            _personRepository.Insert(person);
            _statisticsRepository.IncrementPeopleCount();
            unitOfWork.Complete();
        }
    }
}

Você pode injetar e usar o IUnitOfWorkManager como mostrado aqui (Algumas classes base já têm UnitOfWorkManager injetado por padrão: Controladores MVC, serviços de aplicação, serviços de domínio). Nesta abordagem, você deve chamar o método Complete manualmente. Se você não chamar, a transação é revertida e as alterações não são salvas. O método Begin tem sobrecargas para definir o comportamento do uso do UoW.

Você pode obter o UoW corrente em um método através da propriedade IUnitOfWorkManager.Current 

Desabilitando um UoW

Você pode querer desativar o UoW para algum método. Para fazer isso, use propriedade IsDisabled do UnitOfWorkAttribute:

Bloco de código
languagec#
firstline1
titleRemoveFriendship Method
linenumberstrue
[UnitOfWork(IsDisabled = true)]
public virtual void RemoveFriendship(RemoveFriendshipInput input)
{
    _friendshipRepository.Delete(input.Id);
}

Em algumas situações você pode precisar desabilita-lo: utilizando com um escopo limitado (UnitOfWorkScope). Observe que se um método com UoW chamar RemoveFriendship, a ação de desativar o UoW será ignorado se ele usar o mesmo UoW.

Trabalhando com UoW Não Transacional

Um UoW é transacional como padrão. Em alguns casos especiais, a transação pode causar problemas, uma vez que pode bloquear algumas linhas ou tabelas no banco de dados (LOCK). Nestas situações, podemos desabilitar a transação através do atributo UnitOfWork:

Bloco de código
languagec#
firstline1
titleGetTasksOutput Method
linenumberstrue
[UnitOfWork(isTransactional: false)]
public GetTasksOutput GetTasks(GetTasksInput input)
{
    var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
    return new GetTasksOutput
	{
    	Tasks = Mapper.Map<List<TaskDto>>(tasks)
	};
}

Observe que estruturas de ORM (como NHibernate e EntityFramework) salva internamente as alterações em um único comando. Suponha que você atualizou algumas Entidades com UoW não-transacional. Mesmo nesta situação, todas as atualizações são executadas no final do UoW com um único comando de banco de dados. Mas se você executar uma consulta SQL diretamente, ela será executada imediatamente e não revertida se sua UoW não for transacional. Há uma restrição para UoWs não-transacionais. Se você já estiver em um escopo de UoW  transacional, a configuração isTransactional para false é ignorada.

Se o seu método apenas lê dados ele pode ser seguramente não-transacional.

Se um método que utiliza UoW chama outro método que utiliza UoW, eles compartilham a mesma conexão e transação. O primeiro método gera a conexão e os outros a utilizam.

Definindo um Scope

Para definir o escopo da transação utilize através do .NET TransactionScopeOption em seu atributo UnitOfWorkAttribute ou IUnitOfWorkManager.

Todo método que utilize UoW automaticamente salva as alterações sem precisar que nenhuma chamada seja feita de forma explicita.

Configurações Default para UoW

 Podemos alterar os valores padrão de todos os UoW na configuração de inicialização em nosso modulo. Isso geralmente é feito no método PreInitialize:

Bloco de código
languagec#
firstline1
titleSimpleTaskSystemCoreModule.cs
linenumberstrue
public class SimpleTaskSystemCoreModule : TnfModule
{
    public override void PreInitialize()
    {
        Configuration.UnitOfWork.IsolationLevel = IsolationLevel.ReadCommitted;
        Configuration.UnitOfWork.Timeout = TimeSpan.FromMinutes(30);
    }
}

Para substituir valores default em um determinado UoW utilize o construtor de atributo UnitOfWork ou o método IUnitOfWorkManager.Begin.

Eventos

Uma unidade de trabalho tem eventos concluídos, falhados e descartados. Você pode registrar esses eventos e executar as operações necessárias.

Por exemplo, você pode querer executar algum código quando um UoW é concluído com êxito. Exemplo:

 

Bloco de código
languagec#
firstline1
titleCreate Task Method Example
linenumberstrue
public void CreateTask(CreateTaskInput input)
{
    var task = new Task { Description = input.Description };

    if (input.AssignedPersonId.HasValue)
    {
        task.AssignedPersonId = input.AssignedPersonId.Value;
        _unitOfWorkManager.Current.Completed += (sender, args) => { /* TODO: Send email to assigned person */ };
    }

    _taskRepository.Insert(task);
}