DDD - Criticas à minha arquitetura

Pessoal, bom dia!
Com base nesse tópico: http://www.guj.com.br/posts/list/90694.java , resolvi dar as caras e mostrar um protótipo de arquitetura (baseada em DDD) para ser criticada (sugestões são bem vindas) por vocês e assim todos nós tiramos proveito!
Segue:

Possuo um POJO que recebe um repositório no seu constructor:

[code]
package com.site.domain.model.entity;

import java.io.Serializable;

import com.site.domain.model.repository.UsuarioRepository;

public class Usuario extends Pessoa implements Serializable {
private static final long serialVersionUID = 1L;

private Long id;
            private String login;
private String password;
private boolean isAdministrator;

//transient
private UsuarioRepository repository;

public Usuario(UsuarioRepository repository) {
	this.repository = repository;
}

//behaviour
public List listarMensagensEnviadasPorPeriodo(Date start, Date end) {
return repository.listarMensagensEnviadasPorPeriodo(start, end);
}

/*
Aqui por ex, eu poderia ter outros comportamentos, por ex:
public List listarHistoricosDeLogin() { }
public List listarLogsDeAcesso();

           etc. Entre outros comportamentos referentes ao USUÁRIO, os quais o repositório se encarregaria de resolver.

*/

public String getLogin() {
	return login;
}
public void setLogin(String login) {
	this.login = login;
}
public String getPassword() {
	return password;
}
public void setPassword(String password) {
	this.password = password;
}
public boolean isAdministrator() {
	return isAdministrator;
}
public void setAdministrator(boolean isAdministrator) {
	this.isAdministrator = isAdministrator;
}

public Long getId() {
	return id;
}

public void setId(Long id) {
	this.id = id;
}

}[/code]

Uma interface representando um “repositório genérico”

[code]package com.site.domain.model.repository;

import java.io.Serializable;
import java.util.List;

public interface Repository<T, ID extends Serializable> {
T get(ID id);
List getAll();

T add(T bean);
void remove(T bean);

}[/code]

A interface em sí do “repositório do usuário”:

[code]package com.site.domain.model.repository;

import com.site.domain.model.entity.Usuario;

public interface UsuarioRepository extends Repository<Usuario, Long> {
public List listarMensagensEnviadasPorPeriodo(Date start, Date end);
}[/code]

O DAO, veja que não implementei ainda o tratamento da persistência, mas fique a vontade para criar uma outra interface genérica para acesso aos dados o qual o DAO TAMBÉM poderia estar implementando em conjunto com a interface do repositório. Que tal um EntityManager aí ? :smiley:

[code]package com.site.domain.persistence.dao;

import java.util.List;

import com.site.domain.model.entity.Usuario;
import com.site.domain.model.repository.UsuarioRepository;

public class UsuarioDAO implements UsuarioRepository {

    public List<Mensagem> listarMensagensEnviadasPorPeriodo(Date start, Date end) {
            return null;
    }

public Usuario add(Usuario bean) {
	// TODO Auto-generated method stub
	return null;
}

public Usuario get(Long id) {
	// TODO Auto-generated method stub
	return null;
}

public List<Usuario> getAll() {
	// TODO Auto-generated method stub
	return null;
}

public void remove(Usuario bean) {
	// TODO Auto-generated method stub
	
}

}[/code]

Os serviços, aí eu colocaria o Spring para gerenciar as transações e injetar os repositórios, uma interface para embrulhar os serviços tbm seria interessante, ah sim, e talvez um filtro (aop?) para criar os beanzinhos!

[code]package com.site.service;

import com.site.domain.model.entity.Usuario;
import com.site.domain.model.repository.UsuarioRepository;
import com.site.domain.persistence.dao.UsuarioDAO;

public class UsuarioService {

private UsuarioRepository usuarioRepository;

//spring transaction control
public void delete(Usuario usuario) {
    usuarioRepository.remove(usuario);
	
}

public void save(Usuario usuario)  {
    usuarioRepository.add(usuario);
}

}[/code]

Bom, eras isso!

Editado: Modificações conforme sugestões. Remoção do “autenticar”, e inclusão de uma consulta para exemplo.

Um ponto que eu apontaria é o fato do repositorio estar fazendo autenticação. Isso é no minimo estranho.
Além disso vc está colocando no usuário a responsabilidade de se autenticar, mas na realidade está passando-a para o repositorio. Essa delegação indica que não é do usuário a responsabilidade de autenticar o usuário.
A minha sugestão é que mova esse método para um serviço e o retire do usuário e do repositorio.

Ruim.

E ruim também é a maioria das discussões sobre DDD que se resumem a “faz aquela arquitetura em camadas de sempre, de acordo com os Patterns J2EE, mas adiciona um Repositório no meio”, ou seja, resulta em algo que não tem nada de DDD, mas tem lá o Repositório!. Não preciso nem dizer que isso só adiciona complexidade, sem obter muitos benefícios.

Vamos lá dizer os pontos problemáticos:
1 - Porque eu tenho Usuário e UsuárioLogic? DDD não seria algo mais OO? E portanto, lógica e estado relacionados dentro de um mesma classe?

2- Porque o repositório autentica o usuário? Repositório é para buscar alguma coisa que não se sabe daonde, e depois, com essa coisa em mãos, fazer alguma coisa.

3- Pra que um repositório genérico? Pense nessas coisas depois.

4- Reparou que DAO e Repository possuem o mesmo nível de abstração? Repository deveria ser mais ao nível de usuário, enquanto DAO, ao nível de infraestrutura.

[quote=Leonardo3001]…
[/quote]

E quais são suas idéias para melhorar?

[quote]Vamos lá dizer os pontos problemáticos:
1 - Porque eu tenho Usuário e UsuárioLogic? DDD não seria algo mais OO? E portanto, lógica e estado relacionados dentro de um mesma classe?[/quote]

Entenda por serviço. Ou você estaria gerenciando as transações dentro do repositório ? Como você faria isso ?

Ué. Os métodos são genéricos, ao que me parece … um repositório é lugar onde se guarda coisas, se se guardam coisas num lugar, então temos comportamentos como adicionar, remover, listar todos… pq eu estaria replicando isso em todos os repositórios? Como você faria isso?

Discordo. Meu repositório está a nível de abstração de um repositório (veja a interface List por ex), deixei bem claro que não há nenhum método do DAO dentro do DAO de exemplo, e se tiver, certamente quem precisar de um repositório não irá enxergar!

As única críticas são:
:arrow: Realmente o repositório não tem que autenticar o usuário, o repositório poderia ter um método getByLogin() que um service vai usar pra fazer a autenticação.
:arrow: Eu não faria um UsuarioDAO, teria só um DAO genérico, principalmente se usar o hibernate.
:arrow: Seria bom criar factories pra criar seus usuários, eu uso o próprio repositório pra isso. Eu criaria um método UsuarioRepositorio.create().

[]'s

Rodrigo Auler

[quote=peerless][quote=Leonardo3001]…
[/quote]

E quais são suas idéias para melhorar?

[quote]Vamos lá dizer os pontos problemáticos:
1 - Porque eu tenho Usuário e UsuárioLogic? DDD não seria algo mais OO? E portanto, lógica e estado relacionados dentro de um mesma classe?[/quote]

Entenda por serviço. Ou você estaria gerenciando as transações dentro do repositório ? Como você faria isso ?

[/quote]

Você está usando Faces ou Struts 2? Então coloque um managed bean/action gerenciado pelo Spring e lá você coloca uma regra de transação. Mas isso é algo que você não deveria se preocupar num primeiro instante

Ué. Os métodos são genéricos, ao que me parece … um repositório é lugar onde se guarda coisas, se se guardam coisas num lugar, então temos comportamentos como adicionar, remover, listar todos… pq eu estaria replicando isso em todos os repositórios? Como você faria isso?[/quote]

O fato de um repositório ser um lugar onde guarda coisas, não implica ser um lugar onde você põe as coisas. Repositório só possui buscas.

Isso seria facilmente implementado usando a combinação JPA/Hibernate com Spring, afinal, quem salva é a transação.

Discordo. Meu repositório está a nível de abstração de um repositório (veja a interface List por ex), deixei bem claro que não há nenhum método do DAO dentro do DAO de exemplo, e se tiver, certamente quem precisar de um repositório não irá enxergar!

[/quote]

O seu repositório ainda pensa em termos de base de dados, não em negócio. E o fato de ter getById e getAll denota isso. Na prática, do ponto de vista do negócio, ninguém quer trazer todos os dados existentes, nem um dado baseado num identificador artificial. O que o cara do negócio quer mesmo saber são os clientes inadimplentes, os produtos mais vendidos, o que está faltando no estoque e etcetera.

