Type erasure com GenericEntity

22 respostas
E

Olá pessoal..

Estou montando uma estrutura genérica aqui envolvendo a arquitetura RESTful com a API do Jersey (cliente e servidor) e um dos métodos que tenho retorna uma lista (List). Porém para conseguir pegar esta lista no lado do cliente eu preciso de um GenericEntity> e preciso informar este tipo explícito (tal como GenericEntity>, por exemplo) pois este tipo deve ser conhecido em tempo de compilação. Como posso achar uma solução para isso?

Detalhe: Eu possuo o class deste "Produto", por isso consigo retornar tipos T (pois para a operação REST ser feita eu passo este clazz como tipo de retorno). Vou explicar melhor com um trecho de código (atenção para a linha 13):
public <E> E invokeAdd(String methIdent, E bean) {
		
		ClientResponse cr = null;
		
		try {
			
			cr = wsClient.ws(clazz.getSimpleName())
					.path(methIdent)
					.header(TOKEN_KEY, TOKEN_VALUE)
					.type(MediaType.APPLICATION_JSON)
					.post(ClientResponse.class, bean);

			return cr.getEntity((Class<E>)clazz); // esta clazz é uma variável de instância que é a class do "E" que estou retornando
			
		}catch (Exception e) {
			throw buildClientException(e, cr);
		}
	}
Porém para retorno de listas é um pouco diferente:
public <E> List<E> invokeList(String methIdent, E bean, Object... urlParams) {
		
		ClientResponse cr = null;
		
		try {
			
			WebResource wr = buildWebResource(methIdent, urlParams);

			cr = wr.header(TOKEN_KEY, TOKEN_VALUE)
					.type(MediaType.APPLICATION_JSON)
					.put(ClientResponse.class, bean);
			
			// o problema está aqui pois o GenericType não sabe de que tipo se trata
			return cr.getEntity(new GenericType<List<E>>() {});

		}catch (Exception e) {
			throw buildClientException(e, cr);
		}
	}
Ou seja, se eu fizesse algo do tipo:
return (List<E>) cr.getEntity(new GenericType<List<Produto>> () {});
retornaria normalmente pois o GenericType conhece o tipo certo desta lista, porém obviamente só serve para List.. Alguém teria alguma dica do que poderia ser feito neste caso? Senão terei que ou passar este GenericType por parâmetro (que no meu caso não seria bom), ou estruturar explicitamente a conversão de JSON para List e vice-versa (implementando MessageBodyReader/Writer)..

Qualquer dica é válida..

22 Respostas

A

Não te recomendo trabalhar com retorno baseado em listas (ou qualquer outro tipo de coleção), já que é um tanto problemático retornar XML assim (alguns contêiners “adivinham” o que colocar na raiz do XML, mas isso não é padronizado e é bastante sujeito a bugs). Além disso, você elimina suas chances de colocar HATEOAS, por exemplo, ou qualquer coisa que seja relacionada estritamente ao conjunto de dados, e não a cada uma das informações. Assim, recomendo que você crie uma entidade que vá realizar a agregação desses dados para você.

Se quiser, eu tenho um exemplo desse conjunto já feito com uma base, que você pode alterar à vontade para representar suas necessidades. O link é https://github.com/alesaudate/kickstart-springjerseyhibernate.

[]'s

E

Primeiro obrigado pela resposta…

Olhei os seus fontes, você se refere especificamente à EntityCollection que você tem, certo?

O único detalhe é que eu não queria amarrar este retorno só por causa da chamada ao WebService, pois estou montando uma arquitetura onde as requisições podem vir tanto web como destkop e cair na mesma service (que possui um handler e este sabe como deve chamar a DAO, seja passando pelo web service, numa chamada desktop ou mobile, por exemplo, ou passando diretamente a chamada para a DAO, caso seja uma requisição web) e então seria interessante deixar o retorno da lista por isso (até porque este web service não será usado para integração, somente internamente na empresa).

Então seguindo o que você disse não seria melhor implementar explicitamente a forma com que os parses destes JSONs serão feitos? Pois aí vou saber exatamente como serializar e desserializar essas Collections…

O que você acha? Encapsulo esta lista em um outro objeto como vc recomendou (mesmo que o handler que acessa a DAO diretamente não precise disso) ou implemento a conversão explicitamente?

A

Se existe uma chamada direta, acho que seria mais “negócio” encapsular mesmo as chamadas do serviço em outra entidade (seu handler, no caso), para que a implementação do serviço em sí só lide com detalhes específicos de REST. Esta camada poderia falar de EntityCollections, e a camada de baixo poderia falar de listas. De qualquer forma, é sempre melhor ter essa entidade que encapsule os dados por causa do XML - até porque já ví arquiteturas projetadas para trabalharem com JSON que acabaram tendo que ser revertidas, e dá uma dor de cabeça fazer isso sem um elemento agregador.

