Regras dinâmicas no Domain Model com linguagens de script

Trabalhar com Orientação a Objetos em aplicações comerciais é trabalhoso porque envolve necessariamente outros paradigmas (como bancos de dados relacionais), além de requisitos não funcionais da sua arquitetura (como segurança, concorrência, etc) impedindo uma modelagem pura. Em aplicações que realmente existem lógica de negócios e não apenas um conjunto de cadastros e relatórios, a utilização de uma abordagem com “Domain Driven Design” se faz necessária para a redução da complexidade no coração do software, ou seja, na camada responsável pelo modelo do negócio.

A abordagem de Domain Model proposta por Eric Evans, consegue isolar o núcleo do sistema (responsável pela lógica de manipulação das informações e processos de negócios) abstraindo das particularidades da arquitetura por meio de suas técnicas modernas e maduras. Mesmo assim o esforço de isolamento não é tão claro mesmo para coisas triviais como segurança, onde existe uma linha tênue entre o que é requisito funcional ou não.

Na construção do seu modelo, a profusão de abordagens como Fluent Interfaces e DSL ajudam a maximizar o entendimento do negócio, mas ainda assim restam dúvidas de como e onde aplicar adequadamente.

Confusão

Tenho visto muitos bons desenvolvedores caírem na onda do Hype e se tornarem fanboys “*-tards” e saírem por aí alegando que a linguagem X ou outra coisa qualquer é evolução do Java/C# ou coisas desse tipo, comparando linguagens de abordagens tão distintas entre si e até de paradigmas diferentes. Esse pessoal esquece “o porquê” de existirem tipos estáticos e dinâmicos, fortes e fracos. Uma linguagem funcional não é evolução de uma linguagem orientada a objetos, se bem que o vice-versa é contraditório porque alguns autores alegam que o object calculi seria evolução do lambda-calculi. Linguagens de tipos dinâmicos não são evolução de tipos estáticos, já existiam e servem para propósitos diferentes. Da mesma forma existem JAVAtards que esquecem que existem outras funcionalidades que Java não implementa (lembre-se que você não cria um MapReduce sem conhecimentos profundos de programação funcional).

O ponto principal é que os tipos estáticos são importantes em linguagens imperativas e são importantes na definição da camada de negócio, mas linguagens dinâmicas também o são, pela agilidade nas mudanças e na complexidade da arquitetura. Imagine o custo de um deploy para cada refactoring e mudança em uma arquitetura JEE e compare com o mesmo nível de complexidade de mudança em PHP ou Ruby.

O mais comum ultimamente é a divisão da camada de negócios em duas zonas distintas: uma estática, implementada com uma linguagem estática e fortemente tipada como Java; e uma dinâmica, implementada com o sabor de script que você goste, como ruby, javascript ou python.

Camada estática

Existem princípios em qualquer negócio que por mais ágil que esse negócio seja e mude constantemente, esses princípios nunca mudam, são aquelas funcionalidades facilmente detectadas na modelagem tradicional, onde se detecta o caminho principal de um ator pelo caso de uso e podemos prever os caminhos alternativos.

Alguns pseudoanalistas imaginam que todas as funcionalidades se revelam na modelagem tradicional e se não cobriu todas as possibilidades é porque falhou o processo de desenvolvimento. Essas funcionalidades facilmente detectáveis e controláveis fazem parte da camada estática do seu modelo, não devem mudar nunca ou muito pouco em relação à vida útil do software.

Imagine um sistema contábil, os princípios de crédito e débito, plano de contas, livros contábeis, a própria operação de contabilização a partir de documentos entre outros aspectos são os mesmos há uns 200 anos e mudaram muito pouco nesse tempo todo. Falo sobretudo da contabilidade fiscal. Uma modelagem desses princípios vai sofrer pouca alteração durante toda a vida útil do software.

Imagine o sistema jurídico, é praticamente o mesmo dos romanos. Imagine um sistema de vendas, desde a troca de sal por um carneiro, surgimento da moeda ao cartão de crédito, os princípios são os mesmos.

Para implementar essa camada, uma linguagem de tipos fortes e estáticos é o mais indicado.

Camada dinâmica