Acho também que o exemplo de autenticação é muito ruim, isso se refere à infraestrutura do sistema, um container Java EE faz isso via configuração.

Poderia mudar ligeiramente o exemplo para um cadastro de usuário que ainda não existe, que tal?

Pode esclarecer se você não “pões as coisas” no repositório como faz então?

Não tem nada de errado com o getById ou getAll em si, pelo contrário getById principalmente é bastante comum. Igualmente comum é as pessoas nao entenderem o papel do repositorio no dominio. Se uma regra for realmente importante para o negócio os conceitos envolvidos serão mapeados em objetos de domínio criados com a responsabilidade de atender essa regra específica.

Por outro lado se cliente, produto e estoque ainda são conceitos importantes para o seu domínio (existem na ubiquitious language) mas o código que implementa como os clientes inadimplentes ou os produtos mais vendidos são obtidos não é importante para o negócio neste caso o repositório é util. O repositorio no domínio é descrito em termos da ubiquitous language mas a ele cabe apenas a responsabilidade de abstrair infraestrutura.

Concordo com o colega cmoscoso, quando ele discorda da seguinte afirmação do Leonardo3001:

No capítulo sobre Repositórios do livro do Eric Evans, há um trecho que diz:

Ahh, para! Isso aí é um protótipo exemplar, como citei. Não importa que framework mvc eu vou usar. Pode ser até para um ambiente desktop… Então vc gerencia suas transacoes nas actions? Se não existisse frameworks vc gerenciaria nos servlets, é isso? E se por ventura vc trocar de framework mvc, vai ter que pensar seriamente onde diabos estão sendo gerenciadas as transacoes? Delegar negocio nem sempre é ruim nestes casos.

os amigos em cima já lhe corrigiram.

Voce ja disse (assim como o SergioTaborda) que o exemplo de autenticacao é ruim. Ainda bem que eu coloquei outros exemplos que poderiam entrar em comentários. Essa critica, nao havia ainda comentado, mas acataria sem problemas.

[quote=tnaires]No capítulo sobre Repositórios do livro do Eric Evans, há um trecho que diz:

Ele diz isso, mas isso não é uma verdade imutável. Em várias partes do livro ele defende que não existe receita bolo e que devemos adaptar os padrões e arquiteturas para a realidade da equipe, seja por limitações de infra-estrutura ou requisito maluco ou mesmo por gosto pessoal.

O seu repositório pode estar bem implementado com ou sem um add/remove ou getById.

Por exemplo, se eu crio meus objetos através de uma fábrica ( que pode ou não ser o próprio repositório ), não vejo muita necessidade de ter um método pra adicionar o objeto no repositório.

[]'s

Rodrigo Auler

Acho que você não entendeu, a verdade imutável neste caso é de que não pode haver add/remove nos repositórios.

Eu faco assim geralmente pra salvar entidades pequenas -repositorioCueca.findOrCreate(freiada). Para outros agregados as vezes crio um metodo capaz de incluir ou atualizar o grafico inteiro -repositorioTerno.addOrUpdate(terno)- portanto assumindo o objeto já criado.

Como você faria nesses casos onde a factory não é o repositorio ou mesmo para remover o agregado que nao seja por meio de metodos na interface do repositório?

[quote=Rodrigo Carvalho Auler]Ele diz isso, mas isso não é uma verdade imutável. Em várias partes do livro ele defende que não existe receita bolo e que devemos adaptar os padrões e arquiteturas para a realidade da equipe, seja por limitações de infra-estrutura ou requisito maluco ou mesmo por gosto pessoal.

O seu repositório pode estar bem implementado com ou sem um add/remove ou getById.

Por exemplo, se eu crio meus objetos através de uma fábrica ( que pode ou não ser o próprio repositório ), não vejo muita necessidade de ter um método pra adicionar o objeto no repositório.

[]'s

Rodrigo Auler
[/quote]
É verdade. Dependendo dos requisitos ( ou pelos outros motivos que você citou ), acho que não teria problema em, por exemplo, eu acessar meu mecanismo de persistência ( um DAO ou um objeto Session do Hibernate ) diretamente da camada de aplicação para persistir meu objeto de negócio, ao invés de criar um add() no repositório e delegar para o referido mecanismo.

Mas não entendi sua colocação sobre a fábrica. Ela tem como função abstrair a criação de novas instâncias de objetos de domínio, sendo assim, não vejo como ela poderia substituir um add() no repositório.