Aliás, dê uma olhada na classe BaseService (pacote services). Reparou que a maioria das respostas eu encapsulo como Response, e não com o tipo específico? Isso é para habilitar uma série de coisas interessantes, tipo cache de requisições. Além disso, mesmo que você implemente as respostas dos serviços como as próprias entidades (ou mesmo uma lista delas), se você quiser retornar algo como um 404 Not Found, você terá que lançar uma WebApplicationException - o que já “amarra” os seus clientes que fazem a requisição direta a uma camada JAX-RS.

Resumindo:

[Camada JAX-RS]               => [Camada Handler] => DAO
(retorna EntityCollection)     => (retorna Lista)
       ^                                 ^
       ||                               ||
Cliente JAX-RS                 Cliente local

[]'s

S

erico_kl:

O que você acha? Encapsulo esta lista em um outro objeto como vc recomendou (mesmo que o handler que acessa a DAO diretamente não precise disso) ou implemento a conversão explicitamente?

Eu concordo com o Alexandre, é melhor ter uma estrutura própria. Mesmo que outras partes do sistema usem esse método diretamente. Para ajudar a essas outras chamadas “mais internas” vc pode fazer o objeto agregador fornecer metodos utilitários como asList() para usar como se fosse uma lista normal.

A

Olha o que acontece sem um elemento agregador: http://www.guj.com.br/java/288625-restful-dando-erro-de-retorno-em-json#1526314

E

Pois é, também já tinha passado por isso rsrs (tanto que estava implementando o MessageBodyReader/Writer para parsear os JSONs manualmente)
Bom, então quando for uma chamada web service, teria uma camada própria (com retorno por Response com EntityCollection, por exemplo) e o Resource faria a chamada à DAO diretamente?

Porque eu tinha montado uma implementação onde a Service de cada entidade (que continha um handler) fazia a chamada ao WebService e à DAO diretamente (WebServiceAccessHandler e DAOAccessHandler, respectivamente).. Ou seja, uma action na web invocaria uma ProdutoService, por exemplo, instanciando uma DAOAccessHandler, e uma chamada desktop instanciaria WebServiceAccessHandler e a Service já saberia por onde teria que "caminhar" para chegar até a DAO. Vou exemplificar aqui o que eu tenho atualmente:

common.handler.AccessHandler
public abstract class AccessHandler {

	protected Class<?> clazz;
	
	public Class<?> getTypeClass() {
		return clazz;
	}

	public void setTypeClass(Class<?> clazz) {
		this.clazz = clazz;
	}
	
	public abstract <E> E invokeAdd(String methIdent, E bean);
	public abstract <E> void invokeUpdate(String methIdent, E bean);
	public abstract <E> void invokeDelete(String methIdent, E bean);
	public abstract <E> E invokeGet(String methIdent, E bean);
	public abstract <E> List<E> invokeList(String methIdent, E bean, Object... objs);
	// ...
	
}
Implementação do AccessHandler para WS:
public class WebServiceAccessHandler extends AccessHandler {

	// ...
}
Implementação do AccessHandler para acesso direto:
public class DAOAccessHandler extends AccessHandler {

	// ...
}
common.service.BasicService
public interface BasicService<E> {

	public E add(E bean);
	public void update(E bean);
	public void delete(E bean);
	public E get(E bean);
	public List<E> list(E bean);
	
}
Implementação padrão do BasicService:
public abstract class BasicServiceImpl<E> implements BasicService<E>{

	protected final AccessHandler handler;
	
	public BasicServiceImpl(AccessHandler handler, Class<E> beanClass) {
		this.handler = handler;
		this.handler.setTypeClass(beanClass);
	}

	@Override
	public E add(E bean) {
		return handler.invokeAdd("add", bean);
	}

	// outras implementações...

}
Service específico de um java bean (Produto):
public interface ProdutoService extends BasicService<Produto> {

	public Long getAnything(int number);
	// ...
	
}
Implementação desta service:
public class ProdutoServiceImpl extends BasicServiceImpl<Produto> implements ProdutoService {
	
	public ProdutoServiceImpl(AccessHandler handler) {
		super(handler, Produto.class);
	}
	
	@Override
	public Long getAnything(int number) {
		return handler.invoke("getAnything", Long.class, number);
	}

	// ...
}
Back-end (Resource genérico):
public abstract class BasicResource<T> extends MainResource implements BasicService<T> {

	protected abstract BasicService<T> getDAO();

	// chamadas à DAO genéricas...

}
Resource de Produto:
@Path("/Produto")
public class ProdutoResource extends BasicResource<Produto> implements ProdutoService {

	// este "container" é o container de IoC que utilizo (MentaContainer)
	private final ProdutoDAOImpl produtoDAO = container.get(ProdutoDAOImpl.class);
	
	@Override
	protected BasicService<Produto> getDAO() {
		return produtoDAO;
	}

	@Override
	@GET
	@Path("/getAnything/{number}")
	public Long getAnything(@PathParam("number") int number) {
		return produtoDAO.getAnything(number);
	}

}

E a DAO não é relevante neste caso, mas ela também implementa ProdutoService.. Dessa forma eu poderia usar sempre a mesma arquitetura tanto para WS como para DAO direto.. A diferença usando a arquitetura que vocês sugeriram seria em deixar a parte de WebService totalmente à parte, isso? Esta forma que está hoje estaria errada?

S

erico_kl:

E a DAO não é relevante neste caso, mas ela também implementa ProdutoService… Dessa forma eu poderia usar sempre a mesma arquitetura tanto para WS como para DAO direto… A diferença usando a arquitetura que vocês sugeriram seria em deixar a parte de WebService totalmente à parte, isso? Esta forma que está hoje estaria errada?

Embora o DAO seja um service, ele está em uma camada diferente. Ele não pertence à camada de dominio. Mesmo que pertencesse ( ou vc estivesse usando um Repositorio) não faz sentido forçar que o webservice tenha a mesma interface que um o dao.

Poderíamos argumentar que vc poderia criar um objeto X com a mesma interface que o webservice. Como se o webservice fosse uma implementação de X ou de uma interface H que X tb implementa. Acho que este é o conceito que está imaginando. X e Webservice implementam H e webservice chama X.
Na realidade o webservice é um Adaptar que contem um objeto da interface H. A interface publica do webservice é do webservice e de mais ninguém e é - normalmente - diferente de H. ela pode mudar mesmo quando os serviços internos não mudam, ou ficar a mesma mesmo quando os seviços internos mudam.

vc está pensando que webservice é um “service” e está na camada de services. Não. ele está na camada de apresentação. É por isso que vc deve criar objetos para o webservice que não são os objetos internos usados pelo sistema e os outros serviços. E por isso faz sentido criar objetos agregadores. Vc deve isolar a sua camada interna de quem usa o webservice. O uso de DTO burros ( como objetos de agregação) ajudam nisto. Fora que vc pode colocar neles informação que não existe nos objetos de negocio ( o exemplo classico é um pedido ter a quantidade de itens, embora o objeto Pedido não tem esse campo, mas o DTO de Pedido tem.)

A

sergiotaborda:

vc está pensando que webservice é um “service” e está na camada de services. Não. ele está na camada de apresentação. É por isso que vc deve criar objetos para o webservice que não são os objetos internos usados pelo sistema e os outros serviços. E por isso faz sentido criar objetos agregadores. Vc deve isolar a sua camada interna de quem usa o webservice. O uso de DTO burros ( como objetos de agregação) ajudam nisto. Fora que vc pode colocar neles informação que não existe nos objetos de negocio ( o exemplo classico é um pedido ter a quantidade de itens, embora o objeto Pedido não tem esse campo, mas o DTO de Pedido tem.)

Inclusive, você pode colocar links HATEOAS nesses objetos (tenho calafrios ao ler/ouvir/pensar na palavra DTO, mesmo sabendo que é isso que eles são).

E

Bom, vamos ver se entendi…

Estes DTOs (agregadores), seriam usados somente na chamada ao WebService? Confesso que estou ouvindo conceitos que nunca tinha ouvido falar antes (tal como HATEOAS)…
Montei esta estrutura para “forçar” uma mesma implementação tanto da Service, como da DAO e do Resource… Se a DAO não implementa uma Service, mas ela tem os mesmos métodos que ela, o que seria melhor? Separar criando uma interface idêntica para a DAO implementar?

E quanto aos agregadores, cada Java Bean teria um DTO contendo uma lista deste bean que seria enviado ao web service? No meu caso estou pensando mais como a camada ws ser uma “intermediadora” para chegar até a DAO, talvez eu esteja vendo isso errado, porém o meu objetivo não é usar o web service para integrar aplicações, mas sim para permitir transações, pool de conexões e melhorias consideráveis de performance que possibilitam um cliente Desktop acessar os dados de um servidor online.

Uma coisa é certa, vou olhar com calma os códigos que vocês mandaram, pois é sempre interessante se basear numa estrutura bem implementada e com conceitos bem definidos.

A

Quase isso. Alguns dos pontos são os seguintes:

  1. Não vejo porque forçar seus resources a terem a mesma lógica de acesso do que os services, já que talvez existam métodos que serão acessados apenas por clientes locais;
  2. Nada impede, em todo caso, de você criar essas entidades agregadoras e utilizá-las na definição de interfaces que serão utilizadas tanto nos services quanto nos handlers. Só tenha em mente que isso vai te atrapalhar muito mais do que ajudar;
  3. Seus services não deveriam ter os mesmos métodos que seus DAO’s, já que isso também vai deixá-los muito engessados. Certo mesmo seria você ter métodos com nomes significativos para o negócio nos seus services - de maneira que eles façam, assim, mais sentido.