Agora imagine toda a contabilidade gerencial que é realizada com base na contabilidade fiscal. Imagine todas as operações possíveis e combinações imaginárias de vendas. Todos os cálculos de impostos que os governos mudam todo ano. A análise e modelagem total dos casos de usos é praticamente impossível e se mostrou inviável ao longo do tempo.

Alguém pode sugerir a utilização de métodos ágeis para solucionar esse problema em linguagens estáticas mas não é o bastante. Os métodos ágeis surgiram para prepararem as equipes para as mudanças, suavizar o impacto que refactoring causam no código. Observem que estamos falando apenas de negócio, puro negócio. Os métodos ágeis não dizem como fazer o seu código, eles te auxiliam.

A abordagem de utilizar uma linguagem dinâmica para esses requisitos mutáveis e tardiamente diagnosticáveis se mostrou eficientíssima. Isolamos parte do Domain Model para ser provido de uma linguagem dinâmica (de script de preferência), e concentramos na facilidade de montar um idioma comum para a equipe sem o custo de build ou deploy de linguagens estáticas.

Segmentação

Dividimos então essa camada dinâmica em duas camadas interiores: uma da própria linguagem dinâmica que facilita as modificações ágeis (aconselhavelmente com Fluent Interfaces); e outra mais rebuscada, criada para a criação de regras (DSL com ou sem Fluent Interfaces) e uma linguagem comum entre a equipe e os operadores do sistema que são geralmente não técnicos mas treinados para isso.

Implementação

Trabalhemos com esse pequeno exemplo, tenho uma operação fecharVenda de uma hipotetica classe Venda que entre suas responsabilidades, existe a geração de comissão do vendedor.

1
2
3
4
5
6
7
public void fecharVenda() {
 ...
	//pre-execução
 	this.vendedor.setComissao(this.valorTotal * (5/100) );
	//pos-execução
...
}

Agora imagine que surgiu uma modificação no sistema, existem dois percentuais diferentes. Isso é moleza, você refatora a operação com a modificação:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void fecharVenda() {
 ...
 
	//pre-execução
	if(this.tipo.equals("VendaTipo1"))
		this.vendedor.setComissao(this.valorTotal * (5/100) );
	}
	if(this.tipo.equals("VendaTipo2"))
		this.vendedor.setComissao(this.valorTotal * (8/100) );
	}
	//pos-execução
...
}

Ah! Mas eu sou esperto, criei uma propriedade lá que amarro o tipo de documento com o percentual apropriado:

1
2
3
4
5
6
7
8
public void fecharVenda() {
 ...
	//pre-execução
	this.vendedor.setComissao(this.valorTotal 
                   * this.vendedor.getPercentualApropriado() );
	//pos-execução
...
}

Daí chega o seu cliente todo fogoso e diz que a regra mudou, agora não é mais no total da nota e sim nos ítens, cada item pode ter um percentual diferente. O caminho mais usual é você refatorar o item e copiar a propriedade para lá, percorrer todos os produtos no método fecharVenda e calcular a comissão, claro, deixando a abordagem atual comentada caso o cliente deseje voltar como era antes.

Usando uma linguagem de Script

Observe no comentário //pre e //pos execução, que acha de injetar uma chamada a um script no método?

Você faria essa última modificação assim em javascript (com Rhino):

Chamada ao Script que será injetado no modelo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine = factory.getEngineByName("JavaScript");
try {
	engine.put("lista", this.itens);
	engine.put("vendedor", this.vendedor);
	engine.put("nf", this);
	engine.put("total", this.total);
	engine.put("totalDescontos", this.totalDescontos);
	String path = NotaFiscal.class.getResource("script.js").getPath();
	engine.eval(new FileReader(path));
	this.total = (Double) engine.get("total");
	this.totalDescontos = (Double) engine.get("totalDescontos");
} catch (ScriptException e) {
	e.printStackTrace();
} catch (FileNotFoundException e) {
	e.printStackTrace();
}