Abraços

Não vejo motivos pra factory não ser o próprio repositório, mas mesmo não sendo, ela pode usar os mesmos mecanismos do repositório pra de alguma forma persistir o objeto ao fim de uma transação. Uma outra opção seria Active Record, apesar de alguns não gostarem, não vejo mal em usar se for bem implementado.

Ser flexivel não é desculpa pra fazer besteira.

Na criação de uma nova instancia ele pode injetar no objeto o que ele precisa saber pra se persistir, se for usar ActiveRecord. Ou , dependendo de como for sua persistência, já colocar seu objeto pra ser persistido ao fim da transação.

[]'s

Rodrigo Auler

Peerless,

peço qualquer desculpa se você interpretou minhas declarações como ofensa pessoal, minha intenção era somente emitir opiniões sobre o seu exemplo.

Bom vamos às tréplicas:
:arrow: com relação ao repositório, eu acho realmente estranho ele ter métodos de salvar. Eu sei que pro Evans não é assim, e que pro Nilsson também não, mas é gosto pessoal mesmo. A razão da minha implicância é que quando peço um objeto para o repositório, este objeto não deixou de existir no repositório, tanto é que as próximas consultas irão trazer o mesmo objeto. Então, por que adicionar um objeto ao repositório se este ainda tem conhecimento daquele?
Talvez porque no mundo real, sem um Hibernate, fazer os objetos se salvarem por mágica seja impossível, ou pelo menos, requer muito trabalho, daí esses metódos estão lá no repositório para não complicar muito nossas vidas. Porém, ainda não deixa de ser esquisito pra mim.

:arrow: Foi uma mancada eu ter dito que poderia usar actions como camada de serviço. Idealmente, não. E às vezes dá problema em se fazer isso. Mas tem vezes, principalmente quando as actions são POJOs e quando a ação é simples, que eu “quebro o protocolo” e faço da action a camada de serviço. Não acho isso o fim do mundo, principalmente se isso está sendo feito conscientemente. O problema fui eu em transformar a exceção em regra.

É isso.

Mas não é besteira. Se eu tiver certeza que nunca vou deixar de usar Hibernate no projeto, não há problema em chamar o Session diretamente da camada de aplicação ( embora eu prefira mil vezes mais deixar o repositório “editável”, conforme sugerido pela descrição do padrão no livro do Eric Evans ).

[quote=Rodrigo Carvalho Auler]
Não vejo motivos pra factory não ser o próprio repositório, mas mesmo não sendo, ela pode usar os mesmos mecanismos do repositório pra de alguma forma persistir o objeto ao fim de uma transação.[/quote]

Neste caso ela estaria sendo o próprio repositório! :slight_smile:

Bom, factories não são repositórios. Apenas entidades podem ser persistidas e factories atacam o inicio do ciclo de vida do objeto, quem lida com o ciclo de vida das entidades são os repositórios. Se você não precisa de factories é normal para casos simples mas quando justificar uma não vejo porque criar um objeto hibrido, é um design pouco flexível pra dizer o mínimo. Na minha opinião pessoal acho melhor separar os dois conceitos e delegar responsabilidades entre eles quando for o caso.

Independente do mecanismo de persistência alguem não teria ainda que sinalizar qual o objeto e quando no processo ele seria persitido?

[code]em.persist(entidade); // JPA

session.save(entidade); // Hibernate[/code]

Esse é justamente o código contido no metodo add(). Não entendi o que transação tem com isso.

[quote=cmoscoso][quote=Rodrigo Carvalho Auler]
Ele diz isso, mas isso não é uma verdade imutável. Em várias partes do livro ele defende que não existe receita bolo e que devemos adaptar os padrões e arquiteturas para a realidade da equipe, seja por limitações de infra-estrutura ou requisito maluco ou mesmo por gosto pessoal.
[/quote]

Acho que você não entendeu, a verdade imutável neste caso é de que não pode haver add/remove nos repositórios.
[/quote]

É verdade que o Repositorio começa como uma necessidade orientada a pesquisas. Mas não é verdade que ele só possa ter esse tipo de funcionalidade. Ele ser o criador de objetos é muito interessante também. Ele assume mais do que o papel de fabrica, ele pode assumir o papel de injetor de dependência. Por outro lado é sempre bom encapsular a criação dos objetos e o Repositorio é tão bom quanto qualquer outro lugar. Afinal poderiamos ter um XPTORepository e um XPTOFactory , mas para quÊ complicar se podemos ter apenas um XPTORepository.createNew() ?
Quando a ter outros métodos como add/ remove/ merge ele pode ter mas até que ponto isso é uma vantagem e até que ponto esse objeto é apenas um repositorio é outra historia.