Se tiver interesse, procure por alguns patterns de DDD que irão te ajudar:

  • Linguagem ubíqua;
  • Repositories;
  • Aggregate root.

É claro, vários outros patterns de DDD podem te ajudar, mas esses especialmente :wink:

E

Alexandre Saudate:
Quase isso. Alguns dos pontos são os seguintes:

  1. Não vejo porque forçar seus resources a terem a mesma lógica de acesso do que os services, já que talvez existam métodos que serão acessados apenas por clientes locais;

Estava olhando o seu código e você tem uma BaseService no qual todos seus Resources herdariam, certo? Como seria se você tivesse um formulário web para gravar uma Person? Alguma action chamaria a DAO diretamente?

Pelo que entendi os agregadores ficariam mais para os Resources certo? Ou seja… só passa pelos agregadores as chamadas que passarão pelo web service…

No meu caso uma service seria como uma “ligação” entre a view e a DAO para permitir o desacoplamento entre elas, ou seja, a view requisita serviços, e estes sabem como prover os dados. Então mesmo assim não faria sentido a service possuir a mesma assinatura dos métodos que a DAO? Lembrando que terei sim outros métodos pertinentes ao negócio somente na service, mas elas por sua vez também podem efetuar adds, deletes, updates etc… (métodos que existem também na DAO). Por exemplo vamos supor que tenhamos uma classe Conta com uma lista de Parcelas, além da service possuir métodos normais que a DAO possui (tal como add e get, por exemplo), ela também possuiria métodos como “gerarParcelas”, “quitar” e “calcularJuros”, por exemplo (operações estas que não estarão na DAO). O que você acha?

Se tiver interesse, procure por alguns patterns de DDD que irão te ajudar:

  • Linguagem ubíqua;
  • Repositories;
  • Aggregate root.

É claro, vários outros patterns de DDD podem te ajudar, mas esses especialmente ;)


Procurei ler um pouco sobre isso e encontrei esse artigo… Me pareceu um assunto bem amplo mas bem interessante (envolvendo mais a análise em si do que o código propriamente dito). Também são conceitos que eu não tinha ouvido falar, mas parecem extremamente necessários (como linguagem ubíqua, por exemplo), principalmente quando há uma equipe grande envolvida no projeto…

A

Depende, na verdade, do tamanho final. Se for atrapalhar colocar algo no meio (tipo um Service - DDD), eu ficaria com a chamada direta sim. Mas se a aplicação for ficar grande, acho interessante ter um Service, Repository, etc…

Não necessariamente. Se você identificar mais alguma coisa que poderia se beneficiar dos agregadores, você poderia utilizá-los também.

Fazer sentido, talvez até faça… mas seria melhor encarar isso mais como um acidente (ou seja, sem nada “forçando” essa igualdade) do que algo obrigatório, através de uma interface. Isso porque, se algum dia você quiser remover estes métodos, você vai ter que colocar os famosos “throw new UnsupportedOperationException”. Além disso, existe a questão de você tentar modificar uma assinatura (por exemplo, passar um usuário com permissão para realizar a operação, e coisas do tipo). Enfim, acho mais interessante essa igualdade ser acidental do que forçada.


Procurei ler um pouco sobre isso e encontrei esse artigo… Me pareceu um assunto bem amplo mas bem interessante (envolvendo mais a análise em si do que o código propriamente dito). Também são conceitos que eu não tinha ouvido falar, mas parecem extremamente necessários (como linguagem ubíqua, por exemplo), principalmente quando há uma equipe grande envolvida no projeto…

Então… esses conceitos facilitam a modelagem, sim, mas devem estar muito presentes no código. A questão da linguagem ubíqua, por exemplo, é algo que deve ser intrínseco ao código, ou seja, se o usuário falar “segurado”, a classe no sistema deve ser “Segurado”, e não cliente, usuário, ou coisas do tipo.

Mas a questão que eu gostaria que você se atentasse mais, nisso, é aggregate root. Isso porque um objeto de comando para uma lista de objetos pode ser muito útil quando você quiser efetuar operações em lote para todos eles. No caso, você só usaria para trafegar sua lista de um jeito “aninhado”, mas ele poderia ter outras utilidades, também…

[]'s

E

Ok, obrigado pelas explanações…

Sobre os agregadores, qual a relação deles com DTO? Um DTO sempre será um agregador? E um agregador nem sempre será utilizado como um DTO (que seria o caso que você comentou de ter alguns casos em que os agregadores poderiam facilitar sem ser somente para transferência)?

Outra dúvida: No exemplo que dei, poderíamos considerar a classe Conta já como uma agregadora de Parcelas, sendo que ela poderá ter valorTotal, totalPago, totalVencido etc… que são pertinentes à lista de parcelas?

Quanto à relação das Services com a DAO, você sugere o uso de estruturas genéricas nestes casos ou é melhor existir mesmo uma “ContaService”, “ContaServiceImpl”, “ContaDAO” e “ContaDAOImpl”? Pois se for assim, vejo as interfaces como mais atrapalhando do que ajudando, levando em conta que cada entidade seria totalmente independente de outra.

A

erico_kl:
Ok, obrigado pelas explanações…

Sobre os agregadores, qual a relação deles com DTO? Um DTO sempre será um agregador? E um agregador nem sempre será utilizado como um DTO (que seria o caso que você comentou de ter alguns casos em que os agregadores poderiam facilitar sem ser somente para transferência)?

Outra dúvida: No exemplo que dei, poderíamos considerar a classe Conta já como uma agregadora de Parcelas, sendo que ela poderá ter valorTotal, totalPago, totalVencido etc… que são pertinentes à lista de parcelas?

Quanto à relação das Services com a DAO, você sugere o uso de estruturas genéricas nestes casos ou é melhor existir mesmo uma “ContaService”, “ContaServiceImpl”, “ContaDAO” e “ContaDAOImpl”? Pois se for assim, vejo as interfaces como mais atrapalhando do que ajudando, levando em conta que cada entidade seria totalmente independente de outra.

Os agregadores podem acabar tendo as mesmas características de um DTO. Mas o propósito de ambos é diferente, e você pode acabar colocando alguma lógica no seu agregador. Por exemplo, no caso da própria Conta (na verdade, eu preferiria o termo “Fatura”) - ela é um agregador de Parcela, com certeza. Se não houver nada que não faça sentido colocar numa “Conta” (alguma operação conjunta sobre todas as Parcelas), então ela vai acabar virando isso, um DTO. No entanto, o DTO é pura e simplesmente um “objeto” (perceba as aspas) para trafegar dados, não para conter lógica alguma. Isso acaba incitando o Anemic Domain Model.

Quanto às Services com o DAO, de fato elas não deveriam ter interfaces. Aliás, dependendo de como for, você não precisa nem ter Services - o DDD prevê o padrão Repository, que é como se fosse um DAO, mas contendo lógica própria de negócios. Em uma aula que dei sobre o assunto, eu procurei estabelecer a relação entre Prateleira e Livro, sendo a Prateleira o repositório natural de Livro. No entanto, uma Prateleira comporta um número específico de Livros (suponha, 20). Neste caso, a lógica de capacidade pode ser incluída na própria Prateleira. Mas note que essa é uma lógica própria de uma Prateleira. Se você tiver uma operação como, por exemplo, renomear Livro, já não faz sentido colocar em Prateleira (uma Prateleira não renomeia Livros). Se essa for uma lógica muito complexa, vale a pena implementar no próprio Livro ou ter um LivroService.

[]'s

E

Alexandre Saudate:
erico_kl:
Ok, obrigado pelas explanações…

Sobre os agregadores, qual a relação deles com DTO? Um DTO sempre será um agregador? E um agregador nem sempre será utilizado como um DTO (que seria o caso que você comentou de ter alguns casos em que os agregadores poderiam facilitar sem ser somente para transferência)?

Outra dúvida: No exemplo que dei, poderíamos considerar a classe Conta já como uma agregadora de Parcelas, sendo que ela poderá ter valorTotal, totalPago, totalVencido etc… que são pertinentes à lista de parcelas?

Quanto à relação das Services com a DAO, você sugere o uso de estruturas genéricas nestes casos ou é melhor existir mesmo uma “ContaService”, “ContaServiceImpl”, “ContaDAO” e “ContaDAOImpl”? Pois se for assim, vejo as interfaces como mais atrapalhando do que ajudando, levando em conta que cada entidade seria totalmente independente de outra.

Os agregadores podem acabar tendo as mesmas características de um DTO. Mas o propósito de ambos é diferente, e você pode acabar colocando alguma lógica no seu agregador. Por exemplo, no caso da própria Conta (na verdade, eu preferiria o termo “Fatura”) - ela é um agregador de Parcela, com certeza. Se não houver nada que não faça sentido colocar numa “Conta” (alguma operação conjunta sobre todas as Parcelas), então ela vai acabar virando isso, um DTO. No entanto, o DTO é pura e simplesmente um “objeto” (perceba as aspas) para trafegar dados, não para conter lógica alguma. Isso acaba incitando o Anemic Domain Model.

