DEV Community

Hernani Almeida
Hernani Almeida

Posted on • Updated on

Api Rest .Net completa com JwtToken, integração com api ViaCep utilizando padrão de arquitetura clean architecture

Ferramentas necessárias:

Neste artigo vamos implementar uma api rest utilizando o padrao Clean Architecture, nessa api iremos integrar com uma api externa para buscar dados, configurar e acessar banco de dados postgres fazendo mapeamento e relacionamento de tabelas, criptografar dados para salvar banco de dados e configurar o swagger para realizar a autenticação e o crud em nossa api, bora la.
Para iniciar e necessário fazer o download e instalar o Sdk do .net e a ide Visual Studio Community, links logo acima.
Dentro do terminal digite o comando dotnet --version e devera retornar a versão instalada do sdk que você tem instalada.

Image description

Feito isso abra o Visual Studio e crie um novo projeto api web conforme o passo a passo abaixo.

Image description

Image description

Image description

Image description

Abra esse projeto dentro do visual studio e ele ira ter a seguinte estrutura

Image description

Antes de iniciarmos, ja vamos instalar dependencias que iremos utilizar em nossa aplicação que serão as seguintes

Image description

Instale cada uma delas seguindo o passo a passo abaixo
Clique botão direito na raiz da aplicação e entre em Gerenciar pacotes do nuget

Image description
Na aba que ira abrir vá em Procurar e digite cada dependencia acima para instalar ao projeto conforme abaixo

Image description

Vamos estruturar nossa api utilizando o padrão de arquitetura ´Clean Architecture´ o que nos possibilita ter um sistema coeso e de baixo acoplamento, trabalhando a separação de responsabilidades ao nosso sistema,o que facilita a manutenabilidade de um sistema complexo e robusto.
Para isso vamos ter 3 camadas em nossa api fazendo a separação da mesma nas seguintes estruturas.
Domain Estrutura que será o nosso core, ou seja o núcleo da nossa api, dentro dela era conter o domínio da nossa aplicação e essa camada não depende de nenhuma camada do nosso sistema.
Application Essa camada conterá os casos de usos da nossa aplicação, contendo os serviços que manipulara o nosso domínio para realizar as transações de nossa api.
Observação: Essa camada depende da camada de domínio porem não pode ter nenhuma dependência com a camada seguinte.
Infrastructure Essa camada conterá toda parte de tecnologia/infraestrutura do nosso sistema e será a camada responsável por disponibilizar acesso externo a nossa api.
Observação: Essa camada tem dependência com as outras duas camadas citadas.
Vamos seguir com nossa api, vamos criar 3 pastas em nossa aplicação que ira simbolizar as camadas citadas acima, a estrutura do nosso sistema devera ficar conforme a imagem abaixo.

Image description

Vamos iniciar nossa aplicação definindo nossa camada de Domínio, vamos definir nossa entity Employer conforme abaixo.
Employer

using FirstApi.Domain.ValueObjects;

namespace FirstApi.Domain.Entities
{
    public class Employer
    {
        public int Id { get; set; }
        public string? Nome { get; set; }
        public string? Cargo { get; set; }

        public Payment? Payment { get; set; }
        public double? PaymentTotal { get; set; }

        public void AddPaymentByPremiation(double premiation)
        {
            PaymentTotal = PaymentTotal + premiation;
        }
    }
}


Enter fullscreen mode Exit fullscreen mode

**Observação: **Note que temos o método addPaymentByPremiation em nossa entity, método e responsável por adicionar uma premiação ao pagamento do funcionário, ou seja, nossa classe de domínio não será anêmica.

Vamos definir também em nossa camada de Domínio uma interface com o contrato do nosso repositório ligado a entidade Employer e uma classe Payment que e um value-object da classe Employer.
IEmployerRepository

using FirstApi.Domain.Entities;

namespace FirstApi.Domain.Repositories
{
    public interface IEmployerRepository
    {
        Task<List<Employer>> FindEmployers();
        Task<Employer> FindEmployer(int id);
        Task<Employer> Register(Employer employer);
        Task<Employer> UpdateEmployer(Employer employer);
        void DeleteEmployer(Employer employer);
    }
}

Enter fullscreen mode Exit fullscreen mode

Payment

namespace FirstApi.Domain.ValueObjects
{
    public class Payment
    {
        public double Salary { get; set; }
        public double Benefits { get; set; }

        public double getPaymentTotal()
        {
            return Salary + Benefits;
        }
    }

}

Enter fullscreen mode Exit fullscreen mode

A estrutura do nosso projeto agora ficou conforme a imagem abaixo, nossa camada de Domain é o núcleo da nossa api, portanto, ela não tem dependência com nenhuma outra camada do nosso sistema.

Image description