Ora ai está um problema findOrCreate significa encontre ou crie. Isso são duas coisas. São duas responsabilidade explicitamente fundidas. Quando o objeto retorna ele é novo ou foi encontrado ? Entenda como o desconhecimento da origem do objeto interfere com o processo do programa. Se eu mandar encontrar e quiser testar que ele não encontrou , como faria ? criaria um método existe() ? nesse caso não é mais simples separar create de find ? Afinal são duas ações muiiito diferentes.
AddOrUpdate já não é tão problemático já que significa apenas 'save", mas do ponto de vista do estado do repositório sempre será um update.

O repositorio e os objetos que ele contém são independentes. O objeto da entidade é criado fora do repositorio sem estar anexo a ele. Mesmo com repositorio.create() o repositorio irá apenas criar e retornar o objeto ele não o irá incluir na lista de objetos do repositorio. Para isso existe um add. Para remover o objeto do repositorio remove. Quando de obtêm um objeto e se altera o seu estado isso não pode refletir no objeto quardado no repositorio, ou seja, em uma pesquisa o repositorio irá retornar um copia e não o original. Já que se a alteração é revertida (roolback) o repositorio não pode ter mudado. Isto é controle de fronteira de transação ou se quiserem controle de fronteira de alteração de estado e não é especifico do repositorio.
Então após pegar um objeto do reposiorio e alterá-lo posso querer que o novo estado do objeto seja aplicado ao objeto que está no repositório, e para isso existe o merge. Claro que sendo que os objetos do repositorio são entidades e portanto têm identidade o método merge e o add podem ser fundidos num update ou save ou persist da vida. A ideia é , coloca este objeto no repositorio. Se ele já estiver lá, atualiza o seu estado.
Estas operações têm que ser ACID mesmo quando não estamos num contexto transacional e processo de concurrencia têm que ser levados em consideração. Tantas responsabilidades num só objeto podem ser rápidamente prejudiciais mas cooperam para uma implementação mais simples.

A base de um repositorio é ter métodos find. Vários se necessário ou utilizando QueryObject. getByID deveria ser findByID() já get implica que estou recuperando um atributo do repositório o que não é verdade.
Vários métodos find, para várias situações e ocasiões compõem um repositorio.
Contudo ele pode ainda conter métodos de alteração de estado como remove, add , merge , etc… Mas cuidado que isso faz dele um quase DomainStore e não apenas um Repositorio.

[quote=Leonardo3001]P
Talvez porque no mundo real, sem um Hibernate, fazer os objetos se salvarem por mágica seja impossível, ou pelo menos, requer muito trabalho, daí esses metódos estão lá no repositório para não complicar muito nossas vidas. Porém, ainda não deixa de ser esquisito pra mim.
[/quote]

No mundo real isso não é desejável. Vc quer controlar o ciclo de vida, não quer que ele se controle sozinho “magicamente”.

Usar findOrCreate() ja foi a melhor opcao pra mim porque não havia diferença se a entidade era nova ou usada e a entidade sempre deveria existiri no retorno da chamada. Não é questão de serem duas ações diferentes porque você está afirmando isso sem um contexto, e no contexto daquele cliente de repositorio a ação era apenas uma, obter uma entidade existente no dominio com aquele atributo passado como parametro.

E pra isso existem factories!

Creio que isso seja indiferente para os clientes do repositorio ja que eles dependem apenas da interface.

[quote=sergiotaborda][quote=Leonardo3001]P
Talvez porque no mundo real, sem um Hibernate, fazer os objetos se salvarem por mágica seja impossível, ou pelo menos, requer muito trabalho, daí esses metódos estão lá no repositório para não complicar muito nossas vidas. Porém, ainda não deixa de ser esquisito pra mim.
[/quote]

No mundo real isso não é desejável. Vc quer controlar o ciclo de vida, não quer que ele se controle sozinho “magicamente”.
[/quote]

Só pra esclarecer ao Leonardo as entidades que possuem repositorio próprio são chamadas de Aggregate Root e apesar do Hibernate salvar todo o agregado como “mágica” ainda é necessário chamar persist/save na entidade root, mesma que seja por algum mecanismo de indireção.