Sejam Bem Vindos a mais um módulo da nossa série.
Código Modulo 3 - Repositório GitHub
Primeiramente. Olá
Melhorias desse modulo
- CRUD de Usuários
- Introdução do Repository Pattern
- Divisão em 3 Projetos (Infra - Domain - API)
Nesse módulo, irei abordar a evolução do módulo anterior, será muito fácil se perder na nova arquitetura que implementei, pois são vários caminhos percorridos para conseguir executar um end point.
Divisão em 3 Camadas
- API
- Na camada de API colocamos os itens principais do projeto, Controllers, as models de entrada e saída (RequestDTO e ResponseDTO) e as configurações pertinentes a
program.cs(.net 6)
oustartup.cs (.net 5 ou anterior)
.
- Na camada de API colocamos os itens principais do projeto, Controllers, as models de entrada e saída (RequestDTO e ResponseDTO) e as configurações pertinentes a
- INFRA
- Na camada de infraestrutura colocamos toda a infra de banco de dados e repositórios. (Context, Mapeamentos, Repositories)
- DOMAIN
- Na camada de Domain colocamos tudo que é compartilhado entre os outros projetos, as Models, os Services, Utilitários, Interfaces e helpers compartilhados entre os outros projetos. Domain é onde fica toda a regra de negócio da aplicação onde será compartilhado com as outras camadas.
Arquitetura 3 camadas
A arquitetura em 3 camadas serve para dividir as responsabilidades entre 3 projetos, podendo cada um focar em sua parte. Por exemplo, separando a camada de domínio e colocando toda a regra de negócio apenas ali, facilita assim, a manutenção do código, pois você já sabe que cada tecnologia, cada parte de código, está em seu devido lugar.
Como dividir o projeto em 3 camadas?
Criando Biblioteca de classes
Entenda que dividir o projeto em camadas nada mais é do que você criar sua própria biblioteca para consumo interno.
Para fazer a comunicação entre as camadas, é necessário referencia-las como no gif abaixo.
Repare que o projeto domain
, não contem referências, pois na verdade ele não depende de ninguém, são os outros projetos que dependem dele, pois é ali que se concentra toda a regra de negócio que precisa ser consumida pela infraestrutura e pelas controllers no projeto principal.
Repository Pattern
O repository pattern
é um dos padrões de projetos mais utilizamos mundialmente, ele é amplamente conhecido e aceito em projetos de todos os níveis.
Esse padrão de projeto visa a abstração de responsabilidade e encapsulamento do banco de dados, possibilitando a injeção de dependência.
Mas o que isso significa? Basicamente, você pode ter o banco de dados que for, e isso não irá afetar o resto do sistema. Pois agora cada um tem sua responsabilidade, se o repositório é responsável pelo acesso ao banco de dados, então quando tivermos alguma alteração de banco de dados, já sabemos que a alteração é mais provável que seja apenas ali.
Implementação
Primeiro de tudo eu crio o Repositório base, tem muitos exemplos prontos disponíveis pela internet com os métodos já prontos.
Entenda que pra todo repositório, ou serviço criado, você precisa também criar a Interface, que é o método de acesso a essa classe.
Base Repository
É aqui o único lugar onde terá uma estância do DataContext
que é nosso acesso ao Banco de dados.
Os repositórios sempre implementam os métodos definidos na interface dele, e representam de forma genérica uma entidade/model.
public class BaseRepository<T> : IBaseRepository<T> where T : Entity
{
private readonly DbSet<T> DbSet;
private readonly DataContext _context;
protected BaseRepository(DataContext context)
{
_context = context;
DbSet = _context.Set<T>();
}
public async Task<T?> GetById(Guid id) =>
await DbSet.FindAsync(id);
public async Task<IEnumerable<T>> GetAllAsync() =>
await DbSet.ToListAsync();
public async Task<T?> GetOneBy(Expression<Func<T, bool>> expression) =>
await DbSet.AsNoTracking().FirstOrDefaultAsync(expression);
public async Task<IEnumerable<T>> GetListBy(Expression<Func<T, bool>> expression) =>
await DbSet.Where(expression).ToListAsync();
public async Task<bool> Exists(Expression<Func<T, bool>> expression) =>
await DbSet.AnyAsync(expression);
public async Task AddAsync(T entity)
{
await DbSet.AddAsync(entity);
await SaveChanges();
}
public void Update(T entity) =>
DbSet.Update(entity);
public void Remove(T entity) =>
DbSet.Remove(entity);
public async Task<int> SaveChanges()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
_context?.Dispose();
}
}
IBaseRepository Interface
Notem que a interface tem todos os métodos que implementamos no repositório base.
A interface é o método de acesso aos repositórios e serviços.
public interface IBaseRepository<T> : IDisposable where T : Entity
{
Task<T> GetById(Guid id);
Task<IEnumerable<T>> GetAllAsync();
Task<T> GetOneBy(Expression<Func<T, bool>> expression);
Task<IEnumerable<T>> GetListBy(Expression<Func<T, bool>> expression);
Task<bool> Exists(Expression<Func<T, bool>> expression);
Task<int> SaveChanges();
Task AddAsync(T entity);
public void Update(T entity);
public void Remove(T entity);
}
Service Login
Vou exemplificar como funciona o fluxo com o serviço de Login.
Repare que estanciamos o repositório de Login, que lá dentro acessa o repositório base que tem acesso ao banco de dados. Antes de chegar aqui, esse serviço é estanciado na controller através de sua interface.
public class LoginService : ILoginService
{
private readonly ILoginRepository _loginRepository;
private readonly TokenService _tokenService;
public LoginService(ILoginRepository loginRepository, TokenService tokenService)
{
_tokenService = tokenService;
_loginRepository = loginRepository;
}
public async Task<string> LoginAsync(string email, string password)
{
var user = await _loginRepository.GetUser(email);
if (user == null)
return "User or password invalid";
if (!PasswordHasher.Verify(user.PasswordHash, password))
return "User or password invalid";
try
{
var token = _tokenService.GenerateToken(user);
return token;
}
catch
{
return "Internal Error";
}
}
public void Dispose()
{
_loginRepository?.Dispose();
}
}
Controller
Terminando aqui o fluxo reverso de como acessar o banco de dados com o repository pattern, podemos ver que na controller estanciamos a interface do serviço de interesse.
public class AuthController : ControllerBase
{
private readonly ILoginService _loginService;
public AuthController(ILoginService loginService)
{
_loginService = loginService;
}
Colocarei aqui novamente a imagem do fluxo de dados no repository pattern. Da controller você acessa os serviço de interesse, onde tem toda a regra de negócio da entidade em questão, depois acessa o repositório da entidade através da interface, e por fim, acessa o repositório base pra acesso aos dados.
Pra tudo isso funcionar, temos que declarar as injeções de dependências, pois é dessa forma que estamos estanciando uma classe na outra.
Sempre declarar na ordem da esquerda pra direita no fluxo de dados, então primeiro a interface depois sua classe de implementação.
public static IServiceCollection ResolveDependencies(this IServiceCollection services)
{
services.AddDbContext<DataContext>();
services.AddScoped<TokenService>();
services.AddScoped<ILoginService, LoginService>();
services.AddScoped<ILoginRepository, LoginRepository>();
services.AddScoped<IRoleRepository, RoleRepository>();
services.AddScoped<IRoleService, RoleService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<IUserRepository, UserRepository>();
return services;
}
Porque implementar tudo isso?
Temos diversos padrões e designs de arquiteturas a adotar, no fim das contas implemente aquele que mais se encaixa com sua demanda.
Se você vai fazer uma API simples, com 2 models, 1 controller e um crud simples, talvez não faça sentido você aplicar essa complexidade. Mas é uma decisão e se tomar com o seu time de desenvolvimento.
Lembrando os benefícios que você aplicar conceitos como S.O.L.I.D e Repository pattern por exemplo.
- O sistema ser agnóstico de banco de dados, você pode trocar o banco de dados com facilidade sem afetar o resto do código.
- Código centralizado em um único ponto, evitando duplicidade.
- Facilita a implementação de testes. Em caso de testes mais avançados, talvez você não consiga fazer sem adotar uma arquitetura mais organizada.
- Diminui o acoplamento entre classes.
- Padronização de códigos e serviços.
- Facilita a reutilização de código.
Fique ligado no próximo artigo da série, onde vamos adicionar o padrão de UOW - Unity of Work para acesso ao banco de dados.
Confira os módulos anteriores
Top comments (0)