Trabalho no desenvolvimento de um sistema desde 2005. Desde então o sistema cresceu muito e sempre que posso procuro criar formas de otimizar o nosso trabalho, identificando oportunidades de melhorias para que seja possível fazer mais e melhor em menos tempo. O sistema utiliza Struts 1.2 (pretendemos iniciar uma migração para Struts 2 no primeiro semestre de 2008 ) e Hibernate 3.2.
Algo que tem incomodado ultimamemente é a utilização do chamado “Modelo Anêmico”. Já melhoramos um pouco, mas algumas classes continuam bastante anêmicas, hehehe. Por exemplo, imaginem que eu quero inserir um objeto da classe Pessoa. Hoje a requisição é recebida por uma classe Action (ex: PessoaAction), que encaminha os dados para uma classe que costumam chamar de “Service” (ex: PessoaService) que valida as informações e chama uma classe estática que fica responsável pela persistência dos objetos com o Hibernate. O maior problema que eu vejo nessa solução é que as regras de negócio da classe Pessoa estão em PessoaService. Na minha visão, PessoaService continuaria existindo apenas para diminuir a dependência do Struts, mas ao invés de validar as regras e chamar a classe estática para persistência, ela apenas invocaria o método (não estático) Pessoa.inserir().
Para tornar isso realidade tenho que solucionar uma questão. Como poderia fazer com que minha classe de modelo Pessoa conseguisse persistir os objetos sem criar uma dependência? Eu andei lendo um pouco sobre injeção de dependências, não sei se este é o caminho da solução. Se alguém souber de algo e puder ajudar eu ficarei muito feliz!
Muito obrigado, amigos, bom fim de semana pra vcs!
Pessoa.inserir é o padrão ActiveRecord. Usar esse padrão não é uma boa. Continue usando a sua classe estática (acho que quiz dizer metodo estático) para fazer a persistencia ou seja arrojado e crie um DAO. Regra de negocios de pessoa… qual seria um exemplo de regra de negocios de pessoa ?
Se essa regra usar entidades que não estão no grafo de pessoa então vc precisa de um Service. Mova para Pessoa todas as regras que dependem apenas do objeto pessoa e daqueles que estão no seu grafo e deixe em service aqueles que usam outras entidades. O nome do service deve ser [objetivo]Service ou [acção]Service e não [entidade]Service por isso foque nas acções/objetivos e agrupe os que são comuns
Exatamente, não me expressei bem, quis dizer que tenho uma classe composta apenas por métodos estáticos.
Na verdade o sistema não tem classe Pessoa, foi apenas um exemplo. O que você chamou de grafo são os relacionamentos de dependência? Eu não acho legal essa idéia de passar as coisas para uma classe Service, pois o conhecimento sobre o negócio está deixando as classes de modelo. Por exemplo, eu não posso excluir um objeto da classe Cliente se ele está relacionado a um objeto da classe Pedido. Eu gostaria de um modelo que eu pudesse olhar e entender ali todas as regras de negócio, evitando que elas fiquem espalhadas. Este exemplo com Cliente/Pedido que citei é trivial, mas existem regras que envolvem estados dos objetos que são bem mais complexas, essa descentralização tem trazido problemas.
Eu não sabia que isso não era uma boa. Se não estou enganado isso é usado no queridinho do momento Ruby On Rails, não sei se traz algum problema que eu ainda não tenha conseguido visualizar. Você pode exemplificar algo nesse sentido?
ActiveRecord não é legal para o design de sua modelagem. Pense da seguinte forma, Fornecedores não se salvam nem se deletam.
Se você tenta modelar os objetos de forma que façam sentido para o negócio, os métodos das classes tem que fazer sentido também.
Não. Em O não ha “relacionamentos”, ha objetos contendo objetos. Isso é um grafo.
Cuidado com o que vc pensa.
CRUD não é regra de negocio. Isto que fique bem claro. “Não posso excluir o cliente quando ele tem pedidos” é um regra de consistencia de dados e não de dominio/negocio.
A relação entre cliente e pedido não forma um grafo fixo.
Cliente c = ...
Set<Pedidos> pedidos = c.getPedidos() ;
pode parecer que c contém a coleção de pedidos, mas não. Aquele método de dominio que mostra a relação entre cliente e pedido
apenas consulta o repositorio de pedidos por aqueles cujo cliente é c. Não ha necessidade do cliente andar com uma lista de pedidos às costas.
Ainda mais quando essa lista muda a qq momento.
DDD dix-lhe para criar o método getPedidos() e outros do mesmo tipo e isso que é uma regra de dominio. Isto não tem nada a ver com CRUD.
O crud do pedido é independente do crud do cliente.
[quote=Lezinho]ActiveRecord não é legal para o design de sua modelagem. Pense da seguinte forma, Fornecedores não se salvam nem se deletam.
Se você tenta modelar os objetos de forma que façam sentido para o negócio, os métodos das classes tem que fazer sentido também.[/quote]
Entendo o que você diz, faz sentido. [:(]
Estou começando a acreditar que o que temos hoje no sistema não é tão ruim, embora seja necessário melhorar algumas coisas.
[quote=sergiotaborda]Não. Em O não ha “relacionamentos”, ha objetos contendo objetos. Isso é um grafo.
(…)
2) A relação entre cliente e pedido não forma um grafo fixo.[/quote]
Eu realmente fiquei confuso com essa concepção de grafo, não entendi. Imagine que uma classe Pedido possui um atributo Cliente. Isso não pode ser visto como um relacionamento entre as duas classes? Seguindo o que você falou, Cliente pertence ou não ao grafo de Pedido? Se você tiver um link falando sobre grafos nesse contexto eu gostaria de ver, não consegui entender bem.
[quote=sergiotaborda]
Cliente c = ...
Set<Pedidos> pedidos = c.getPedidos() ;
pode parecer que c contém a coleção de pedidos, mas não. Aquele método de dominio que mostra a relação entre cliente e pedido
apenas consulta o repositorio de pedidos por aqueles cujo cliente é c. Não ha necessidade do cliente andar com uma lista de pedidos às costas.
Ainda mais quando essa lista muda a qq momento.[/quote]
Agora que eu não entendi mesmo. Necessidade de andar com uma lista? Mas isso não está relacionado a forma como você implementa? Também não entendi o porquê de Cliente não conter uma coleção de pedidos, porque o que você descreveu, para mim, indica claramente que existe um relacionamento bidirecional entre as duas classes.
Acho que essa questão seria facilmente resolvido com o uso de um PessoaRepository.
A classe Pessoa deve conter regras do tipo que elas tem no mundo real. Disso consiste a abstração.
Por exemplo, Pessoa.pedirMercadoriaParaEstoque(String mercadoria). Já o codigo pertinente a salvar uma Pessoa ou não vai no PessoaRepository.
Mas aí fica uma outra dúvida, é bom chamar o repository direto da Action do Struts?
[quote=feliperod]Acho que essa questão seria facilmente resolvido com o uso de um PessoaRepository.
A classe Pessoa deve conter regras do tipo que elas tem no mundo real. Disso consiste a abstração.
Por exemplo, Pessoa.pedirMercadoriaParaEstoque(String mercadoria). Já o codigo pertinente a salvar uma Pessoa ou não vai no PessoaRepository.
Mas aí fica uma outra dúvida, é bom chamar o repository direto da Action do Struts?[/quote]
Cabe a decisão da equipe, contudo repository faz parte do negócio. Qualquer livro de DDD você vai ler a recomendação de isolar camada de negócio de camada de aplicação (Actions, ManagedBeans, etc), geralmente isso é o mais indicado.
Cabe a decisão da equipe, contudo repository faz parte do negócio. Qualquer livro de DDD você vai ler a recomendação de isolar camada de negócio de camada de aplicação (Actions, ManagedBeans, etc), geralmente isso é o mais indicado. [/quote]
Concordo contigo em isolar o domain da camada de application. E isso não é nem do DDD só, vem dos Patterns J2EE mesmo e está sendo reforçado no DDD. Mas aí o cara cai na utilização de um Service pra fazer esse isolamento (É o que eu uso, ou utilizo uma Entity chamando o repository). Alguém pode postar um exemplo de como vocês fazem?
Mas aí caímos no Entity.insert() falado acima. Só que a logica dentro de Entity.insert() seria uma simples chamada ao repository.
A verdade é que o modelo anêmico está mais relacionado com o que a classe não faz do que com o que ela faz. Caso ela esteja fazendo coisas que não estão no seu escopo, aí o problema é de classes obesas, e não anêmicas.
Mas essas discussões são sempre ótimas para reafirmar o conceitos que temos e a forma que fazemos as coisas.
Não necessariamente. Um repository não faz apenas CRUD, eles realizam buscas necessárias para os entities. Nestes e outros casos um repository dentro de um entity faz todo sentido do mundo.
Outra situação é ocorrer em meio de um processamento de método de negócio, um objeto (um Aggregate) persistir outro objeto (composto pelo Aggregate), ou até mesmo requisitar sua própria persistencia. A grande diferença é o contrato de sua assinatura, ela não faz sentido ter um save ou persist. Mas se em meio a sua lógica for necessário guardar sua informação no repositorio, não te pq não faze-lo.
Normalmente, mas não por regra, os CRUDs acabam por cair nas costas de um Service.
Pedido contém um atributo do tipo Cliente. Sim. Cliente pertence ao grafo de Pedido e eu faço pedido.getCliente().
O cliente do pedido reponde a "Quem fez o pedido?"
Mas se eu quiser responder a “Quais pedidos o cliente fez?” ai eu uso cliente.getPedidos().
Com estas duas coias eu posso responder a “Que pedidos o cliente que fez este pedido, fex?” apenas com pedido.getCliente().getPedidos().
Isto é uma regra de dominio.
O facto de pedido ter um atributo ter um cliente sim pode ser encarado como um relacionamento entre as duas entidades, mas esse relacionamento é “virtual” ou seja, não existe um objeto Relacionamento que tenha um referencia a cada um. (Poderia haver, mas normalmente não ha). O que ha é uma Referencia de pedido a cliente , mas não de cliente a pedidos. Quando executa Cliente.getpedidos() vc está executando uma regra de negocio que é muito mais complexa que pedido.getCliente(). cliente.getPedido() implica num raciocinio “procura os pedidos onde o cliente é this”
Em codigo :
[code]class Pedido {
Cliente cliente; // attributo. Cliente pertence ao grafo fixo de Pedido
}
class Cliente {
// não ha nenhuma referencia ao Set<Pedido>
public Set<Pedido> getPedidos(){
return PedidoRepository.findByClient(this);
}
}[/code]
Embora do lado de fora pareça que Set<Pedidos> existe dentro de cliente, ele não existe de fato. Isso é a vantagem de encasulação.
O que tudo isto interessa?
Quando vc salva o pedido , ha um campo na tabela Pedido que diz respeito ao cliente. Logo o sistema tem que averiguar se o cliente está tb salvo. Se não, salva-o ( ou salva-o logo por default e pronto)
Mas quando o sistema salva o cliente ele não salva a coleção de pedidos. Porque ele não salava? porque não ha nenhum atributo desse tipo em cliente.
Então getPedidos() parece igual a getCliente(), mas não são. Mas o objetivo do DDD é exatamente que pareçam, mesmo que internamente não sejam.
[quote]
Agora que eu não entendi mesmo. Necessidade de andar com uma lista? Mas isso não está relacionado a forma como você implementa? Também não entendi o porquê de Cliente não conter uma coleção de pedidos, porque o que você descreveu, para mim, indica claramente que existe um relacionamento bidirecional entre as duas classes.
![/quote]
Se o cliente contivesse um lista de pedidos cada vez que vc salva o cliente salvaria os pedidos e cada vez que retorna um cliente, retorna todos os pedidos tb. Entenda que isso cria um cliclo vicioso. Salvar o pedido salva o cliente que salva o pedido, que salva o cliente … Claro que a sua ferramente de persistencia pode evitar isso, mas o ponto aqui é que Pedido não pertence ao grafo de cliente. Pedido não especifica o cliente. Mas cliente especifica o pedido. Essa é a diferença.
E não, ninguém anda com uma lista. Sim, depende de como implementa. Mas se vc implementar diferente vai ter muita dor de cabeça por causa do paradoxo que falei antes.
Em termos de banco de modelo E-R existe um relacionamento bidirecional, mas em termos de DDD e OO não. Isso porque não é a mesma coisa vc obter pedidos a partir de um cliente e o cliente ter uma referencia a esses pedidos. E em OO o que conta são as referencias e não os relacionamentos.
Pense assim : vc poderia executar PedidosRepository.findByCliente(Cliente c) sempre que precisasse em qq ponto da aplicação. Mas isso é uma falha de encasulamento. Isso é equivalente a usar o objeto cliente como um DTO e isso é contra a ideia do DDD. É mais claro , e simples , que seja cliente.getPedidos().
Oi, sergiotaborda, gostei muito do seu último post. Agora eu entendi melhor o que você quis dizer, embora algumas coisas não tenham ficado perfeitamente claras. Atribuo isso a minha inexperiência com DDD, tenho procurado ler sobre o assunto.
Seguindo o Padrão GRASP Especialista na Informação, poderiamos dizer que: Quem tem os dados deve ser responsável por manipula-los. Com isso, conseguimos um boa divisão de responsábilidades, conseguimos fornecer um bom encapsulamento, evitamos inveja dos dados enfim, conseguimos evitar modelos anêmicos.
Muitas vezes olhando para o Especialista da Informação fiz reflexões parecidas com esta: O objeto Fornecedor conhece seus dados logo seria responsabilidade dele se salvar, assim manteriamos o encapsulamento etc… etc…
Mas como persistência é um problema de infra vamos voltar lá no mundo de bobject…
Seguindo o Padrão GRASP Especialista na Informação, poderiamos dizer que: Quem tem os dados deve ser responsável por manipula-los. Com isso, conseguimos um boa divisão de responsábilidades, conseguimos fornecer um bom encapsulamento, evitamos inveja dos dados enfim, conseguimos evitar modelos anêmicos.
Muitas vezes olhando para o Especialista da Informação fiz reflexões parecidas com esta: O objeto Fornecedor conhece seus dados logo seria responsabilidade dele se salvar, assim manteriamos o encapsulamento etc… etc…
Mas como persistência é um problema de infra vamos voltar lá no mundo de bobject…
reflexões…[/quote]
É, Bruno, controverso. Eu entendo os dois lados, não é fácil decidir. Neste momento, eu vejo mais benefícios em fazer com que a classe Fornecedor saiba como salvar e recuperar seus objetos. Com AspectJ eu vi uma forma bem legal de fazer isso, o código na classe Fornecedor não seria alterado, ela apenas herdaria uma interface.
Se trabalhar com Domain Model não é pré-requisito para a modelagem, tbm não vejo problema em utilizar Active Record.
Ë puramente uma questão de escolha de design.
Seguindo o Padrão GRASP Especialista na Informação, poderiamos dizer que: Quem tem os dados deve ser responsável por manipula-los. Com isso, conseguimos um boa divisão de responsábilidades, conseguimos fornecer um bom encapsulamento, evitamos inveja dos dados enfim, conseguimos evitar modelos anêmicos.
Muitas vezes olhando para o Especialista da Informação fiz reflexões parecidas com esta: O objeto Fornecedor conhece seus dados logo seria responsabilidade dele se salvar, assim manteriamos o encapsulamento etc… etc…
[/quote]
Isso é uma falácia.
O fornecedor realmente tem conhece os seus dados, mas não conhece a localização da persistência. Entenda que essa localização pode mudar e ver variada. Se o fornecedor conhecer essa localização ele está puxando para si uma responsabilidade que não lhe cabe. Não é que o forncedor não se possa persistir, é que ele não sabe onde.
Compara com Serializable. Vc pode implementar no objecto um write e um read para substituir a seriaização padrão. Aqui o objeto está chamando para si a responsabilidade de quais dados e em que ordem os vai serializar, mas o processo não é da responsabilidade dele, o Objectstream que sabe a localização e o algoritmo para a serialização.
No caso do fornecedor ele não tem meios de sobreecrever as operações crud, mas tb não precisa, já que tudo é feito pelo sistema de persistência.
Então sim, existem uma forte necessidade de separar a persistencia da entidade porque ela não detalhes da persistencia e como tal não pode ter essa responsabilidade.
O encapsulamento não tem nada a ver com o problema já que tanto em entidade.save() como em algo.save(entidade) ninguem está vendo como o save acontece realmente.
[quote=sergiotaborda]Não. Em O não ha “relacionamentos”, ha objetos contendo objetos. Isso é um grafo.
(…)
2) A relação entre cliente e pedido não forma um grafo fixo.[/quote]
Eu realmente fiquei confuso com essa concepção de grafo, não entendi. Imagine que uma classe Pedido possui um atributo Cliente. Isso não pode ser visto como um relacionamento entre as duas classes? Seguindo o que você falou, Cliente pertence ou não ao grafo de Pedido? Se você tiver um link falando sobre grafos nesse contexto eu gostaria de ver, não consegui entender bem.
[/quote]
Pedido contém um atributo do tipo Cliente. Sim. Cliente pertence ao grafo de Pedido e eu faço pedido.getCliente().
O cliente do pedido reponde a "Quem fez o pedido?"
Mas se eu quiser responder a “Quais pedidos o cliente fez?” ai eu uso cliente.getPedidos().
Com estas duas coias eu posso responder a “Que pedidos o cliente que fez este pedido, fex?” apenas com pedido.getCliente().getPedidos().
Isto é uma regra de dominio.
O facto de pedido ter um atributo ter um cliente sim pode ser encarado como um relacionamento entre as duas entidades, mas esse relacionamento é “virtual” ou seja, não existe um objeto Relacionamento que tenha um referencia a cada um. (Poderia haver, mas normalmente não ha). O que ha é uma Referencia de pedido a cliente , mas não de cliente a pedidos. Quando executa Cliente.getpedidos() vc está executando uma regra de negocio que é muito mais complexa que pedido.getCliente(). cliente.getPedido() implica num raciocinio “procura os pedidos onde o cliente é this”
Em codigo :
[code]class Pedido {
Cliente cliente; // attributo. Cliente pertence ao grafo fixo de Pedido
}
class Cliente {
// não ha nenhuma referencia ao Set<Pedido>
public Set<Pedido> getPedidos(){
return PedidoRepository.findByClient(this);
}
}[/code]
Embora do lado de fora pareça que Set<Pedidos> existe dentro de cliente, ele não existe de fato. Isso é a vantagem de encasulação.
O que tudo isto interessa?
Quando vc salva o pedido , ha um campo na tabela Pedido que diz respeito ao cliente. Logo o sistema tem que averiguar se o cliente está tb salvo. Se não, salva-o ( ou salva-o logo por default e pronto)
Mas quando o sistema salva o cliente ele não salva a coleção de pedidos. Porque ele não salava? porque não ha nenhum atributo desse tipo em cliente.
Então getPedidos() parece igual a getCliente(), mas não são. Mas o objetivo do DDD é exatamente que pareçam, mesmo que internamente não sejam.
Uau. Excelente explicação Sérgio.
Com código ainda que facilita bastante o entendimento.