Ultimamente estou obcecado em isolar o máximo possível das regras de negócio dentro da camada de domínio. Os anos de desenvolvimento de sistemas data centric com regras de negócios espalhadas pelo código deixaram lembranças ruins e imaginar a possibilidade de perder o controle das regras de negócio não é uma coisa boa.

Mesmo desenvolvendo em uma arquitetura em camadas com DDD algumas regras de negócio podem simplesmente escaparem da camada de domínio se não ficarmos atentos. Isso costuma acontecer quando precisamos aplicar filtros nos dados, pois alguns destes simples filtros podem esconder regras de negócios.

Imagine que uma determinada página no sistema de e-commerce precise ter um filtro para pedidos vencidos. Vamos assumir que a regra para descobrir se um pedido está vencido é a seguinte: são considerados como vencidos os pedidos novos com data maior que 10 dias corridos. Veja que existe aí uma regra de negócio que pode se tornar um simples where em uma consulta LINQ.

// camada de apresentaçãovar 
pedidosVencidos = _pedidoService.ListarTodos().Where(x => x.Status.Equals("Novo") && x.Data <= DateTime.Now.AddDays(-10));

Provavelmente terão outros pontos do sistema em que será necessário filtrar os tais pedidos vencidos e esse simples where vai se propagar para várias camadas.

Um método especializado em nosso serviço de pedidos (PedidoService) isolaria bem esta regra e resolveria bem o problema.

// chamada na camada de apresentação
var pedidosVencidos = _pedidoService.ListarPedidosVencidos();

// método do serviço
public IQueryable<Pedido> ListarPedidosVencidos()
{
  return _pedidoRepository.ListarTodos().Where(x => x.Status.Equals("Novo") && x.Data <= DateTime.Now.AddDays(-10));
}

Essa solução é simples mas pode não ser o suficiente caso haja a necessidade de usar o mesmo filtro na camada de repositório para, por exemplo, escrever uma consulta especializada que retorne dados diretamente para camada de apresentação.

Pensando em uma alternativa eu concluí que este seria um bom cenário para usar métodos de extensão. A ideia é criar métodos de extensão usando Fluent Interface a fim de produzir um código flexível e de fácil leitura.

// método de extensão
public static class PedidosExtensions
{
    public static IQueryable<Pedido> Vencidos(this IQueryable<Pedido> pedidos)
    {
        return pedidos.Where(x => x.Status.Equals("Novo") && x.Data <= DateTime.Now.AddDays(-10));
    }
}

// exemplo de query especializada no repositório que usa o método de extensão
public IQueryable<Pedido> PedidosVencidosAbaixoValorMinimo(decimal valorMinimo)
{
    return _context.Pedidos.Include(x => x.Itens).Vencidos().Where(x => x.Valor < valorMinimo);
}

// método ListarPedidosVencidos reescrito usando o método de extensão (se quiser manter este método no serviço)
public IQueryable<Pedido> ListarPedidosVencidos()
{
    return _pedidoRepository.ListarTodos().Vencidos();
}

Com o uso do método de extensão podemos usar o Filtro de Pedidos Vencidos em qualquer camada do sistema sem que percamos o controle da centralização da regra de negócios e, caso a regra precise ser alterada, basta mudar o método de extensão e nada mais.

Além disso as consultas ficam bem claras sobre o que está sendo filtrado (use nomes expressivos como Vencidos ou Inadiplentes, por exemplo). Se estivermos usando Entity Framework ainda temos o benefício de mandar queries mais específicas para o banco de dados, pois os métodos de extensão recebem e devolvem um IQUeryable de pedidos e portanto não provocam a execução da query prematuramente.

Design e arquitetura de software não são receitas de bolo e acredito que esta aplicação de métodos de extensão é uma boa opção para se ter na manga. Particularmente gosto muito de métodos de extensão porque contribuem muito para um código limpo e bem legível quando bem aplicados.

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *