ASP.NET
Core Web API - Clean Architecture - II
![]() |
Hoje vamos criar um projeto ASP.NET Core Web API aplicando o padrão de arquitetura Clean Architecture. |
Continuando o artigo anterior vamos implementar agora os recursos de infraestrutura no projeto Persistence da pasta Infrastructure.
Camada Infrastructure
A camada de
infraestrutura é um componente da Clean Architecture que implementa os detalhes
técnicos de um aplicativo, como acesso a dados, registro, e-mail e outros
mecanismos de comunicação. A camada de infraestrutura interage com sistemas e
tecnologias externas, como bancos de dados, APIs ou serviços em nuvem.
Essa camada deve conter os detalhes de implementação da infraestrutura do
aplicativo, como a implementação de repositórios de acesso a dados ou o sistema
de registro. A camada de infraestrutura não deve conter nenhuma lógica de
negócios ou conhecimento de domínio e deve interagir apenas com a camada de
domínio por meio da camada de aplicativo (Application).
O objetivo da camada de infraestrutura é encapsular os detalhes técnicos do
aplicativo para que possam ser facilmente alterados ou substituídos sem afetar o
restante do aplicativo. Essa camada deve utilizar abstrações e interfaces para
interagir com a camada de aplicação, possibilitando alterar a implementação de
um componente específico sem afetar o restante da aplicação.
Dentro da pasta Infrastructure, haverá uma biblioteca de classes chamada
Persistence que contém a implementação das
interfaces do repositório. O projeto Persistence
deve referenciar o projeto Application.
O projeto Persistence contém as pastas : Context, Repositories e Migrations e o arquivo ServiceExtensions
Na pasta Context temos o arquivo AppDbContext que é o arquivo de contexto que herda de DbContext do EF Core
using
CleanArchitecture.Domain.Entities; using Microsoft.EntityFrameworkCore; namespace CleanArchitecture.Persistence.Context;public class AppDbContext : DbContext{ public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { } public DbSet<User> Users { get; set; } } |
O arquivo de contexto é uma classe que herda da classe DbContext fornecida pelo Entity Framework Core. Essa classe é responsável por representar o banco de dados em código e fornece um ponto de entrada para interagir com o banco de dados por meio de operações de criação, leitura, atualização e exclusão (CRUD) em objetos persistentes.
No construtor da classe AppDbContext, recebemos uma instância de DbContextOptions<AppDbContext> options, que contém informações de configuração para o banco de dados, como a conexão, o provedor de banco de dados, etc. Ao passar essas opções para o construtor da classe base (DbContext), estamos configurando a conexão e outros aspectos do banco de dados.
Dentro do AppDbContext, você define propriedades do tipo DbSet<TEntity>, onde TEntity representa uma entidade de domínio da sua aplicação. Aqui temos a definição de DbSet<User> chamado Users. Esses conjuntos de dados definem como as entidades são mapeadas para tabelas do banco de dados. Você pode usar esses conjuntos de dados para consultar, adicionar, atualizar ou excluir registros no banco de dados associado à entidade correspondente.
Na pasta Repositories temos as implementações das interfaces definidas no projeto Domain:
1- UserRepository
using
CleanArchitecture.Domain.Entities; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Persistence.Context; using Microsoft.EntityFrameworkCore; namespace CleanArchitecture.Persistence.Repositories;public class UserRepository : BaseRepository<User>, IUserRepository{ public UserRepository(AppDbContext context) : base(context) {} public async Task<User> GetByEmail(string email, CancellationToken cancellationToken) { return await Context.Users.FirstOrDefaultAsync(x => x.Email == email, cancellationToken); } } |
Esta classe é um repositório que lida com operações de acesso a dados relacionadas à entidade User. A classe UserRepository herda da classe BaseRepository<User> e implementa a interface IUserRepository.
O construtor aceita uma instância de AppDbContext como parâmetro. Ele chama o construtor da classe base (BaseRepository<User>) passando o contexto do banco de dados. Isso implica que a classe UserRepository está usando a mesma instância do contexto do banco de dados definido na classe AppDbContext para realizar operações de acesso a dados.
Este método é usado para obter um usuário pelo seu endereço de e-mail. Ele faz uma consulta ao banco de dados usando o Context fornecido no construtor. O método FirstOrDefaultAsync é usado para retornar o primeiro usuário que corresponda à condição especificada, que neste caso é que o campo Email do usuário seja igual ao valor fornecido.
O parâmetro cancellationToken é usado para suportar o cancelamento assíncrono, permitindo que a operação seja cancelada caso o token de cancelamento seja acionado.
2- BaseRepository
using
CleanArchitecture.Domain.Common; using CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Persistence.Context; using Microsoft.EntityFrameworkCore; namespace CleanArchitecture.Persistence.Repositories;public class BaseRepository<T> : IBaseRepository<T> where T : BaseEntity{ protected readonly AppDbContext Context; public BaseRepository(AppDbContext context) { Context = context; } public void Create(T entity) { Context.Add(entity); } public void Update(T entity) { Context.Update(entity); } public void Delete(T entity) { entity.DateCreated = DateTimeOffset.UtcNow; Context.Update(entity); } public async Task<T> Get(Guid id, CancellationToken cancellationToken) { return await Context.Set<T>().FirstOrDefaultAsync(x => x.Id == id, cancellationToken); } public async Task<List<T>> GetAll(CancellationToken cancellationToken) { return await Context.Set<T>().ToListAsync(cancellationToken); } } |
A classe BaseRepository<T> é uma classe genérica que fornece operações básicas de acesso a dados, como criar, atualizar, excluir e obter entidades. Ela segue o padrão de repositório na arquitetura Clean Architecture (ou em padrões semelhantes), onde as operações de banco de dados são encapsuladas em um repositório para isolar a lógica de acesso a dados das outras partes da aplicação.
A classe possui uma propriedade protegida Context que é uma instância de AppDbContext, permitindo acesso ao contexto do banco de dados. O construtor aceita uma instância de AppDbContext como parâmetro e a atribui à propriedade Context.
O método
Get é usado para obter uma entidade com base no seu
identificador Guid. Ele utiliza o Set<T>() do
contexto para selecionar o conjunto de entidades T e, em seguida, usa
FirstOrDefaultAsync para buscar a entidade pelo seu
Id; o método GetAll retorna todas as entidades do
tipo T presentes no banco de dados, utilizando Set<T>()
para selecionar o conjunto de entidades e
ToListAsync para buscá-las.
O método Create é usado para adicionar uma nova
entidade ao contexto do banco de dados e o método Update
é usado para marcar uma entidade como modificada no contexto do banco de dados,
para que suas alterações sejam persistidas ao chamar SaveChanges.
O método Delete não realmente exclui a entidade do banco de dados, mas, em vez disso, geralmente marca a entidade como excluída. Neste caso, a propriedade DateCreated da entidade é atualizada com o valor atual da data e hora em UTC antes de chamar Update no contexto.
3- UnitOfWork
public
class
UnitOfWork
: IUnitOfWork { private readonly AppDbContext _context; public UnitOfWork(AppDbContext context) { _context = context; } public async Task Commit(CancellationToken cancellationToken) { await _context.SaveChangesAsync(cancellationToken); } } |
Esta classe implementa a interface IUnitOfWork, que é usada para gerenciar transações e operações em lotes que envolvem múltiplas operações de acesso a dados. O conceito de Unidade de Trabalho visa garantir que várias operações relacionadas ao banco de dados sejam tratadas como uma única transação.
O construtor da classe UnitOfWork recebe uma instância de AppDbContext como parâmetro. Essa instância é usada para acessar o contexto do banco de dados, que é necessário para executar e gerenciar operações de acesso a dados.
O método Commit é responsável por efetivar as alterações pendentes no banco de dados. Ele chama o método SaveChangesAsync do contexto do banco de dados, que aplica todas as alterações realizadas nas operações anteriores de Create, Update, Delete e outras operações semelhantes.
O parâmetro cancellationToken é utilizado para permitir o cancelamento assíncrono, caso seja necessário cancelar a operação de salvamento.
A seguir temos no projeto a classe estática ServiceExtensions que define o método de extensão ConfigurePersistenceApp para a interface IServiceCollection:
4- ServiceExtensions
using
CleanArchitecture.Domain.Interfaces; using CleanArchitecture.Persistence.Context; using CleanArchitecture.Persistence.Repositories; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace CleanArchitecture.Persistence;public static class ServiceExtensions{ public static void ConfigurePersistenceApp(this IServiceCollection services, IConfiguration configuration) { var connectionString = configuration.GetConnectionString("Sqlite");services.AddDbContext<AppDbContext>(opt => opt.UseSqlite(connectionString)); services.AddScoped<IUnitOfWork, UnitOfWork>(); services.AddScoped<IUserRepository, UserRepository>(); } } |
Este é um método de extensão que adiciona configurações relacionadas à persistência à coleção de serviços. Ele aceita dois parâmetros:
Essas linhas de código configuram o contexto do banco de dados (AppDbContext) usando o Entity Framework Core. Elas obtêm a string de conexão chamada "Sqlite" das configurações e a utilizam para configurar o contexto do banco de dados usando o provedor SQLite. Isso significa que a aplicação usará o SQLite como banco de dados.
Aqui, são registradas as implementações de interfaces no contêiner de injeção de dependência:
Esses registros permitem que as classes da aplicação possam depender das interfaces e receber as implementações correspondentes, seguindo o princípio da inversão de controle (IoC) e a injeção de dependência.
Na próxima parte do artigo vamos implementar a camada de apresentação na pasta Presentation.
"Como guardaste a palavra da minha paciência, também eu te guardarei da hora da
tentação que há de vir sobre todo o mundo, para tentar os que habitam na terra."
Apocalipse 3:10
Referências:
C# - Tasks x Threads. Qual a diferença
DateTime - Macoratti.net
Null o que é isso ? - Macoratti.net
Formatação de data e hora para uma cultura ...
C# - Calculando a diferença entre duas datas
NET - Padrão de Projeto - Null Object Pattern
C# - Fundamentos : Definindo DateTime como Null ...
C# - Os tipos Nullable (Tipos Anuláveis)