Começando com .NET Core, com Arquitetura em Camadas

Entendendo a arquitetura utilizada

  1. Camada de aplicação: responsável pelo projeto principal, pois é onde será desenvolvido os controladores e serviços da API. Tem a função de receber todas as requisições e direcioná-las a algum serviço para executar uma determinada ação.
    Possui referências das camadas Service e Domain.
  2. Camada de domínio: responsável pela implementação de classes/modelos, as quais serão mapeadas para o banco de dados, além de obter as declarações de interfaces, constantes, DTOs (Data Transfer Object) e enums.
  3. Camada de serviço: seria o “coração” do projeto, pois é nela que é feita todas as regras de negócio e todas as validações, antes de persistir os dados no banco de dados.
    Possui referências das camadas Domain, Infra.Data e Infra.CrossCutting.
  4. Camada de infraestrutura: é dividida em duas sub-camadas
    - Data: realiza a persistência com o banco de dados, utilizando, ou não, algum ORM.
    - Cross-Cutting: uma camada a parte que não obedece a hierarquia de camada. Como o próprio nome diz, essa camada cruza toda a hierarquia. Contém as funcionalidades que pode ser utilizada em qualquer parte do código, como, por exemplo, validação de CPF/CNPJ, consumo de API externa e utilização de alguma segurança.
    Possui referências da camada Domain.

Criando o projeto

Criando uma projeto vazio
Adicionando pastas
Modelo final, após criação das pastas
Criando um projeto do tipo Asp.Net Core Web Application
Selecionando o tipo Web API
Criando projeto do tipo Class Library (.Net Core)
Estrutura final do projeto

Implementando as camadas

Camada Domain

Estrutura da camada de Domain
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Domain/Entities/BaseEntity.cs

namespace Layer.Architecture.Domain.Entities
{
public abstract class BaseEntity
{
public virtual int Id { get; set; }
}
}
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Domain/Entities/User.cs

namespace Layer.Architecture.Domain.Entities
{
public class User : BaseEntity
{
public string Name { get; set; }

public string Email { get; set; }

public string Password { get; set; }
}
}
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Domain/Interfaces/IBaseRepository.cs

using Layer.Architecture.Domain.Entities;
using System.Collections.Generic;

namespace Layer.Architecture.Domain.Interfaces
{
public interface IBaseRepository<TEntity> where TEntity : BaseEntity
{
void Insert(TEntity obj);

void Update(TEntity obj);

void Delete(int id);

IList<TEntity> Select();

TEntity Select(int id);
}
}
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Domain/Interfaces/IBaseService.cs

using FluentValidation;
using Layer.Architecture.Domain.Entities;
using System.Collections.Generic;

namespace Layer.Architecture.Domain.Interfaces
{
public interface IBaseService<TEntity> where TEntity : BaseEntity
{
TEntity Add<TValidator>(TEntity obj) where TValidator : AbstractValidator<TEntity>;

void Delete(int id);

IList<TEntity> Get();

TEntity GetById(int id);

TEntity Update<TValidator>(TEntity obj) where TValidator : AbstractValidator<TEntity>;
}
}

Camada Infra.Data

Estrtura da camada Infra.Data
  • Context: ficará a classe de contexto, responsável por conectar no banco de dados e, também, por fazer o mapeamento das tabelas do banco de dados nas entidades.
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Infra.Data/Context/MySqlContext.cs

using Layer.Architecture.Domain.Entities;
using Layer.Architecture.Infra.Data.Mapping;
using Microsoft.EntityFrameworkCore;

namespace Layer.Architecture.Infra.Data.Context
{
public class MySqlContext : DbContext
{
public MySqlContext(DbContextOptions<MySqlContext> options) : base(options)
{

}

public DbSet<User> Users { get; set; }

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);

modelBuilder.Entity<User>(new UserMap().Configure);
}
}
}
  • Mapping: ficará as classes referente ao mapeamento de cada entidade. Nela realiza-se algumas configurações referente a própria entidade, como, por exemplo, o nome da tabela que vai para o banco de dados, o nome das colunas e qual será a chave primária.
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Infra.Data/Mapping/UserMap.cs

using Layer.Architecture.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Layer.Architecture.Infra.Data.Mapping
{
public class UserMap : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("User");

builder.HasKey(prop => prop.Id);

builder.Property(prop => prop.Name)
.HasConversion(prop => prop.ToString(), prop => prop)
.IsRequired()
.HasColumnName("Name")
.HasColumnType("varchar(100)");

builder.Property(prop => prop.Email)
.HasConversion(prop => prop.ToString(), prop => prop)
.IsRequired()
.HasColumnName("Email")
.HasColumnType("varchar(100)");

builder.Property(prop => prop.Password)
.HasConversion(prop => prop.ToString(), prop => prop)
.IsRequired()
.HasColumnName("Password")
.HasColumnType("varchar(100)");
}
}
}
  • Repository: ficará as classes responsáveis por realizar o CRUD no banco de dados.
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Infra.Data/Repository/BaseRepository.cs

using Layer.Architecture.Domain.Entities;
using Layer.Architecture.Domain.Interfaces;
using Layer.Architecture.Infra.Data.Context;
using System.Collections.Generic;
using System.Linq;