Código no script.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(function() {
	var _$ = function(els) {
		this.elements = [];
		var iterator = els.iterator();
		while(iterator.hasNext()) {
			this.elements.push(iterator.next());
		}
		return this;
	};
	_$.prototype = {
		each: function(fn) {
			for ( var i = 0, len = this.elements.length; i < len; ++i ) {
				fn.call(this, this.elements[i]);
			};
			return this;
		},
		comissionar:function(vendedor) {
			var comissao = function(e) {
				vendedor.comissao += (e.total * e.percentual);
			}
			this.each(comissao);
		}
	};
	$ = function() {
		return new _$(arguments[0]);
	};
}());
$('lista').comissionar();

Fortemente inspirado no código do prototype resolvi o problema temporariamente sem precisar refatorar o código da aplicação e deixei aberto para quantas modificações forem necessárias nessa parte que é variável.

16 thoughts on “Regras dinâmicas no Domain Model com linguagens de script

  1. Felipe Gaúcho

    humm, o exemplo nao convence muito porque você pode usar o padrão Strategy e conseguir a mesma flexibilidade. De qualquer forma, você precisa manter o script ou código “estático”, e supor que uma coisa eh mais leve ou mais fácil que outra não me convence muito 🙂

    Outra: em web-services, a separação entre processamento e camada de visão fica quase implícita a tecnologia. Mesmo que você não queira deixar suas regras codificadas estaticamente, elas serão transportadas via soap ou rest e o sistema vai ser naturalmente heterogêneo 🙂 então não sei ateh onde a tua injeçao de regras vai sobreviver 🙂

    Boas idéias, bom post, mas assunto polêmico… a palestra promete ser muito boa..

  2. Hildeberto

    Se MapReduce é um framework, porque você diz: ” (…) esquecem que existem outras funcionalidades que Java não implementa (lembre-se que você não cria um MapReduce sem conhecimentos profundos de programação funcional).” MapReduce pode ser implementado perfeitamente em Java sem precisar de nenhuma feature adicional: http://en.wikipedia.org/wiki/Hadoop

  3. cmilfont Post author

    @Felipe Gaúcho

    Usar “Strategy” para todas as as possíveis combinações de lógicas é inviáve, vai causar um overpattern. Fiz um exemplo apenas para comissão, mas imagine que na venda eu tenho reflexo na contabilidade, tratamento de descontos, impostos e uma massa d informações em constante mutação.
    A estratégia de usar um script nesse caso agiliza o desenvolvimento por tirar a complexidade do deploy, resolver rapidamente sem mexer na estrutura estática e mesmo assim o código pode passar para a estrutura estática caso seja relevante e detectado que faz parte dela.
    Webservices em um DomainModel? Web Services são consumidos de srviços de ouros sistemas estritamente, porque eu faria webservices no meu domainmodel? Que ganho eu teria nisso?

  4. cmilfont Post author

    @Hildeberto
    MapReduce não é apenas um framework, é baseado em um algoritmo. Algoritmo esse baseado em duas funções de alta ordem que já existiam a Map e a Reduce (ou Fold) e que só quem tem conhecimentos em Lisp ou outra linguagem funcional conseguiria criar já que exige abstração pelo cálculo lambda coisa que um Javer puro não tem.
    Implementar esse tipo de coisa em Java até que não é difícil quando se vai copiar, já que até tail recursion dá para fazer, mas o desempenho não será o mesmo de uma linguagem funcional.

  5. Felipe Gaúcho

    1 – falar em MapReduce nao faz parte da realidade brasileira 🙂

    2 – supor que o deployment vai ficar mais simples por ter scripts eh supor que você nao vai precisar testar a parte estática contra as novas regras editadas no script.. Na prática, alterando qualquer linha do projeto (seja estática ou dinâmica) você precisa executar todos os testes de aceitaçao novamente, então adeus simplicidade 🙂 Jah vi muitos palestrantes sugerirem “eu vou abrir um editor de texto, trocar meia duzia de linhas do meu script e tudo estará atualizado sem a necessidade de compilar código, etc.”.. me parece uma grande falácia quando eu lembro que o codigo vai ter que ser retestado.. então a unica simplicidade eh usar um editor de texto para editar o script ao inves do eclipse para edtar o codigo Java. Depois de editados, ambos os artefatos passarão por uma bateria de testes que nao vao ficar mais simlpes pelo formato dos arquivos 🙂

    Ainda espero alguém que me convença da menor complexidade dos scripts diante de linguagens OO e fortemente tipadas 🙂 Continuo supondo que exista grandes benefícios no uso do script, mas ateh hoje nao apareceu a prova cabal disso 🙂 eheh a menos, claro, se estamos falando no maldito browser e suas gambiarras javascript.. aih nao tem jeito mesmo.. ahah

  6. Rafael Ponte

    Post muito bom, mas por que você complicou tanto o código JavaScript, poderia ter simplificado para o post 🙂 Pára de ser esnobe, rss.. Vê se não complica na palestra de amanhã!

    Pare com essa balela de “somente com linguagem funcional é possível implementar MapReduce”, toda vida você fala isso, rsss.. Acho que você quis dizer que Java não implementa as funções map e reduce que são comumente usadas em linguagens funcionais 🙂

    Enfim, eu gostei do post, excelente qualidade no texto, contudo isso ainda não me convenceu, por incrível que pareça ainda me parece complicado manter um sistema assim, talvez por não haver ainda uma estrutura de organização, não sei.. mas será que não seria mais fácil utilizar-se de Groovy à JavaScript, não? Assim não precisaríamos ficar “populando” as variáveis através daquela engine..

    Parabéns, estou muito ancioso pela palestra de amanhã!

  7. Hildeberto

    Desempenho parece ser o único argumento para MapReduce em linguagem funcional? Hadoop tá implementando isso em Java. Obvio que é melhor fazer em C (e não em C++ para os puristas) se o problema for desempenho. Porque o Google usou C++ mesmo? Deve ter algum objeto lá, não? heheheh

  8. cmilfont Post author

    @Felipe Gaúcho

    “1 – falar em MapReduce nao faz parte da realidade brasileira :)”

    MapReduce foi apenas um exemplo de algo que se cria conhecendo linguagens funcionais, sequer faz parte do foco do Post. Mesmo assim dizer que MapReduce não faz parte da realidade brasileiro voce cai em erro, temos muitas empresas que se preocupam com escalabilidade, imagine quantos milhões de pessoas consultam o BigBrother diariamente, não que o MapReduce esteja envolvido diretamente e sim que soluções de manipualção maciça de dados são da realidade brasileira sim. Mas esquecamos o MapReduce no foco do artigo…

    “2 – supor que o deployment vai ficar mais simples por ter scripts eh supor que você nao vai precisar testar a parte estática contra as novas regras editadas no script.. Na prática, alterando qualquer linha do projeto (seja estática ou dinâmica) você precisa executar todos os testes de aceitaçao novamente, então adeus simplicidade 🙂 ”

    Custo de deploy não tem nada a ver com testes, rodar uma bateria de testes é primordial sim e os testes devem e são feitos no script tambem. Reveja seus conceitos de deploy, o que excutar uma bateria de testes tem a ver com o custo do deploy diretamente?
    Outra coisa, a proposta abordada no artigo são para mudanças imprevisíveis e ágeis, separei bem os conceitos do código estático, aquele que envolve os principios do negocio daquela parte dinamica que são regras gerais de varejo.

    “Jah vi muitos palestrantes sugerirem “eu vou abrir um editor de texto, trocar meia duzia de linhas do meu script e tudo estará atualizado sem a necessidade de compilar código, etc.”.. me parece uma grande falácia quando eu lembro que o codigo vai ter que ser retestado.. então a unica simplicidade eh usar um editor de texto para editar o script ao inves do eclipse para edtar o codigo Java. Depois de editados, ambos os artefatos passarão por uma bateria de testes que nao vao ficar mais simlpes pelo formato dos arquivos :)”

    Abro o Eclipse mesmo para editar os scripts e me beneficio de uma IDE moderna para isso, a simplicidade está na manutenção e agilidade nas mudanças e não com o que se vai alterar.
    Mais uma ves, codigo estatico ou não, a bateria de testes são rodados do mesmo jeito e isso não tem impacto em nenhuma abordagem em relação a diferenciação de custo

    “Ainda espero alguém que me convença da menor complexidade dos scripts diante de linguagens OO e fortemente tipadas 🙂 Continuo supondo que exista grandes benefícios no uso do script, mas ateh hoje nao apareceu a prova cabal disso 🙂 eheh a menos, claro, se estamos falando no maldito browser e suas gambiarras javascript.. aih nao tem jeito mesmo.. ahah”

    Como eu enfatizei no texto, cada linguagem tem seu uso, a construção de um Domain Model com uma linguagem estática é primordial por tudo ue envolve a qualidade de verificação em tempo de compilação, etc. não há nesse ponto concorrência entre linguagens dinamicas e estaticas, o uso das dinamcias seriam nos pontos que a estatica sofre com manutenção e agilidade.

  9. cmilfont Post author

    @Rafael Ponte

    “Post muito bom, mas por que você complicou tanto o código JavaScript, poderia ter simplificado para o post 🙂 Pára de ser esnobe, rss.. Vê se não complica na palestra de amanhã!”

    Pelo contrário, eu usei um trecho de código roubado do prototype e comum a quem conhece algum framework javascript, pode ser difícil de ler para quem não tem conhecimento de javascript da New School.

    “Pare com essa balela de “somente com linguagem funcional é possível implementar MapReduce”, toda vida você fala isso, rsss.. Acho que você quis dizer que Java não implementa as funções map e reduce que são comumente usadas em linguagens funcionais :)”

    Eu não falei em ponto algum que MapReduce é implementado apenas em linguagens funcionais, entenda de uma ves por todas, eu falei que MapReduce é criado por QUEM conhece linguagens funcionais porque exige uma abstração de alguem que já trabalhou com Lisp ou similar. Implementar voce consegue com outra linguagens, mas criar, imaginar só tendo a abstração necessária. Um Javer puro que não conhece linguagens funcionais dificilmente sairia com um algoritmo desses.

    “Enfim, eu gostei do post, excelente qualidade no texto, contudo isso ainda não me convenceu, por incrível que pareça ainda me parece complicado manter um sistema assim, talvez por não haver ainda uma estrutura de organização, não sei.. mas será que não seria mais fácil utilizar-se de Groovy à JavaScript, não? Assim não precisaríamos ficar “populando” as variáveis através daquela engine..”

    Popular variáveis através de que? não entendi.
    O que voce quis dizer com estrutura de organização?
    Groovy a Javascript é questão mais de gosto pessoal.
    Observe que o foco do artigo é no Domain Model, quando existe um, falei claramente que se o seu sistema é uma amontoado de consultar, cadastros e relatórios, não há necessidade aparente de ter um Domain Model e consequente utilizar uma abordagem com linguagens dinâmicas.

    “Parabéns, estou muito ancioso pela palestra de amanhã!”
    Amanhã nos degladiamos ao vivo 🙂

  10. cmilfont Post author

    @Hildeberto

    “Desempenho parece ser o único argumento para MapReduce em linguagem funcional? Hadoop tá implementando isso em Java. Obvio que é melhor fazer em C (e não em C++ para os puristas) se o problema for desempenho. Porque o Google usou C++ mesmo? Deve ter algum objeto lá, não? heheheh”

    Desempenho não é o único argumento para linguagens funcionais. Processamento assíncrono de um loop da função só é feito em linguagens funcionais, não em java.
    Os proprios criadores do MapReduce admitiram que foi pela influência de LISP que criaram o algoritmo, desenlveram em C++ mas se voce observar o Paper não há nenhum objeto lá, não usaram como uma linguagem OO. Aliás Alan kay nem considera C++ uma linguagem adequada para OO 🙂

    Outra coisa, como falei para o @Rafael Ponte e para o @Felipe Gaúcho, MapReduce foi apenas um exemplo de algo que se cria apenas com a abstração necessária para isso e um Javer puro que não conheça linguagens funcionais dificlmente conseguiria, não afirmei em ponto algum que não dê para implementar em outros paradigmas.

  11. Rafael Ponte

    @Christiano Milfont

    “Pelo contrário, eu usei um trecho de código roubado do prototype e comum a quem conhece algum framework javascript, pode ser difícil de ler para quem não tem conhecimento de javascript da New School.”

    Exacto, e quem disse que o código do prototype é simples? Como você disse, o código acima é comum à quem conhece um framework javascript, mas essa não é a realidade da maioria do desenvolvedores do mercado, eu mesmo me perco na New School 🙂 Não é só porque vou utilizar o Spring ou Hibernate que preciso entender o código contido neles, alias, com certeza é algo até complexo e muito extenso de entender, além do mais eu quero somente utilizar o framework, não preciso entendê-lo a fundo.

    “Popular variáveis através de que? não entendi.”

    Através da engine de scripting, engine.put(“lista”, this.itens);

    Com Groovy e até mesmo com JRuby você consegue acessar as classes e objetos Java, assim não há necessidades de usar essa engine ae 🙂 Não sei se é possível diretamente com JavaScript e o Rhino, sabe dizer se é possível?

  12. cmilfont Post author

    @Rafael Ponte

    “Exacto, e quem disse que o código do prototype é simples? Como você disse, o código acima é comum à quem conhece um framework javascript, mas essa não é a realidade da maioria do desenvolvedores do mercado, eu mesmo me perco na New School 🙂 Não é só porque vou utilizar o Spring ou Hibernate que preciso entender o código contido neles, alias, com certeza é algo até complexo e muito extenso de entender, além do mais eu quero somente utilizar o framework, não preciso entendê-lo a fundo.”

    Voce precisa entender os princípios de um framework se quer usá-lo, saber quais patterns ele implementa, na verdade qual o problema que ele resolve, mas isso é assunto que já extrapola essa discussão. O ue importa é criar uma linguagem comum para a equipe em seus diversos niveis, lembre-se que o dominio que essa linguagem atuará é bem específico, a arquitetura como um todo ficará n a linguagem multipropósito como todo mundo tem o costume de usar.

    “Através da engine de scripting, engine.put(”lista”, this.itens);”

    Esse método é para compartilhar variáveis em tempo de execução no mesmo contexto, seja em Jruby, Groovy ou outra linguagem qualquer usando a JSR223

    “Com Groovy e até mesmo com JRuby você consegue acessar as classes e objetos Java, assim não há necessidades de usar essa engine ae 🙂 Não sei se é possível diretamente com JavaScript e o Rhino, sabe dizer se é possível?”

    Veja que o Java que está chamando o script e não o contrário, voce precsa compartilhar as variaveis no mesmo contexto que seu script. Voce deve ta confundindo com a chamada a partir do script a um objeto java como por exmeplo a chamada a uma Util:

    importClass(“java.util.List”);

  13. Rafael Ponte

    @Christiano Milfont

    “Veja que o Java que está chamando o script e não o contrário, voce precsa compartilhar as variaveis no mesmo contexto que seu script. Voce deve ta confundindo com a chamada a partir do script a um objeto java como por exmeplo a chamada a uma Util:

    importClass(”java.util.List”);”

    Não, não.. estou falando de código JavaScript instanciando classes Java, em JRuby (ou mesmo Groovy) teríamos algo como:

    frame = javax.swing.JFrame.new()
    frame.getContentPane().add(javax.swing.JLabel.new(‘Hello, World!’))
    frame.setDefaultCloseOperation(javax.swing.JFrame::EXIT_ON_CLOSE)
    frame.pack()
    frame.set_visible(true)

    Saca? Dá uma olhada em http://en.wikipedia.org/wiki/JRuby
    Com Groovy também é possível, assim conseguimos ter uma “conversação” mais agradável entre os dois paradigmas 🙂

  14. cmilfont Post author

    @Rafael Ponte

    Eu falei sobre isso mesmo, releia minha resposta, colei um trecho de importação para que possa instanciar objetos diretamente como voce exemplificou.
    Agora entenda, preciso trocar referencias entre contextos diferentes já instanciados no java para o script, daí precisa usar a APi da jsr223 para enviar esses valores ao script.

    para clarear, faça uma analogia com Http, preciso botar variaveis no request ou session para compartilhar contextos diferentes:

    uma coisa é voce fazer
    var frame = new JFrame(“”);

    outra é var desconto = valorTotal – 10; // onde esse ValorTotal é uma propriedade de uma classe java fora do contexto do script

  15. Pingback: Christiano via Rec6

Comments are closed.