Home
FunSharp oferece uma abordagem funcional para o tratamento de erros e valores nulos em .Net, através de RailWay Oriented Programming.
1. Motivação
2. Instalação
3. Result<T>
Primeiro, vamos ver como fazemos sem o FunSharp, e como ele pode nos ajudar.
Segue abaixo o exemplo de um método muito comum em qualquer projeto, a obtenção de dados de um repositório:
public class PessoaService
{
// Demais implementações da classe ...
public Pessoa ObterPessoa(int id)
{
try
{
return repository.ObterPessoa(id);
}
catch(Exception ex)
{
return null;
}
}
}
Consumindo o método acima, não há como saber se o resultado é o esperado ou não, ou se houve algum erro. Se o desenvolvedor não verificar o valor retornado, podem ocorrer erros como NullReferenceException
.
var pessoa = pessoaService.ObterPessoa(id);
No caso acima, podem ocorrer 2 situações:
- O objeto Pessoa é retornado com os dados da pessoa;
- O objeto Pessoa é retornado com o valor NULL.
E caso o valor seja NULL, pode ter sido por não existir no banco de dados OU por ter ocorrido uma Exception
.
FunSharp oferece uma interface fluída para que possamos tratar estas questões de uma forma muito simples, usando o pattern Monad. Reescrevendo o método acima, basta você envolver o tipo de retorno em um tipo Res<T>
, no caso, Res<Pessoa>
, ou seja, o resultado da obtenção do objeto Pessoa
:
using FunSharp;
public Res<Pessoa> ObterPessoa(int id)
{
try
{
return repository.ObterPessoa(id);
}
catch(Exception ex)
{
return new Error(ex, "Erro ao obter os dados da pessoa.");
}
}
Se ocorrer uma Exception, basta retornar um objeto Error
, ele será convertido para o tipo Res<Pessoa>
.
Ao consumir o método acima, você pode ter uma lógica para cada situação através de pattern matching:
- Retorno de valor (some);
- Não retorno de valor (none);
- Erro (error);
O código abaixo mostra como consumir o método em um Action de um Controller Asp.Net Core Web API:
public IActionResult Get(int id)
{
return pessoaService.ObterPessoa(id)
.Match<IActionResult>(
some: pessoa => Ok(pessoa),
none: _ => NotFound(),
error: err => BadRequest(err.Message)
);
}
Veja que o método Match
retornou o objeto IActionResult
mais adequado para cada situação, sem o uso de if .. else
, sem o uso de switch
, de forma bastante simplificada e elegante.
Este é apenas um dos inúmeros recursos de FunSharp.
No Package Manager Console (Visual Studio) digite Install-Package FunSharp
e tecle Enter.
Ou clique com o botão direito do mouse sobre o projeto onde será instalado o FunSharp (ou sobre a Solution), depois clique na opção Manage NuGet Packages.... Na aba Browse, digite FunSharp no campo de pesquisa e tecle Enter. Na listagem, clique sobre o FunSharp e no painel lateral direito clique no botão Install.
Dentro da pasta do projeto onde o FunSharp será instalado, digite dotnet add package FunSharp
.
Os tipos Res
e Res<T>
encapsulam o resultado de uma operação sem retorno de valor (Res
) e com retorno de valor (Res<T>
).
Ao invés de void
, usar o tipo Res
como retorno de um método que não retorna valor trará muito mais segurança e clareza.
Ao invés de:
public void InsereUsuarioNoBanco(Usuario usuario)
{
try
{
repository.Insert(usuario);
}
catch (Exception ex)
{
// grava log
}
}
Faça assim com FunSharp:
using FunSharp;
public Res InsereUsuarioNoBanco(Usuario usuario)
{
try
{
repository.Insert(usuario);
return Res.Success();
}
catch (Exception ex)
{
// grava log
return new Error(ex, "Erro ao salvar dados do usuário.");
}
}
E para invocar o método:
var resultado = obj.InsereUsuarioNoBanco(usuario);
var mensagem = resultado.Match<string>(
success: () => "Sucesso",
error: err => err.Message
);
É possível também usar os métodos OnError()
e OnSuccess()
para realizar operações que sejam necessárias em cada um desses contextos, antes do método Match
. Por exemplo, se houver erro, gravar na log de erros:
var mensagem = resultado
.OnError(err => logger.LogError(err.Exception, err.Message))
.Match<string>(
success: () => "Sucesso",
error: err => err.Message
);
Além do exemplo da seção 1. Motivação, vamos criar um outro exemplo e ver o que mais o tipo Res<T>
pode nos oferecer:
using FunSharp;
public Res<string> ObterEndereço(string cep)
{
try
{
return servico.ObterEndereco(cep);
}
catch(Exception ex)
{
return new Error(ex, "Erro ao obter o endereço pelo CEP.");
}
}
// Executando o método e obtendo o resultado
var resultado = ObterEndereco(cep);
Em alguns casos não necessitamos tratar o erro e só queremos saber do valor. Para isso, existe o método GetValueOrElse():
// Se o valor não puder ser obtido, retorna "Endereço não encontrado".
string endereco = resultado.GetValueOrElse("Endereço não encontrado.");
O tipo Res<T>
possibilita encadear execuções usando o método Then(). O método Then() é análogo à função map da maioria das linguagens funcionais: ele recebe uma função que transforma o valor, retornando o valor transformado.