namespace Layer.Architecture.Infra.Data.Repository
{
public class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : BaseEntity
{
protected readonly MySqlContext _mySqlContext;

public BaseRepository(MySqlContext mySqlContext)
{
_mySqlContext = mySqlContext;
}

public void Insert(TEntity obj)
{
_mySqlContext.Set<TEntity>().Add(obj);
_mySqlContext.SaveChanges();
}

public void Update(TEntity obj)
{
_mySqlContext.Entry(obj).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
_mySqlContext.SaveChanges();
}

public void Delete(int id)
{
_mySqlContext.Set<TEntity>().Remove(Select(id));
_mySqlContext.SaveChanges();
}

public IList<TEntity> Select() =>
_mySqlContext.Set<TEntity>().ToList();

public TEntity Select(int id) =>
_mySqlContext.Set<TEntity>().Find(id);

}
}

Camada Infra.CrossCutting

Camada Service

Estrutura da camada Service
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Service/Validators/UserValidator.cs

using FluentValidation;
using Layer.Architecture.Domain.Entities;

namespace Layer.Architecture.Service.Validators
{
public class UserValidator : AbstractValidator<User>
{
public UserValidator()
{
RuleFor(c => c.Name)
.NotEmpty().WithMessage("Please enter the name.")
.NotNull().WithMessage("Please enter the name.");

RuleFor(c => c.Email)
.NotEmpty().WithMessage("Please enter the email.")
.NotNull().WithMessage("Please enter the email.");

RuleFor(c => c.Password)
.NotEmpty().WithMessage("Please enter the password.")
.NotNull().WithMessage("Please enter the password.");
}
}
}
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Service/Services/BaseService.cs

using FluentValidation;
using Layer.Architecture.Domain.Entities;
using Layer.Architecture.Domain.Interfaces;
using System;
using System.Collections.Generic;

namespace Layer.Architecture.Service.Services
{
public class BaseService<TEntity> : IBaseService<TEntity> where TEntity : BaseEntity
{
private readonly IBaseRepository<TEntity> _baseRepository;

public BaseService(IBaseRepository<TEntity> baseRepository)
{
_baseRepository = baseRepository;
}

public TEntity Add<TValidator>(TEntity obj) where TValidator : AbstractValidator<TEntity>
{
Validate(obj, Activator.CreateInstance<TValidator>());
_baseRepository.Insert(obj);
return obj;
}

public void Delete(int id) => _baseRepository.Delete(id);

public IList<TEntity> Get() => _baseRepository.Select();

public TEntity GetById(int id) => _baseRepository.Select(id);

public TEntity Update<TValidator>(TEntity obj) where TValidator : AbstractValidator<TEntity>
{
Validate(obj, Activator.CreateInstance<TValidator>());
_baseRepository.Update(obj);
return obj;
}

private void Validate(TEntity obj, AbstractValidator<TEntity> validator)
{
if (obj == null)
throw new Exception("Registros não detectados!");

validator.ValidateAndThrow(obj);
}
}
}

Camada Application

Criando um controller
Selecionando um tipo de controller
# https://github.com/alexalvess/layer-architecture/blob/main/Layer.Architecture.Application/Controllers/UserController.cs

using Layer.Architecture.Domain.Entities;
using Layer.Architecture.Domain.Interfaces;
using Layer.Architecture.Service.Validators;
using Microsoft.AspNetCore.Mvc;
using System;

namespace Layer.Architecture.Application.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
private IBaseService<User> _baseUserService;

public UserController(IBaseService<User> baseUserService)
{
_baseUserService = baseUserService;
}

[HttpPost]
public IActionResult Create([FromBody] User user)
{
if (user == null)
return NotFound();

return Execute(() => _baseUserService.Add<UserValidator>(user).Id);
}

[HttpPut]
public IActionResult Update([FromBody] User user)
{
if (user == null)
return NotFound();

return Execute(() => _baseUserService.Update<UserValidator>(user));
}

[HttpDelete("{id}")]
public IActionResult Delete(int id)
{
if (id == 0)
return NotFound();

Execute(() =>
{
_baseUserService.Delete(id);
return true;
});

return new NoContentResult();
}

[HttpGet]
public IActionResult Get()
{
return Execute(() => _baseUserService.Get());
}

[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (id == 0)
return NotFound();

return Execute(() => _baseUserService.GetById(id));
}

private IActionResult Execute(Func<object> func)
{
try
{
var result = func();

return Ok(result);
}
catch (Exception ex)
{
return BadRequest(ex);
}
}
}
}

Conclusão

Referências

Bachelor in Computer Science, MBA in Software Architecture and .NET Developer.

Love podcasts or audiobooks? Learn on the go with our new app.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alex Alves

Alex Alves

Bachelor in Computer Science, MBA in Software Architecture and .NET Developer.

More from Medium

Dotnet Test CLI Summary

Select N Item In Each Group With Entity Framework

Easy Modular Monolith — Part 6 — Synchronous communication between modules

Unit Tests a .NET 6 project with Azure Pipelines DotNetCoreCLI@2

GitHub Gist focus image