Chain of Responsibility e ASP.Net Core
Tags: csharp, dotnet, programming, braziliandevs
Published: 2021-11-22 00:19:15

Olá!

Este é mais um post da seção **Design**, e nele vamos tratar de um pattern bastante útil em cenários com múltiplas condições, o Chain of Responsibility (CoR, ou Cadeia de Responsabilidade, em tradução livre). Veremos também como integrá-lo ao container de injeção de dependência do ASP.Net Core.

Vamos lá!

## O Problema

Antes de mais nada, precisamos entender qual a utilidade do pattern, ou seja, qual problema ele resolve. Patterns são soluções cabíveis para um dado tipo de problema, e com o CoR não é diferente.

Imagine um cenário onde, para atender a uma dada requisição (ou comando) a satisfação de diversas condições seja necessária e que, para cada condição, pode haver um dado processamento específico a ser realizado ou um tipo de resultado a ser retornado.

Soa estranho? Explico.

Vamos imaginar um caixa eletrônico e sua função de saque. Para permitir o saque, o caixa eletrônico precisa validar se há saldo em conta disponível, se há o montante solicitado disponível no compartimento de notas, se há alguma limitação no valor do saque por horário etc.

Uma implementação ingênua seria mais ou menos assim:

```c#
public (bool, string) Withdraw(WithdrawalRequest request)
{
    if(request.Amount == 0)
        return (false, "Please fill a valid positive amount to withdraw.");
    
    var account = _accountRepository.Get(request.AccountNumber);
    if(!account.HasAmount(request.Amount))
        return (false, "There is not enough balance for this withdraw.");
    
    if(!_billStorage.HasAmount(request.Amount))
        return (false, "There aren't enough bills for this withdraw.");
    
    if(_withdrawRestrictionService.ShouldRestrictWithdraw(request.Amount, DateTime.Now))
        (false, "The amount informed is greater than allowed at this time.");
 
    _billStorage.Withdraw(request.Amount);
    return (true, "Sucessful withdrawal.");
}
```

Agora, você pode estar se perguntando: por quê está implementação é ingênua?

Por dois motivos:

1. Quanto mais condições forem adicionadas à esta operação, maior o método vai se tornar.
2. Quanto mais dependências forem necessárias para atender a estas condições, maior será a carga cognitiva para lidar com todas elas.

Vejamos a seguir como o CoR pode nos ajudar a lidar com estas questões.

## O Pattern

O pattern sugere que, para cada condição a ser atendida para uma requisição ou comando, tenhamos um *handler*, um tipo responsável por validá-la, e que este contenha uma referência a outro *handler*, que será o próximo da cadeia, para encaminhar esta requisição caso não haja razão para interceptá-la e tratá-la.

> **Nota**: neste post, sugiro uma abordagem diferente da canônica para a aplicação do pattern. Um exemplo da abordagem canônica pode ser encontrado no [Refactoring Guru](https://refactoring.guru/design-patterns/chain-of-responsibility/csharp/example) (em inglês).

Como precisaremos de um *handler* para cada condição, e todos estão sujeitos ao mesmo procedimento, ou seja, recebem a mesma requisição e retornam um mesmo tipo de resultado, podemos estabelecer um contrato que represente este comportamento. Vejamos abaixo:

```c#
public interface IHandler<TRequest, TResult>
{
	public bool ShouldHandle(TRequest request);
    public TResult Handle(TRequest request);
}
```

Aqui temos dois métodos: um que vai verificar se o *handler* em questão deve interceptar a requisição recebida; e outro que manipula a requisição de fato, interceptando-a.

> **Nota**: uma abordagem alternativa é tornar os dois métodos assíncronos, em uma segunda interface chamada *IAsyncHandler*, e por um bom motivo: nem sempre o que vai determinar se a requisição deve ou não ser interceptada depende da validação de seu próprio estado. Há situações onde uma operação, como um I/O, precisa acontecer para fazer esta verificação e, para estes casos, um método assíncrono é muito bem-vindo!

Com estes dois métodos, atendemos à primeira porção do pattern, que cada handler saiba se é responsável ou não por interceptar e tratar uma dada requisição e, em caso positivo, que a manipule em seguida.

Agora precisamos atender à segunda porção, precisamos guardar uma referência para o próximo *handler*, e garantir que todos os *handlers* que implementarmos seguirão a mesma lógica de verificação e manipulação. Para isso, vamos usar uma classe abstrata que implementa nosso contrato:

```c#
public abstract class HandlerBase<TRequest, TResponse> : IHandler<TRequest, TResponse>
{
    private readonly IHandler<TRequest, TResponse> _next;
    
    public HandlerBase(IHandler<TRequest, TResponse> next) =>
        _next = next;
    
    public abstract bool ShouldHandle(TRequest request);
    
    public TResponse Handle(TRequest request)
    {
  		if(ShouldHandle(request))
            return HandleCore(request);
        
         return _next.Handle(request);
    }
    
    protected abstract TResponse HandleCore(TRequest request);
}
```

Agora temos garantido o seguinte comportamento: se a requisição puder ser manipulada pelo *handler* atual, ela o será. Caso contrário, será encaminhada ao *handler* seguinte.

Com isso, podemos implementar um *handler* para cada condição de nosso método de saque. Vamos a um exemplo:

```c#
public class BillStorageHandler : HandlerBase<WithdrawalRequest, WithdrawalResult>
{
    private readonly BillStorage _billStorage;
    
    public BillStorageHandler(WithdrawHandler next,
                              BillStorage billStorage) : base(next)
    {
        public override bool ShouldHandle(WithdrawalRequest request) =>
            !_billStorage.HasAmount(request.Amount);
        
        protected override WithdrawalResult HandleCore(WithdrawalRequest request) =>
            WithdrawalResult.Fail("There aren't enough bills for this withdrawal.");
    }
}
...
public class WithdrawHandler : HandlerBase<WithdrawalRequest, WithdrawalResult>
{
    private readonly BillStorage _billStorage;
    
    public WithdrawHandler(BillStorage billStorage) : base(null) =>
        _billStorage = billStorage;
        
    public override bool ShouldHandle => true;
    
    protected override WithdrawalResult HandleCore(WithdrawalRequest request)
    {
        _billStorage.Withdraw(request.Amount);
        return WithdrawalResult.Ok("Successful withdrawal.");
    }
}
```

Repare em dois detalhes importantes na implementação acima: 

1. O *handler* `BillStorageHandler` recebe uma instância de `WithdrawHandler` em seu construtor, e o guarda como o próximo da cadeia. Este é um detalhe importante porque injetar a interface `IHandler<TRequest, TResult>`, ou a classe abstrata `HandlerBase<TRequest, TResult>`, além de mais verboso, impede a identificação do próximo *handler* da cadeia. Recebendo a especialização por injeção, fica mais claro qual é o próximo passo caso a requisição não deva ser manipulada por este *handler*.
2. O *handler* `WithdrawalHandler` informa `null` como próximo *handler* da cadeia, e sempre retorna `true` em seu método `ShouldHandle`. Isso acontece porque ele é o último nó da cadeia. Fixando o retorno `true` em `ShouldHandle` há a garantia de que a requisição sempre receberá um tratamento ao final da cadeia.

## Injeção de Dependência

Aqui precisamos falar sobre a abordagem canônica do *pattern* e o motivo pelo qual ela foi evitada neste post. A abordagem canônica sugere que na interface `IHandler<TRequest, TResponse>` haja um método chamado `SetNext`, onde seria passada por parâmetro a instância do próximo *handler*, permitindo assim a seguinte declaração:

```c#
var billStorageHandler = new BillStorageHandler(...);
billStorageHandler.SetNext(new WithdrawHandler(...));
```

O problema com esta abordagem é que a inversão de controle é inviabilizada, e qualquer dependência de quaisquer dos *handlers* precisariam ser instanciadas a priori de sua criação, impedindo os ganhos oferecidos pelo contêiner de injeção de dependência.

Com a abordagem proposta neste post, a declaração se torna bastante simplificada, como o seguinte exemplo:

```c#
public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<BillStorage>()
            .AddScoped<IHandler<WithdrawalRequest, WithdrawalResult>, BillStorageHandler>()
            .AddScoped<WithdrawalHandler>();
}
```

> **Nota**: repare que ao registrar o *handler* `BillStorageHandler` foi informada a interface `IHandler<TRequest, TResult>`. Essa declaração, opcional, é uma forma de anonimizar o primeiro *handler* na classe onde a cadeia será invocada, se desejado. Desta forma, caso o primeiro *handler* da cadeia precise ser substituído, não haverá a necessidade de se modificar a classe que consumirá a cadeia. 

Com isso temos todas as nossas dependências registradas e podemos refatorar nosso método de saque:

```c#
public class WithdrawalProcessor
{
	private readonly IHandler<WithdrawalRequest, WithdrawalResult> _handler;
	
    public WithdrawalProcessor(IHandler<WithdrawalRequest, WithdrawalResult> handler) =>
        _handler = handler;
    
    public WithdrawalResult Withdraw(WithdrawalRequest request) =>
        _handler.Handle(request);
}
```

Muito mais simples. Não? Não há mais uma sequência potencialmente infinita de condicionais, as dependências agora são injetadas em cada *handler*, deixando nosso processador de requisições mais leve e limpo, e o código foi bastante enxugado, tornando sua compreensão e manutenção mais simples.

## Conclusão

O Chain of Responsibility torna muito mais simples lidar com situações que demandam múltiplas condicionais, e que podem, ou não, resumir o fluxo de uma dada requisição ou comando. É um acessório muito útil e que pode ser usado em diversas situações, desde validações a execução de procedimentos.

Gostou? Me deixe saber pelos comentários ou por minhas redes sociais.

Muito obrigado pela leitura, e até a próxima!