.NET

19 jun, 2018

Notificações ao invés de exceções .NET Core

Publicidade

Olá, galera!

Ao longo de muito tempo desenvolvendo, vi que para tratar condições de regras de negócios, criamos exceções. Eu mesmo já usei essa estratégia. Ela pode ser mais fácil, mas isso é totalmente prejudicial ao servidor, pois cada exceção é logada no EventViewer como se fosse algo anormal da aplicação.

Precisamos entender que exceções precisam, sim, ser tratadas. Mas como o próprio nome diz: exceções. Regras de negócio de sua aplicação são condições que a aplicação espera que possa acontecer, e por isso não são exceções.

Exemplos de exceções: conexão com o banco de dados não encontrada, conversão de dados errada, objeto não encontrado, etc.

“Um objeto que coleta informações sobre erros e outras informações na camada de domínio e as comunica à apresentação” – Martin Fowler

Para tratar as condições de regras de negócios como notificações, vou explicar uma das funcionalidades da biblioteca MediatR, que utilizamos muito com o pattern CQRS, mas podemos usar parte do pattern para utilizar as notificações.

Vamos lá!

Vamos criar uma WebApi em .NET Core

Criação de API

Para melhorar o nosso entendimento, vamos criar uma camada “Application” para criarmos algumas regras que serão notificadas; uma camada de “Domain” para classes de domínio e uma camada “Common” com classes comuns para a lógica das notificações.

Criando as camadas

Agora vamos instalar o pacote do MediatR nos projetos API e Common.

PM> install-package MediatR

E também instalar o pacote de injeção de dependências do MediatR no projeto da API.

PM> install-package MediatR.Extensions.Microsoft.DependencyInjection

Na camada Common vamos incluir a seguinte estrutura de pastas e classes:

Estrutura de classes

A classe Notifications.cs herdará a interface INotification que marca a classe que será de notificação, e terá as propriedades Key e Value, que vamos utilizar para dizer qual é o tipo da mensagem, e a própria mensagem.

Classe de notificação

public class Notifications : INotification
{
    [JsonProperty(Order = -2)]
    public string Key { get; }

    [JsonProperty(Order = -1)]
    public string Value { get; }

    public Notifications(
        string key,
        string value)
    {
        Key = key;
        Value = value;
    }
}

Classe NotifiyHandler.cs, que será a classe que processará as mensagens de acordo com a interface INotificationHandler, que implementa a classe Notifications.cs.

Classe de controle das notificações

public class NotifiyHandler : INotificationHandler<Notifications>
{
  private List<Notifications> _notifications;

  public NotifiyHandler()
  {
      _notifications = new List<Notifications>();
  }
  public Task Handle(Notifications message, CancellationToken cancellationToken)
  {
      _notifications.Add(message);
      return Task.CompletedTask;
  }

  public virtual List<Notifications> GetNotifications()
  {
      return _notifications.Where(not =>
          not.GetType() == typeof(Notifications)).ToList();
  }

  public virtual bool HasNotifications()
  {
      return GetNotifications().Any();
  }

  public void Dispose()
  {
      _notifications = new List<Notifications>();
  }
}

E a classe Notify.cs que será a classe que irá gerar as mensagens. Quando invocada, executa o handler de notificação implementada na classe NotifiyHandler.cs.

Classe de criação de notificações

public class Notify : INotify
{
    private readonly NotifiyHandler _messageHandler;

    public Notify(INotificationHandler<Notifications> notification)
    {
        _messageHandler = (NotifiyHandler)notification;
    }

    public Notify Invoke()
    {
        return this;
    }

    public bool IsValid()
    {
        return !_messageHandler.HasNotifications();
    }

    public void NewNotification(string key, string message)
    {
        _messageHandler.Handle(new Notifications(key, message), default(CancellationToken));
    }
}

Depois injete as dependências das classes que criamos em Startup.cs no projeto da API, conforme abaixo:

Injetando as classes de notificação

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
        services.AddMediatR(typeof(Startup));

        services.AddScoped<INotificationHandler<Notifications>, NotifiyHandler>();
        services.AddScoped<INotify, Notify>();
        services.AddScoped<IStudentAppService, StudentAppService>();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }

        app.UseMvc();
    }
}

Tendo configurado essas classes, vamos criar uma regra de um POST da API na camada de “Application”. Notificaremos quando o aluno for menor de 18 anos. Na camada “Application”, crie a classe StudentAppService.cs com um método que valida a idade.

Método que valida o aluno e notifica caso a condição seja verdadeira

public class StudentAppService : IStudentAppService
{
    private readonly Notify _notify;

    public StudentAppService(INotify notify)
    {
        _notify = notify.Invoke();
    }

    public Student Add(Student obj)
    {
        if (obj.Age < 18)
        {
            _notify.NewNotification("Student", "Aluno com menos de 18 anos");
            return null;
        }

        return obj;
    }
}

Depois, criamos na API um BaseController com métodos de verificação se existe notificação para ser exibida ao usuário na requisição do POST.

Classe de verificação se existe notificação a ser exibida ao usuário

public abstract class BaseController : Controller
{
    private readonly NotifiyHandler _messageHandler;

    protected BaseController(INotificationHandler<Notifications> notification)
    {
        _messageHandler = (NotifiyHandler)notification;
    }

    protected IActionResult CreatedHasNotification(string route, object result = null)
    {
        if (!HasNotifications())
        {
            if (result != null)
                return Created(route, new
                {
                    success = true,
                    data = result
                });

            return Created(route, new
            {
                success = true
            });
        }

        return NoticationsEntity();
    }

    protected bool HasNotifications()
    {
        return _messageHandler.HasNotifications();
    }

    protected IActionResult NoticationsEntity()
    {
        var notifications =
            _messageHandler.GetNotifications();

        if (notifications.Any())
        {
            return BadRequest(new ApiResponse(HttpStatusCode.BadRequest.ToString(), notifications.ToList()));
        }

        return BadRequest(new ApiResponse(HttpStatusCode.BadRequest.ToString(), notifications.ToList()));
    }
}

Em seguida, crio a controller StudentController.cs, com a chamada do método de validação do aluno.

Controller de Student

[Produces("application/json")]
[Route("api/Student")]
public class StudentController : BaseController
{
    private readonly IStudentAppService _appService;
    public StudentController(INotificationHandler<Notifications> notification,
        IStudentAppService appService) : base(notification)
    {
        _appService = appService;
    }

    [HttpPost]
    public IActionResult Add([FromBody] Student obj)
    {
        return CreatedHasNotification(nameof(Add), _appService.Add(obj));
    }
}

Veja no exemplo que no método POST, que executa o método CreatedHasNotification, que está na classe BaseController.cs, onde verifica se existe notificações ou não. Caso não tenha notificações, retorna o código 201(Created), caso contrário, retorna 404(BadRequest) com a(s) notificação(ões).

Chamada da API pelo postman

Quando executado a API:

Código de retorno
Notificação ao usuário

Veja que é simples usar notificações, e tenha certeza que usando esse pattern, você estará implementando boas práticas, que serão de total benefício ao seu projeto.

Como disse no início deste artigo, use exceções quando realmente for necessário e não para regras de negócio; isso é um anti-pattern.

Espero que tenham gostado. Até a próxima!

Segue o exemplo em meu GitHub: