# Unit of Work

## Contexto e Problema Identificado

Analisando a API atual, observa-se a existência da camada de infraestrutura e dos repositórios, como o `ProductRepository`, que atualmente possui apenas operações de leitura por ID. Para complementar o CRUD, será implementado um método `Create` para criar novos produtos.

### Implementação Inicial do Método Create

No `ProductRepository`, adiciona-se o seguinte método:

```csharp
public async Task CreateAsync(Product product, CancellationToken cancellationToken = default)
{
    await context.Products.AddAsync(product, cancellationToken);    
    await context.SaveChangesAsync(cancellationToken);
}
```

Sua assinatura correspondente na interface `IProductRepository`:

```csharp
Task CreateAsync(Product product, CancellationToken cancellationToken = default);
```

Esta implementação segue o padrão estabelecido anteriormente, mas apresenta um ponto crítico de atenção.

## O Problema do SaveChangesAsync em Métodos Isolados

A chamada explícita de `SaveChangesAsync()` dentro do método `CreateAsync` representa um problema arquitetural significativo.

### Compreendendo o Ciclo de Vida do DbContext

Para entender completamente o problema, é fundamental compreender como o DbContext funciona no contexto de uma requisição HTTP:

* **Instância por requisição**: Para cada requisição aberta, é criada uma instância única do DbContext
* **Escopo de vida**: Esta instância persiste durante toda a duração da requisição
* **Compartilhamento**: O mesmo DbContext é compartilhado entre todos os repositórios utilizados durante a requisição

### Consequências da Persistência Precoce

Quando `SaveChangesAsync()` é executado em um repositório específico (por exemplo, no repositório de produtos), todas as entidades rastreadas por **qualquer repositório** que utilize o mesmo DbContext são afetadas.

**Exemplo Prático**:

1. Um repositório de produto adiciona um novo produto
2. Um repositório de categoria adiciona uma nova categoria
3. Um repositório de usuário atualiza informações do usuário
4. Se o `SaveChangesAsync()` for chamado no repositório de produto, **todas** as três operações serão persistidas no banco de dados simultaneamente

<details>

<summary>Analogia para melhor compreensão</summary>

Imagine que o DbContext é como uma "lista de compras" compartilhada entre vários departamentos de uma loja:

* **Departamento de Eletrônicos** (ProductRepository) adiciona um smartphone
* **Departamento de Móveis** (CategoryRepository) adiciona uma cadeira
* **Departamento de Roupas** (UserRepository) adiciona uma camiseta

Se o funcionário do departamento de Eletrônicos decidir "finalizar a compra" (`SaveChangesAsync`) imediatamente após adicionar o smartphone, todos os itens dos outros departamentos também serão comprados, mesmo que os outros departamentos ainda estejam escolhendo produtos.

O ideal é que todos os departamentos coloquem seus itens na lista primeiro, e somente no final alguém finalize a compra de todos os itens de uma vez.

</details>

### Impacto no Desempenho

Executar `SaveChangesAsync` múltiplas vezes durante uma única requisição resulta em múltiplas viagens ao banco de dados ("descendo ao banco" múltiplas vezes), o que impacta significativamente o desempenho devido à latência de rede e sobrecarga de transações.

## Solução: Padrão Unit of Work

### Reestruturando o Repositório

A solução envolve remover a chamada explícita de `SaveChangesAsync` dos métodos do repositório, transferindo a responsabilidade de persistência para um componente coordenador.

**Repositório modificado**:

```csharp
public async Task CreateAsync(Product product, CancellationToken cancellationToken = default)
    => await context.Products.AddAsync(product, cancellationToken);
```

Agora o método apenas adiciona a entidade ao contexto em memória, sem persistir no banco de dados. Esta abordagem é aplicada a todas as operações do CRUD (Create, Read, Update, Delete).

### Implementando o Caso de Uso Create

Cria-se um novo diretório `Create` no mesmo nível de `GetById` dentro de `UseCases/Products`, contendo os arquivos `Command`, `Handler` e `Response`.

**Response**:

```csharp
namespace CleanArchitectureStore.Application.UseCases.Products.Create;

public sealed record Response(Guid Id, string Title);
```

**Command**:

```csharp
using CleanArchitectureStore.Domain.Abstractions;
using MediatR;

namespace CleanArchitectureStore.Application.UseCases.Products.Create;

public sealed record Command(string Title) : IRequest<Result<Response>>;
```

**Handler**:

