Estudo de Caso, Programação Orientada a Domínio
Postado por | Categorias Arquitetura de Softwares, Artigos, Artigos de Programação, ColdFusion, Design Pattern, OOP, Programação | Postado em 16-05-2012
Quando falamos de Programação Orientada ao Domínio ou Problema como achar melhor, costuma passar um entendimento meio superficial do assunto.
Então, resolvi escrever de uma forma mais prática sobre o assunto e implementar umas soluções baseadas em Programação Orientada a Domínio.
Para começar vamos definir que teremos uma nova camada em nossa aplicação. E esta camada será chamada de Domain.
Vamos no diagrama abaixo como ficaria nossas camadas de programação.
Na camada Domain ficarão nossos problemas, a ideia é transferir os problemas de implementação da nossa lógica de negócio para a camada de Domínios.
Dessa maneira reduziremos nossos códigos na camada de negócio bruscamente, além de conseguir implementações mais limpas.
Como implementar
Não usaremos framework nenhum e sim, implementaremos nossa camada de domínio com código próprio para entendermos a resolução do problema em si e assim entenderemos muito melhor como funciona esse conceito.
Vamos abordar somente a parte da camada de negócios, não vamos ver consultas em banco propriamente e sim trabalhar com as chamadas e em resultados imaginários. Pois dessa forma conseguiremos um artigo prático e não muito grande.
Vamos começar
Para criarmos nossa prática, vamos criar um cenário de refactoring, onde temos uma camada de lógica e refatoraremos a mesma inserindo o conceito de orientação a domínios.
Nosso problema consiste em uma aplicação de gestão de tarefas, onde múltiplos usuários recebem e executam tarefas diariamente. Então trabalharemos num relatório de tarefas executadas por período, quantidade e nota.
Hoje teríamos em nossa classe de controle Relatoras-gerais um método chamado: carregarTarefasPorPeriodoQuantidadeNotas().
Nosso objetivo é ter tarefas divididas pelos seguimentos contidos no nome do método:
<cfscript>
public function constructor() {
this.instance.factory = Application.factory;
this.tarefasTopDez = [];
this.tarefasTopCinco = [];
this.totalTopDez = 0;
this.totalTopCinco = 0;
}
public function carregarTarefasPorPeriodoQuantidadeNotas( periodoInicial , periodoFinal ) {
var tarefas = this.instance.factory.getService("Tarefas").getTarefasConcluidasPorPeriodo(
periodoInicial : arguments.periodoInicial , periodoFinal : arguments.periodoFinal
);
// primeiro precisamos separar tarefas com quantidade de
// atividades acima de 10 e com notas de avalição igual a 10
loop query="tarefas" {
// carrega tarefasTopDez
if( tarefas.quantidadeAtividades > 10 ) {
if( tarefas.notaAvaliatoria == 10 ) {
this.totalTopDez++;
ArrayAppend( this.tarefasTopDez , this.instance.factory.getService("Tarefas").get( tarefas.id ) );
}
}
// carrega tarefasTopCinco
if( tarefas.quantidadeAtividades == 5 ) {
if( tarefas.notaAvaliatoria == 10 ) {
this.totalTopCinco++;
ArrayAppend( this.tarefasTopCinco , this.instance.factory.getService("Tarefas").get( tarefas.id ) );
}
}
this.totalTopDez = ArrayLen( this.tarefasTopDez );
this.totalTopCinco = ArrayLen( this.tarefasTopCinco );
}
}
</cfscript>
Bom, visto o código acima, entendemos que temos uma lógica dentro do nosso controller, essa lógica na hora de fazer refactoring deve ser abstraída e tratada com um problema. Inicialmente fizemos uma lógica simples, que inclusive poderia ser resolvida no banco, a ideia é demonstrar um problema básico para que o entendimento fique mais claro.
Agora vamos entender como resolver esse problema usando a camada de Domínio.
A ideia de se ter uma camada de Domínio que pode ser um framework, é retirar esse tipo de problemas do código, é torná-lo mais fácil de se entender, mas temos que atentar para: O mais fácil de entender, pode não ser o mais simples de implementar.
Um refactoring não vai garantir que vai reduzir a quantidade de código e arquivos que sua aplicação tem, pelo contrário, a refatoração existe para melhorar a qualidade do seu código, tornando-o mais legível, mais claro e bem estruturado. Além de permitir que a aplicação tenha uma abertura maior para receber manutenção ou até em alguns casos, torna a aplicação acessível a escalabilidade.
Entendemos então com isso, que usar Programação Orientada a Domínios, pode fazer você acabar escrevendo mais, porém vai garantir uma divisão de responsabilidade maior e um domínio do problema.
Para aplicarmos nossa refatoração, precisamos criar alguns métodos privados em nossa classe e transferir alguns recursos para a camada de domínio, vamos ver como funciona.
Domain.cfc
<cfcomponent>
<cfscript>
package Void function construtor() {
this.solucaoProblema = "";
this.objeto = "";
}
/**
* Executa uma resolução de problemas
* @name selecionar
* @return Domain
**/
package Domain function selecionar( objeto , argumentParams ) {
this.objeto = arguments.objeto;
this.solucaoProblema = [];
if( IsArray( objeto ) == true ) {
this.solucaoProblema = this.arrayObjeto( this.objeto , arguments.argumentParams );
}
if( IsQuery( objeto ) == true ) {
solucaoProblema = this.queryObjeto( this.objeto , arguments.argumentParams );
}
return this;
}
/**
* Permite adicionar mais um parâmetro de lógica
* @name and
* @return Domain
**/
package Domain function and( argumentParams ) {
this.select( this.objeto , arguments.argumentParams );
return this;
}
/**
* Define operador Equals
* @name $Eq
* @return String
**/
package String function $Eq( val ) {
return 'EQ|$ ' & arguments.val;
}
/**
* Define operador Not Equals
* @name $Neq
* @return String
**/
package String function $Neq( val ) {
return 'NEQ|$ ' & arguments.val;
}
/**
* Define operador Less Then
* @name $Lt
* @return String
**/
package String function $Lt( val ) {
return 'LT|$ ' & arguments.val;
}
/**
* Define operador Less Then or Equals
* @name $Lte
* @return String
**/
package String function $Lte( val ) {
return 'LTE|$ ' & arguments.val;
}
/**
* Define operador Greater Then
* @name $Gt
* @return String
**/
package String function $Gt( val ) {
return 'GT|$ ' & arguments.val;
}
/**
* Define operador Greater Then or Equals
* @name $Gte
* @return String
**/
package String function $Gte( val ) {
return 'GTE|$ ' & arguments.val;
}
/**
* Soma Total de uma query
* @name SomaQuery
* @return Numeric
**/
package Numeric function somarQuery( objeto ) {
return objeto.recordCount;
}
/**
* Soma Total de um array
* @name somarArray
* @return Numeric
**/
package Numeric function somarArray( objeto ) {
return ArrayLen( objeto );
}
/* ====== PRIVATE METHODS ====== */
/**
* Trata lógicas de objetos do tipo Array
* @name arrayObjeto
* @return Array
**/
private Array function arrayObjeto( objeto , argumentParams ) {
var this.solucaoProblema = [];
this.solucaoProblema = this.realizarOperacao( this.objeto , arguments.argumentParams );
return this;
}
/**
* Define qual tipo de operação será realizada e já a realiza de acordo com o parametro passado
* @name realizarOperacao
* @return Array
**/
private Array function realizarOperacao( objeto , argumentParams ) {
var structParametro = {
indice : ListGetAt( param , 1 , "|$" ) ,
argumento : ListGetAt( param , 2 , "|$" )
};
switch( structParametro.indice ) {
case 'EQ' :
return this.arrayObjetoEqual( objeto , structParametro.argumento );
break;
case 'NEQ' :
return this.arrayObjetoNotEqual( objeto , structParametro.argumento );
break;
case 'LT' :
return this.arrayObjetoLessThen( objeto , structParametro.argumento );
break;
case 'LTE' :
return this.arrayObjetoLessThenOrEqual( objeto , structParametro.argumento );
break;
case 'GT' :
return this.arrayObjetoGreaterThen( objeto , structParametro.argumento );
break;
case 'GTE' :
return this.arrayObjetoGreaterThenOrEqual( objeto , structParametro.argumento );
break;
}
}
/**
* Trata lógicas de objetos do tipo Array usando o operador Equals
* @name arrayObjetoEqual
* @return Array
**/
private Array function arrayObjetoEqual( objeto , argumentParams ) {
var this.solucaoProblema = [];
for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
if( this.objeto[idx] == arguments.argumentParams ) {
AppendArray( this.solucaoProblema , this.objeto[idx] );
}
}
return this.solucaoProblema;
}
/**
* Trata lógicas de objetos do tipo Array usando o operador Not Equals
* @name arrayObjetoNotEqual
* @return Array
**/
private Array function arrayObjetoNotEqual( objeto , argumentParams ) {
var this.solucaoProblema = [];
for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
if( this.objeto[idx] != arguments.argumentParams ) {
AppendArray( this.solucaoProblema , this.objeto[idx] );
}
}
return this.solucaoProblema;
}
/**
* Trata lógicas de objetos do tipo Array usando o operador Less Then
* @name arrayObjetoLessThen
* @return Array
**/
private Array function arrayObjetoLessThen( objeto , argumentParams ) {
var this.solucaoProblema = [];
for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
if( this.objeto[idx] < arguments.argumentParams ) {
AppendArray( this.solucaoProblema , this.objeto[idx] );
}
}
return this.solucaoProblema;
}
/**
* Trata lógicas de objetos do tipo Array usando o operador Less Then or Equals
* @name arrayObjetoLessThenOrEqual
* @return Array
**/
private Array function arrayObjetoLessThenOrEqual( objeto , argumentParams ) {
var this.solucaoProblema = [];
for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
if( this.objeto[idx] <= arguments.argumentParams ) {
AppendArray( this.solucaoProblema , this.objeto[idx] );
}
}
return this.solucaoProblema;
}
/**
* Trata lógicas de objetos do tipo Array usando o operador Greater Then
* @name arrayObjetoGreaterThen
* @return Array
**/
private Array function arrayObjetoGreaterThen( objeto , argumentParams ) {
var this.solucaoProblema = [];
for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
if( this.objeto[idx] > arguments.argumentParams ) {
AppendArray( this.solucaoProblema , this.objeto[idx] );
}
}
return this.solucaoProblema;
}
/**
* Trata lógicas de objetos do tipo Array usando o operador Greater Then or Equals
* @name arrayObjetoGreaterThenOrEqual
* @return Array
**/
private Array function arrayObjetoGreaterThenOrEqual( objeto , argumentParams ) {
var this.solucaoProblema = [];
for( idx = 1; idx <= ArrayLen( this.objeto ); idx++ ) {
if( this.objeto[idx] >= arguments.argumentParams ) {
AppendArray( this.solucaoProblema , this.objeto[idx] );
}
}
return this.solucaoProblema;
}
private Array function castQueryToArray( query ) {
// fazer o cast da query para um array
}
</cfscript>
<!---
* Trata lógicas de objetos do tipo Query
* @name queryObjeto
* @return Query
--->
<cffunction name="queryObjeto" access="private" returntype="query">
<cfargument name="objeto" type="query" required="true" />
<cfargument name="argumentParams" type="string" required="true" />
<cfset var this.solucaoProblema />
<cfquery name="solucaoProblema" dbtype="query">
SELECT * FROM this.objeto WHERE arguments.argumentParams
</cfquery>
<cfreturn this.solucaoProblema />
</cffunction>
</cfcomponent>
Feita nossa classe de Domínio, vamos refatorar nosso controller:
<cfcomponent extends="Domain">
<cfscript>
public function constructor() {
super.construtor();
this.instance.factory = Application.factory;
this.tarefasTopDez = [];
this.tarefasTopCinco = [];
this.totalTopDez = 0;
this.totalTopCinco = 0;
}
public function carregarTarefasPorPeriodoQuantidadeNotas( periodoInicial , periodoFinal ) {
var tarefas = this.instance.factory.getService("Tarefas").getTarefasConcluidasPorPeriodo(
periodoInicial : arguments.periodoInicial , periodoFinal : arguments.periodoFinal
);
this.tarefasTopDez = super.castQueryToArray( super.selecionar( tarefas , this.quantificarMaiorQue(10) )
.and( this.avaliarIgualA(10) )
.solucaoProblema );
this.tarefasTopCinco = super.castQueryToArray( super.selecionar( tarefas , this.quantificarIgualA(5) )
.and( this.avaliarIgualA(10) )
.solucaoProblema );
this.totalTopDez = super.somarArray( tarefasTopDez );
this.totalTopCinco = super.somarArray( tarefasTopCinco );
}
private String function quantificarMaiorQue( parametro ) {
return super.$Gt( arguments.parametro );
}
private String function quantificarIgualA( parametro ) {
return super.$Eq( arguments.parametro );
}
private String function avaliarIgualA( parametro ) {
return super.$Eq( arguments.parametro );
}
</cfscript>
</cfcomponent>
Como vimos acima, concentramos a resolução dos problemas na camada de Domínio e em nosso método publico do controller, teremos somente uma implementação básica deixamos as responsabilidades para métodos privados e/ou para a camada de domínio.
Gente, o conceito é esse, não tem uma regra de como desenvolver e sim o entendimento de que problemas são resolvidos por quem deve resolver problemas e as responsabilidades internas devem ser direcionadas para classes privadas.
Só atentar que sua classe de Domínio deve ser o mais genérica possível, para que lide com vários tipos de situações sem precisar ter uma classe de domínio para cada classe sua.
Lembrando também que a Programação Orientada a Domain, não deve ser somente aplicada na camada de controle… se houver necessidade da mesma em outras camadas, favor usar.
A implementação que fizemos neste artigo, é bem simples, não quis fazer algo muito complexo para não ficar grande demais, por isso usem para estudos e para formar um conceito nas suas cabeças. Na vida real, esse tipo de definição é bem mais complexa.
Não testei os códigos, no momento nem tenho como testá-los.
Grande abraço