Seguimos agora para a camada Application que e a responsável pelos serviços e implementação da regra de negocio de nossa api, na Clean Architecture utilizamos UseCases,ou seja, casos de uso para implementar e realizar uma ação em nossa api, nessa ação definimos o que iremos receber de dados em nossa request para realizar a ação e o que vamos retornar após a ação realizada, isso nos da a facilidade para alterarmos, se necessário, tanto os dados de entrada como os de saída da nossa api tornando nossa aplicação resiliente a mudanças.
Para não estender o artigo não vou seguir passo a passo mas o codigo dessa camada pode ser visualizado no repositorio
Nossa camada de Application sera implementado 4 casos de uso ligados a entity Employer e ficara implementado conforme abaixo.

Image description
Observação: Como já citamos antes essa camada tem dependência com a camada de Domain pois utilizamos a entity Employer e a interface IEmployerRepository definida na camada de domínio que será implementada na camada mais externa da nossa aplicação, ou seja, a Infrastructure.
Para realizarmos a injeção da classe IEmployerRepository e dos services de cada casos de uso via construtor temos que adiciona-los ao escopo da nossa aplicação dentro da classe Program.cs, que ficara conforme abaixo.

using FirstApi.Application.UseCases.CasesEmployer.ConsultEmployer;
using FirstApi.Application.UseCases.CasesEmployer.DeleteEmployer;
using FirstApi.Application.UseCases.CasesEmployer.Register;
using FirstApi.Application.UseCases.CasesEmployer.UpdateEmployer;
using FirstApi.Domain.Repositories;
using FirstApi.Infrastructure.Data;
using FirstApi.Infrastructure.Handler;
using FirstApi.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore;

namespace FirstApi
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);

            // Add services to the container.
            builder.Services.AddScoped<IEmployerRepository, EmployerRepository>();
            builder.Services.AddScoped<IRegisterEmployerService, RegisterEmployerService>();
            builder.Services.AddScoped<IUpdateEmployerService, UpdateEmployerService>();
            builder.Services.AddScoped<IConsultEmployerService, ConsultEmployerService>();
            builder.Services.AddScoped<IDeleteEmployerService, DeleteEmployerService>();

            builder.Services.AddControllers();
            // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
            builder.Services.AddEndpointsApiExplorer();
            builder.Services.AddSwaggerGen();

            var app = builder.Build();

            // Configure the HTTP request pipeline.
            if (app.Environment.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI();
            }

            app.UseHttpsRedirection();

            app.UseAuthorization();

            // global error handler
            app.UseMiddleware<GlobalExceptionHandler>();

            app.MapControllers();

            app.Run();
        }
    }
}
A classe EmployerRepository iremos construir na camada de Infrastructure
Enter fullscreen mode Exit fullscreen mode

Após definirmos nossos Usecases vamos seguir agora para a camada seguinte da nossa aplicação, a camada Infrastructure, responsável pela parte tecnológica do nosso sistema e por disponibilizar acesso externo a nossa api, nessa camada fica as classes de Controllers, configurações banco de dados, implementações dos repositórios para acesso a transações no banco de dados, fluxo de mensagerias e etc...
Vamos configurar agora a conexão ao nossa database postgres, vamos criar uma pasta Data e dentro dela uma classe SystemDbContext.cs que ira configurar um DbContext, um tipo de ORM em dotnet.
SystemDbContext

using FirstApi.Domain.Entities;
using FirstApi.Infrastructure.Data.Map;
using Microsoft.EntityFrameworkCore;

namespace FirstApi.Infrastructure.Data
{
    public class SystemDbContext : DbContext
    {
        public SystemDbContext(DbContextOptions<SystemDbContext> options)
            : base(options)
        {

        }

        public DbSet<Employer> Employers { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new EmployerMap());
            base.OnModelCreating(modelBuilder);
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Dentro da pasta Data crie uma nova pasta Map e dentro dela uma classe EmployerMap.cs que ira mapear a tabela Employer para ser criada em nosso database.

using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.EntityFrameworkCore;
using FirstApi.Domain.Entities;

namespace FirstApi.Infrastructure.Data.Map
{
    public class EmployerMap : IEntityTypeConfiguration<Employer>
    {
        public void Configure(EntityTypeBuilder<Employer> builder)
        {
            builder.HasKey(x => x.Id);
            builder.Property(x => x.Nome).IsRequired().HasMaxLength(255);
            builder.Property(x => x.Cargo).IsRequired().HasMaxLength(255);
            builder.Property(x => x.PaymentTotal).IsRequired();
            builder.ToTable("Employers")
                .OwnsOne(x => x.Payment, x =>
                {
                    x.Property(a => a.Salary)
                    .HasColumnName("salary")
                    .IsRequired();
                    x.Property(a => a.Benefits)
                    .HasColumnName("benefits")
                    .IsRequired()
                    .HasDefaultValue(0);
                });
        }
    }
}

Enter fullscreen mode Exit fullscreen mode

Após isso temos que definir nossa string de conexão ao database no arquivo appsetings.Development.json que ficara assim.

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "ConnectionStrings": {
    "DefaultConnection": "Host=localhost;Port=5433;Database=net;Username=admin;Password=password"
  }
}
Enter fullscreen mode Exit fullscreen mode

Vamos configurar na classe main da nossa aplicação Program.cs as configurações de acesso ao banco conforme abaixo.

Image description

Agora vamos rodar dois comandos dentro do console Gerenciador de pacotes que ira criar uma migration e criar nossa tabela dentro do database.
Va no console Gerenciador de pacotes que entre com o comando Add-Migration InitialDB -Context SystemDbContext

Image description

Danto tudo certo, em seguida entre com o comando Update-Database -Context SystemDbContext

Image description
Crie uma pasta Repositories e dentro dela a classe EmployerRepository que ira implementar a classe IEmployerRepository definida em nosso dominio.

using FirstApi.Domain.Entities;
using FirstApi.Domain.Repositories;
using FirstApi.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

namespace FirstApi.Infrastructure.Repositories
{
    public class EmployerRepository : IEmployerRepository
    {
        private readonly SystemDbContext _Dbcontext;