```csharp
using CleanArchitectureStore.Domain.Abstractions;
using CleanArchitectureStore.Domain.Entities;
using CleanArchitectureStore.Domain.Repositories;
using MediatR;

namespace CleanArchitectureStore.Application.UseCases.Products.Create;

public sealed class Handler : IRequestHandler<Command, Result<Response>>
{
    private readonly IProductRepository _repository;
    private readonly IUnitOfWork _unitOfWork;
    
    public Handler(IProductRepository repository, IUnitOfWork unitOfWork)
    {
        _repository = repository ?? throw new ArgumentNullException(nameof(repository));
        _unitOfWork = unitOfWork ?? throw new ArgumentNullException(nameof(unitOfWork));
    }
    
    public async Task<Result<Response>> Handle(
        Command request, 
        CancellationToken cancellationToken)
    {
        // Criação da entidade produto
        var product = Product.Create(request.Title);
        
        // Adição ao repositório (apenas em memória)
        await _repository.CreateAsync(product, cancellationToken);
        
        // Persistência de todas as mudanças pendentes
        await _unitOfWork.CommitAsync(cancellationToken);

        return Result.Success(new Response(product.Id, product.Title));
    }
}
```

A injeção de `IUnitOfWork` permite que o handler controle quando as mudanças devem ser persistidas, separando a lógica de negócio da mecânica de persistência.

## Definindo a Abstração Unit of Work

### Interface IUnitOfWork

Cria-se a interface `IUnitOfWork` no projeto Domain, dentro de `Abstractions`:

```csharp
namespace CleanArchitectureStore.Domain.Abstractions;

public interface IUnitOfWork
{
    Task CommitAsync(CancellationToken cancellationToken = default);
}
```

**Observação sobre Rollback**: Em cenários típicos de aplicações web, não é necessário implementar explicitamente métodos de rollback. Quando o DbContext sai do escopo da requisição (seja por conclusão bem-sucedida ou por exceção), todas as mudanças não confirmadas são automaticamente descartadas, não persistindo nenhuma informação indesejada no banco de dados.

### Escopos de Vida de Serviço no .NET

<details>

<summary>Tabela comparativa de escopos de vida</summary>

| Escopo                         | Descrição                                | Tempo de Vida                 | Casos de Uso Típicos                          |
| ------------------------------ | ---------------------------------------- | ----------------------------- | --------------------------------------------- |
| **Transient** (`AddTransient`) | Nova instância criada a cada solicitação | Muito curto (por solicitação) | Serviços stateless, serviços leves, factories |
| **Scoped** (`AddScoped`)       | Mesma instância por requisição           | Duração da requisição HTTP    | DbContext, UnitOfWork, repositórios           |
| **Singleton** (`AddSingleton`) | Única instância por aplicação            | Vida da aplicação             | Caches, configurações, serviços de log        |

**Explicação detalhada**:

* **Transient**: Ideal para serviços que não mantêm estado entre chamadas ou têm custo de criação baixo
* **Scoped**: Padrão para serviços que precisam compartilhar estado durante uma requisição, como transações de banco de dados
* **Singleton**: Usado para serviços que são caros para criar ou precisam compartilhar estado global

No contexto do Unit of Work, o escopo **Scoped** é o mais apropriado, garantindo que todas as operações durante uma requisição compartilhem a mesma transação.

</details>

## Implementação Concreta do UnitOfWork

A implementação concreta reside na camada de infraestrutura, no diretório de dados ao lado de `AppDbContext`:

```csharp
using CleanArchitectureStore.Domain.Abstractions;
using Microsoft.EntityFrameworkCore;

namespace CleanArchitectureStore.Infrastructure.Data;

public class UnitOfWork : IUnitOfWork
{
    private readonly AppDbContext _context;
    
    public UnitOfWork(AppDbContext context)
    {
        _context = context ?? throw new ArgumentNullException(nameof(context));
    }
    
    public async Task CommitAsync(CancellationToken cancellationToken = default)
    {
        // Validações pré-persistência podem ser adicionadas aqui
        await _context.SaveChangesAsync(cancellationToken);
    }
}
```

## Configuração de Dependências

### Registro no Container de DI

A configuração de dependências é realizada na classe `DependencyInjection` da infraestrutura:

```csharp
using CleanArchitectureStore.Domain.Abstractions;
using CleanArchitectureStore.Domain.Repositories;
using CleanArchitectureStore.Infrastructure.Data;
using CleanArchitectureStore.Infrastructure.Repositories;
using Microsoft.Extensions.DependencyInjection;

namespace CleanArchitectureStore.Infrastructure;

public static class DependencyInjection
{
    public static IServiceCollection AddInfrastructure(this IServiceCollection services)
    {
        // UnitOfWork com escopo Scoped para garantir uma instância por requisição
        services.AddScoped<IUnitOfWork, UnitOfWork>();
        
        // Repositórios também como Scoped para compartilhar o mesmo DbContext
        services.AddScoped<IProductRepository, ProductRepository>();
        
        return services;
    }
}
```

