Como reutilizar regras de negócio de forma eficiente?

2014/09/25

Sistemas complexos dispões de muitas regras de negócio que muitas vezes precisam ser reutilizadas em diferentes módulos, no entanto, não é uma boa prática ficar repetindo código em diferentes lugares de seu código fonte.

Pensando nisso, criei um framework que facilita a criação e o reaproveitamento de regras de négocio, os fontes do mesmo encontram-se disponíveis no meu repositório do GitHub e a documentação está disponível nos meus JavaDocs.

A interface pública do framework é composta pelos seguintes tipos:

Para entender como o framework funciona na prática, veja o exemplo abaixo:

Imagine que uma pessoa possui os seguintes atributos: nome, idade e sexo. Para validar uma pessoa, foram definidas as seguintes regras:

Primeiro definimos a classe Pessoa conforme abaixo:

class Pessoa {
     
    String nome;
    int idade;
    char sexo;

    Pessoa(String nome, int idade, char sexo) {
        this.nome = nome;
        this.idade = idade;
        this.sexo = sexo;
    }
}

Agora criamos três especificações distintas para as regras que validam nome, idade e sexo da Pessoa.

Especificação da validação do nome:

import br.com.staroski.rules.*;

// Especificação da regra que valida o nome de uma Pessoa
class Nome implements Specification {

    public void verify(Pessoa pessoa) throws UnattendedException {
        if (!pessoa.nome.matches("[A-Z]{1}[a-z]+")) {
            throw new UnattendedException("Nome precisa começar com letra maiúscula e ter pelo menos duas letras");
        }
    }
}

Especificação da validação da idade:

import br.com.staroski.rules.*;

// Especificação da regra que valida a idade de Pessoa
class Idade implements Specification {

    public void verify(Pessoa pessoa) throws UnattendedException {
        if (pessoa.idade < 0) {
            throw new UnattendedException("Idade não pode ser negativa");
        }
    }
}

Especificação da validação do sexo:

import br.com.staroski.rules.*;

// Especificação da regra que valida o sexo de uma Pessoa
class Sexo implements Specification {

    public void verify(Pessoa pessoa) throws UnattendedException {
        switch (pessoa.sexo) {
            case 'M':
            case 'F':
                return;
            default:
                throw new UnattendedException("Sexo só pode ser 'M' ou 'F'");
        }
    }
}

Agora ja temos a classe Pessoa e as especificações das regras para nome, idade e sexo criadas. Podemos então utilizar a classe Rule para validar instancias de Pessoa de diversas formas, por exemplo:

import br.com.staroski.rules.*;

public class Exemplo {

    public static void main(String[] args) {
        // instanciamos as regras a partir das especificações 
        Rule nome = Rule.create(new Nome());
        Rule idade = Rule.create(new Idade());
        Rule sexo = Rule.create(new Sexo());

        // criamos uma pessoa com nome, idade e sexo validos
        Pessoa pessoa = new Pessoa("Fulano", 30, 'M');
        // criamos uma regra só que corresponde às três regras: nome, idade e sexo
        // e validamos com um único if
        if (nome.and(idade).and(sexo).isSatisfiedBy(pessoa)) {
            System.out.println("Teste 1");
            System.out.println("O nome, idade e sexo da pessoa atendem as regras\n");
        }

        // criamos uma pessoa com nome, idade e sexo inválidos
        pessoa = new Pessoa("FuLaNo", -1, 'S');
        // criamos uma regra só que corresponde às três regras: nome, idade e sexo
        // armazenamos essa regra numa variável
        Rule regra = nome.and(idade).and(sexo);
        // assim, validamos as três regras, com um único if
        if (regra.not().isSatisfiedBy(pessoa)) {
            System.out.println("Teste 2");
            System.out.println("A pessoa não atendeu às seguintes regras:");
            // se a pessoa não atendeu às regras,
            // usamos a variável declarada para obter os detalhes
            for (String detalhe : regra.getDetails()) {
                System.out.println(detalhe);
            }
        }
    }
}

Executando a classe Exemplo acima, obteremos a seguinte saída:

Teste 1
O nome, idade e sexo da pessoa atendem as regras

Teste 2
A pessoa não atendeu as seguintes regras:
Nome precisa começar com letra maiúscula e ter pelo menos duas letras
Idade não pode ser negativa
Sexo só pode ser 'M' ou 'F'

Assim podemos observar como o uso de regras reutilizáveis podem facilitar a implementação e manutenção de validações nos mais variados sistemas.