        public EmployerRepository(SystemDbContext context)
        {
            _Dbcontext = context;
        }

        async Task<Employer> IEmployerRepository.Register(Employer employer)
        {
            await _Dbcontext.Employers.AddAsync(employer);
            _Dbcontext.SaveChanges();
            return employer;
        }

        async void IEmployerRepository.DeleteEmployer(Employer employer)
        {
            _Dbcontext.Employers.Remove(employer);
            await _Dbcontext.SaveChangesAsync();
        }

        async Task<Employer> IEmployerRepository.UpdateEmployer(Employer employer)
        {
            _Dbcontext.Employers.Update(employer);
            await _Dbcontext.SaveChangesAsync();
            return employer;
        }

        async Task<Employer> IEmployerRepository.FindEmployer(int id)
        {
            return await _Dbcontext.Employers.FirstOrDefaultAsync(u => u.Id == id);
        }

        async Task<List<Employer>> IEmployerRepository.FindEmployers()
        {
            return await _Dbcontext.Employers.ToListAsync();
        }

    }
}

Enter fullscreen mode Exit fullscreen mode

Crie agora uma pasta Controllers e dentro a classe EmployerController.cs que será a responsável por disponibilizar acesso externo a nossa api via chamadas Http e ficara conforme abaixo.

using FirstApi.Application.UseCases.CasesEmployer;
using FirstApi.Application.UseCases.CasesEmployer.ConsultEmployer;
using FirstApi.Application.UseCases.CasesEmployer.DeleteEmployer;
using FirstApi.Application.UseCases.CasesEmployer.Register;
using FirstApi.Application.UseCases.CasesEmployer.UpdateEmployer;
using FirstApi.Domain.Entities;
using Microsoft.AspNetCore.Mvc;

namespace FirstApi.Infrastructure.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class EmployerController
    {
        private  IRegisterEmployerService _registerService;
        private IUpdateEmployerService _updateService;
        private IConsultEmployerService _consultService;
        private IDeleteEmployerService _deleteService;

        public EmployerController(
            IRegisterEmployerService service,
            IUpdateEmployerService updateService,
            IConsultEmployerService consultService,
            IDeleteEmployerService deleteService)
        {
            _registerService = service;
            _updateService = updateService;
            _consultService = consultService;
            _deleteService = deleteService;
        }

        [HttpPost]
        public RegisterEmployerOutput Post([FromBody] RegisterEmployerInput input)
        {
            return _registerService.Execute(input);
        }

        [HttpPut]
        public UpdateEmployerOutput Put([FromBody] UpdateEmployerInput input)
        {
            return _updateService.Execute(input);
        }

        [HttpGet("{id}")]
        public ConsultEmployerOutput findEmployer(int id)
        {
            return _consultService.FindEmployerById(id);
        }

        [HttpGet]
        public List<ConsultEmployerOutput> findEmployers()
        {
            return _consultService.FindEmployers();
        }

        [HttpDelete("{id}")]
        public string Delete(int id)
        {
            return _deleteService.DeleteEmployer(id);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Nossa camada Infrastructure ficara assim.

Image description

Crie um arquivo com o nome docke-compose.yaml na raiz do projeto, vamos configurar esse arquivo para subir um DB Postgres no docker.

version: '3'

services:

 postgres:
   image: 'postgres:alpine'
   volumes:
     - postgres-volume:/var/lib/postgresql/data
   ports:
     - 5433:5432
   environment:
     POSTGRES_USER: admin
     POSTGRES_PASSWORD: password
     POSTGRES_DB: net
volumes:
  postgres-volume:
Enter fullscreen mode Exit fullscreen mode

Na pasta raiz rode o comando docker-compose up -d conforme abaixo para subir o database no docker.

Image description

Image description

Vamos rodar nossa aplicação clicando nesse botao que fica na parte de cima do visual studio

Image description
E já podemos ver o swagger da nossa aplicação.

Image description

Vamos testar nossa aplicação, primeiro vou realizar um registro

Image description
Agora vou buscar esse registro pelo Id

Image description

Para seguirmos o projeto vamos ter a segunda parte desse artigo, onde vamos criar um fluxo para salvar um usuário obtendo seus dados de Endereço através de uma integração da nossa api com a api ViaCep, ate la.

Parte 2

linkedin
github

Top comments (0)