**Justificativa dos escopos**: Ambos `IUnitOfWork` e `IProductRepository` são registrados como `Scoped` para garantir que compartilhem a mesma instância de `DbContext` durante uma requisição, permitindo o funcionamento correto do padrão Unit of Work.

## Expondo o Endpoint na API

### Mapeamento da Rota POST

Adiciona-se o mapeamento para criação de produtos na API:

```csharp
app.MapPost("v1/products", async (
    ISender sender,
    CleanArchitectureStore.Application.UseCases.Products.Create.Command command,
    CancellationToken cancellationToken) =>
{
    var result = await sender.Send(command, cancellationToken);

    return result.IsSuccess
        ? Results.Ok(result.Value)
        : Results.BadRequest(result.Error);
});
```

## Testando a Implementação

### Execução da Aplicação

Inicia-se a aplicação e o banco de dados:

```
C:/Dev/.NET/Repository Pattern/CleanArchitectureStore/CleanArchitectureStore.Api/bin/Debug/net10.0/CleanArchitectureStore.Api.exe

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5202
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: C:\Dev\.NET\Repository Pattern\CleanArchitectureStore\CleanArchitectureStore.Api
```

### Teste no Postman

**Requisição POST**:

```http
POST http://localhost:5202/v1/products/
Content-Type: application/json

{
    "title": "Product Unit Of Work 2"
}
```

**Resposta (200 OK)**:

```json
{
    "id": "96e6d858-15a9-4558-982e-d03a94c57a68",
    "title": "Product Unit Of Work 2"
}
```

**Verificação com GET**:

```http
GET http://localhost:5202/v1/products/96e6d858-15a9-4558-982e-d03a94c57a68
```

**Resposta (200 OK)**:

```json
{
    "id": "96e6d858-15a9-4558-982e-d03a94c57a68",
    "title": "Product Unit Of Work 2"
}
```

## Boas Práticas e Considerações Adicionais

### Padrões Recomendados

1. **Injeção via Construtor**: Sempre preferir injeção via construtor sobre propriedades para melhor testabilidade
2. **Tratamento de Exceções**: Implementar tratamento adequado para exceções de persistência
3. **Transações Explícitas**: Para operações complexas, considerar o uso de transações explícitas
4. **Validações de Domínio**: Executar validações antes do commit para evitar exceções do banco de dados

### Exemplo Aprimorado do Handler

```csharp
public async Task<Result<Response>> Handle(
    Command request, 
    CancellationToken cancellationToken)
{
    try
    {
        // Validação de entrada
        if (string.IsNullOrWhiteSpace(request.Title))
        {
            return Result.Failure<Response>(
                new Error("VALIDATION_ERROR", "Product title is required"));
        }
        
        // Criação da entidade
        var product = Product.Create(request.Title);
        
        // Verificação de regras de negócio adicionais
        var validationResult = product.Validate();
        if (!validationResult.IsSuccess)
        {
            return Result.Failure<Response>(validationResult.Error);
        }
        
        // Operação de repositório
        await _repository.CreateAsync(product, cancellationToken);
        
        // Persistência coordenada
        await _unitOfWork.CommitAsync(cancellationToken);
        
        return Result.Success(new Response(product.Id, product.Title));
    }
    catch (DbUpdateException ex)
    {
        // Log e tratamento específico de erros de banco
        return Result.Failure<Response>(
            new Error("PERSISTENCE_ERROR", "Failed to save product to database"));
    }
    catch (Exception ex)
    {
        // Tratamento genérico de exceções
        return Result.Failure<Response>(
            new Error("INTERNAL_ERROR", "An unexpected error occurred"));
    }
}
```

## Conclusão

A implementação do padrão Unit of Work proporciona significativas melhorias arquiteturais:

1. **Coordenação Centralizada**: Todas as operações de persistência são coordenadas por um único ponto
2. **Consistência Transacional**: Garante que múltiplas operações sejam tratadas atomicamente
3. **Otimização de Performance**: Reduz o número de viagens ao banco de dados
4. **Separação de Responsabilidades**: Isola a lógica de persistência da lógica de negócio
5. **Testabilidade Aprimorada**: Facilita a simulação de operações de persistência em testes

A abordagem apresentada segue os princípios da Arquitetura Limpa, mantendo as camadas de aplicação e domínio independentes dos detalhes de implementação de infraestrutura, enquanto fornece um mecanismo robusto para gerenciamento transacional em aplicações empresariais.