Quanto às Services com o DAO, de fato elas não deveriam ter interfaces. Aliás, dependendo de como for, você não precisa nem ter Services - o DDD prevê o padrão Repository, que é como se fosse um DAO, mas contendo lógica própria de negócios. Em uma aula que dei sobre o assunto, eu procurei estabelecer a relação entre Prateleira e Livro, sendo a Prateleira o repositório natural de Livro. No entanto, uma Prateleira comporta um número específico de Livros (suponha, 20). Neste caso, a lógica de capacidade pode ser incluída na própria Prateleira. Mas note que essa é uma lógica própria de uma Prateleira. Se você tiver uma operação como, por exemplo, renomear Livro, já não faz sentido colocar em Prateleira (uma Prateleira não renomeia Livros). Se essa for uma lógica muito complexa, vale a pena implementar no próprio Livro ou ter um LivroService.

[]'s

E era aí que eu queria chegar para levantar uma outra questão… No seu exemplo, vamos supor que tenhamos um método verificaExemplarDisponivel que executa uma rotina em uma lista de exemplares contidos no Livro. Esta operação necessita de operação com o banco, ou seja, necessita chegar até a DAO para fazer um select em algum lugar no banco de dados e retornar isso para a view. A classe Livro ou até mesmo a LivroService não conseguem, por si só, realizar esta operação sem chegar até a DAO. Então aqui teríamos um caso de ter um verificaExemplarDisponivel tanto na Service como na DAO, correto?

Outra questão, vamos supor que temos um agregador para uma lista de Livros, este agregador faria parte do negócio? Por exemplo uma operação buscaPorAutor ou totalLivosDisponiveis voltaria um objeto agregador de livros ou uma lista de livros (na DAO e na Service)?

A

erico_kl:

E era aí que eu queria chegar para levantar uma outra questão… No seu exemplo, vamos supor que tenhamos um método verificaExemplarDisponivel que executa uma rotina em uma lista de exemplares contidos no Livro. Esta operação necessita de operação com o banco, ou seja, necessita chegar até a DAO para fazer um select em algum lugar no banco de dados e retornar isso para a view. A classe Livro ou até mesmo a LivroService não conseguem, por si só, realizar esta operação sem chegar até a DAO. Então aqui teríamos um caso de ter um verificaExemplarDisponivel tanto na Service como na DAO, correto?

Outra questão, vamos supor que temos um agregador para uma lista de Livros, este agregador faria parte do negócio? Por exemplo uma operação buscaPorAutor ou totalLivosDisponiveis voltaria um objeto agregador de livros ou uma lista de livros (na DAO e na Service)?

No caso de verificaExemplarDisponivel, você poderia ter um repositório de livros e colocar a busca nele. Fim da história.

A idéia é deixar o DAO “magro” (ou seja, só com as consultas mais genéricas de banco de dados) e deixar coisas mais especializadas, como uma busca por autor, num Repository. A diferença entre os dois é que o Repository está na camada de domínio e o DAO não. Assim, você pode levar somente os repositórios para serem utilizados pela view.

Quanto aos agregadores, eles fazem parte da camada de domínio, sim. Isso quer dizer que seu repositório poderia retornar o agregador ou a lista (dependendo do que for mais conveniente).

[]'s

E

E a DAO seria acessada somente pelo Repository ou alguém além dele poderia chamá-la diretamente?

E pelo que eu entendi então a DAO não deve conter nada pertinente ao negócio, sendo que para tal, usa-se um Repository… isso?

A

erico_kl:
E a DAO seria acessada somente pelo Repository ou alguém além dele poderia chamá-la diretamente?

E pelo que eu entendi então a DAO não deve conter nada pertinente ao negócio, sendo que para tal, usa-se um Repository… isso?

Isso mesmo.

S

erico_kl:
E a DAO seria acessada somente pelo Repository ou alguém além dele poderia chamá-la diretamente?

E pelo que eu entendi então a DAO não deve conter nada pertinente ao negócio, sendo que para tal, usa-se um Repository… isso?

O DAO como o nome diz é um Serviço de Acesso a Dados, mas no fim queremos dizer que estamos isolando a tencologia de acesso aos dados ( normalmente jdbc, mas pode ser outra coisa. Por exemplo, seu cliente desktop pode ter DAO que usam este ws que vc está tentando desenhar). Existem várias sabores de DAO. Para prover isolamento, para prover mapeamento ORM e para prover acesso legado.

O modelo em que cada método do dao faz uma coisa X e está dentro do método como fazer essa coisa X é o modelo de legado. Este não é o modelo que vc quer usar hoje em dia e em uma aplicação nova. Ou vc quer fazer ORM ou vc quer prover isolamento.
Normalmente vc quer fazer ORM. Assim sendo vc deve usar um outro padrão que é especialista em ORM - o DomainStore. O EntityManager do JEE é um DomainStore.

