«

»

May 17

Print this Post

Estudo de Caso, Programação Orientada a Domínio

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.

Diagrama

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:

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

Pulo Teixeira
Post original

Permanent link to this article: http://ensina.me/coldfusion/estudo-de-caso-programacao-orientada-a-dominio/

Leave a Reply