O Repositorio é um objeto da camada de dominio ( o DAO não é, é da camada de integração, abaixo da do dominio). Portanto, o Repositorio sempre contem regras. Que regras? É ele que sabe qual pesquisa irá dar o resultado que vc precisa. Mas ele não executa a pesquisa. Ele delega isso à camada de integração, que pode ser na forma de DAO ou de DomainStore. O bom do repositorio é que ele descreve a query de uma forma geral, de forma que não está associada à tecnologia subjacente de persistencia. Dessa forma vc pode mudar a camada de integração sem mudar a regra de negocio e vice-versa. Isto seria impossivel usando o DAO no modelo de legado (que é o design que normalmente vem nos livros)

Não é que o DAO não pode conter regras de negócio (pode, se usado como integração ao legado, e o fez outrora.) O ponto é que vc não deve acoplar as regras de negocio às regras de persistencia e vice-versa. Hoje em dia isto é mais obvio. O seu dominio não deve se preocupar/saber se a pesquisa está sendo feita no SQL ou num NoSQL, se está distribuido ou não, se tem map/reduce ou não, etc… o que interessa à camada de negocio é que seja feita a pesquisa. Desde desacoplamento nasce o Repositorio. Que é o conceito de que os dados são objetos e ficam em algum lugar e são pesquisados conforme certas regras. O ponto aqui é “são objetos”. Onde ficam esses objetos é irrelevante ao repositorio e por isso ele delega para quem sabe isso que é a camada de integração. A camada de integração em si, pode ser composta de DAO ou DomainStore conforme onde realmente vc estiver gravando as coisas. O DomainStore provê uma abstração melhor que o DAO quando vc trabalha com objetos e nesse caso o DomainStore esconde o uso real de DAO para isolamento de tecnologia deixando-se ao cargo do DomainStore a parte de ORM.

E

Interessante isso que você comentou… Inclusive li um artigo seu em que você comenta este padrão (também tem este artigo que introduz uma outra abordagem deste conceito)…

Mas isso na prática não seria um pouco mais custoso em termos de praticidade para criação de novas entidades?

Outra questão… Visto que uma DAO (no caso de um ORM um DomainStore) faz parte da camada de integração e que este não conterá regra de negócios (no caso de uma DomainStore) e somente ela sabe como prover os dados para o Repositório, o que você pensa sobre algoritmos mais próximos da persistência ou do banco de dados em si (tal como triggers, por exemplo)… Você condena totalmente esse uso?

Por exemplo, digamos que a gente tenha uma entidade Fatura que é mapeada para um banco relacional como uma tabela “faturas”, bem como uma tabela “parcelas” mapeadas em classe como Parcela (Fatura 1xN Parcela). Para cada alteração de valor de uma das parcelas é necessário atualizar o valor total pago e o status contido na fatura. Uma operação destas (na minha opinião) é típico de criação de uma trigger. Sei que isto amarra um pouco a aplicação, mas por outro lado é tão raro alterar o banco de dados do sistema quanto modificar a linguagem. E vale a pena lembrar ainda que a maioria das integrações com outras bases são feitas através de XML ou importação de arquivos texto com layouts próprios que o sistema deve entender e tratar da forma certa.
Além disso, há ainda uma outra vantagem em relação à trigger, que é a questão de segurança e da não necessidade de abrir transações em código, o que, muitas vezes, deixa de ser produtivo tanto em questões de desenvolvimento como performance.

O que vocês pensam sobre isso?

S

erico_kl:
Interessante isso que você comentou… Inclusive li um artigo seu em que você comenta este padrão (também tem este artigo que introduz uma outra abordagem deste conceito)…

Esse meu artigo está um pouco desatualizado. O exemplo que eu dou de escrever um SQL era para ajudar a entender, mas acabou confundindo o padrão parecendo que o repositório gera SQL. Ele gera QueryObjects do qual SQL é um exemplo, mas o artigo deveria mostrar isso. O exemplo real seria por exemplo o repositorio escrevendo uma Criteria do hibernate de depois chamamdo o session.

O segundo artigo usa o padrão repositorio do jeito que eu falei ( Repositorio chama DomainStore, que - no caso - é o Session do Hibernate )

public class Repository {
  private final Session session;  
  public Repository(Session session) {   // <- isto que interessa para nós
    this.session = session;
  }
   
  public void get(Long id) {
    // poderia delegar para um dao, se fosse necessário
    Usuario u =  session.load(id);  // <- isto é uma query
    u.setRepository(this);   // <- isto que interessa para o artigo deles
  } 
}

A discussão deles é se o entity deve ou não ter acesso ao repositorio. Isso é a implementação de outro padrão (ActiveRecord). Se a sua aplicação usar ActiveRecord, o artigo discute como injetar o repositorio em cada instancia da entidade.
Não contraria o fato do repositorio em si, comunicar com o domainStore e não com o dao (pese embora o comentário que poderia chamar o DAO, o que eu considero uma alternativa ao uso do session)

O exemplo do ActiveRecord sim. mas o uso de domainStore com repositorio não. Não tem nenhum problema. Vc usa como usaria um DAO, mas por baixo dos panos vc tem mais flexibilidade.

Trigger e Procedure é o jeito legado de fazer. Se vc usa isso, então vc precisa do DAO “sabor legado”, que falei antes. Eu não uso. Qualquer processamento é feito é java. Por uma razão simples : portabilidade.

Entenda que se vc usa procedure ou trigger a sua camada de serviço/domínio vai até ao banco. Isto é contrário ao principio de isolamento. Se vc só manda dados, então o banco tem um proposito especifico e um só (SRP - Single Responsability Principle) o que o torna muito mais simples de mudar. Pode mudar de SQL Server para Postgress ou Oracle sem pensar duas vezes. Já com triggers e SP é um parto … Vc fala que isso nunca muda … bem depende muito da sua aplicação. Se vc tem um produto e o cliente que escolhe o banco, importa e muito.

O exemplo que vc cita é a forma como alguém pensa fora do java ( em clipper , ou vb , sei lá). Em java vc não pensa assim. Repare que o conceito de trigger ainda é usado, mas é tudo feito em java. (vc configura um listener no hibernate, por exemplo, que o avisa quando a entidade é inserida ou atualizada, etc… e ai vc faz algum codigo de logica. É assim que são feitos os códigos de auditoria, por exemplo). O SP é o codigo que está nos Services, e triggers são eventos trabalhados com listeners. Simples.

Acho que a simplicidade de manutenção trunfa qualquer vantagem que vc se lembrar. Até a performance é maior porque vc pode distribuir vários AS que são muito mais stateless que o banco e por isso mais fáceis de escalar.

Tem pessoas que acham que devem delegar ao banco, eu não acho. Para mim banco de dados é um objeto burro.

Mas atenção, o padrão DomainStore não proibe vc de usar procedure e triggers. Mas ai vc terá que implementar o seu próprio DomainStore pois os de fábrica como o JPA e o Hibernate não têm muito suporte a isso. Vc até consegue invocar um SP no hibernate, mas não lhe dá muita vantagem pois coisas como o cache do hibernate não são usados nessas operações ( e é no cache que está a magia e a escalabilidade da coisa). Vc pode pensar em uma camada onde implementa o seu DomainStore chamamdo o hibernate, por exemplo, por baixo dos panos, mas como as queries que são recebidas são genéricas vc tem que entendê-las e saber “ah! vc está tentando acessar o total das faturas, então em vez de fazer esse sum que vc está pedindo, vou chamar este SP aqui” … é possível, mas é complicado demais.

O JEE 7 e 8 vão trazer muita coisas util como uso do fork/join e concorrencia em ambiente JEE o um framework de batch. Com isto, vc perde totalmente necessidade de SP e triggers no banco.

E

Ok, somente a DAO conhece a infraestrutura, ou seja, é ela que está amarada diretamente ao modelo de dados que for usado (banco, xml, web service etc…). Li em outro artigo que os Repositórios geralmente são interfaces que a DAO implementa, mas aí onde ficam as implementações de domínio? Neste caso o repositório não acabaria sendo somente um facade para expor os métodos da DAO?

Ao meu ver, para separar bem as coisas, um repositório não deveria conter nada que envolva sql, transações, hibernate ou qualquer outro framework ORM, sendo que isso é responsabilidade da DAO (ou do DomainStore).

Mas aí estamos escolhendo entre ou ficar preso ao banco, ou ficar preso ao framework… E em ambos os casos seria um parto a manutenção em uma troca de banco relacional para um banco OO, por exemplo (claro que aqui estou indo um pouco além)…

Eu discordo. Acredito que muita “gambiarra” que é feita em código java, que além de ser menos performática, acaba sujando um pouco o que realmente interessa para o código e o domínio em si, poderia ser implementada diretamente no banco (desde operações de cálculo, logs, controle de usuários (roles) etc, até as próprias triggers que comentei). Claro que esse uso depende da abordagem e do contexto em que o sistema está incluído, mas acho que os bancos de dados, em alguns casos, podem servir mais do que somente para o propósito de CRUD.

Por outro lado também é interessante deixar isso desacoplado e fazer o controle por código, mas como falei tudo depende do contexto e das necessidades.

O que preciso mesmo é montar uma arquitetura com os padrões que realmente irão ajudar (principalmente na manutenção e agilidade) e não acabar burocratizando o processo…

Criado 30 de novembro de 2012
Ultima resposta 5 de dez. de 2012
Respostas 22
Participantes 3