Primeira coisa gostaria de dar os parabéns ao Phillip pela matéria.
Mas como toda boa matéria ela também me deixou com algumas dúvidas:
Um POJO precisa saber como ficar num estado consistente? Ou ele deve delegar isso a um outro objeto?
Somente exemplificando, em livros de O.O. é pregado que um objeto tem sempre que estar num estado válido e que o construtor dele deve se encarregar disso. Já pela convenção JavaBeans e até Pojos, vejo que existem brechas que PODEm deixar o objeto num estado inconsistente.
Pelo que entendi da matéria os objetos devem sempre estar num estado consistente e eles próprios devem se certificar de que isso aconteça.
Se sim, caso seja fornecido um argumento invalido para o construtor ou para um setter o melhor seria dar um throw numa IllegalArgumentException com uma mensagem bem animadora?
Pergunto isso, pois em vários lugares vi que POJOS praticamente não sabem o porquê vieram ao mundo, ja na matéria, pelo que entendi, os objetos sabem muito bem que espaço devem ocupar.
[quote=grprado]Primeira coisa gostaria de dar os parabéns ao Phillip pela matéria.
[/quote]
Obrigado
Mais que POJOs, objetos de uma maneira geral devem garantir seu estado. Programar com contratos (DBC - Design by Contract) reforça isto através de algumas regras. uma pequena introdução á DBC pode ser encontrado aqui.
Note que mesmo numa linguagem totalmente integrada com DBC como Eiffel pdoe acontecer de um objeto não amnter consistência. O programador deve garantir a validade do estado do objeto. Linguagens como a citada Eiffel provêem recursos para facilitar esta verificação, em Java não tem muito jeito, é no braço mesmo.
Não conhecia DBC, mas já imaginava algo do tipo e que não seria muito fácil.
Quanto aos fantoches, o grande problema é que em muitos lugares eu via algo resumidamente deste tipo:
Camada de persistência retorna um pseudo-objeto (um objeto java travestido de struct, somente dados sem comportamento)
Ai a camada de negocios lida com o pseudo-objeto e vários objetos super fodões dessa camada fazem as operações de negocio e implementam externamente um compartamento interno do pseudo-objeto, por fim devolvem os dados para o usuário ou para serem persistidos novamente.
Era nesse ponto que eu me perdia, pois isso de O.O. não tem nada.
Após ler os artigos, MJ17 e do seu wiki, acredito ter encontrado um bom caminho para trilhar. Mais uma vez, obrigado.
Opa, tenho uma pergunta sobre a mesma matéria, dentro do mesmo contexto.
Seguinte, no exemplo da listagem 1, tem um factory method para criar o cliente. Eu gostei muito desse approach pois caso a criação do objeto seja complexa a mesma fica bem encapsulada e cria o obj com um estado válido.
Mas como ficaria se fossem mais argumentos, ou seja, o cadastro de cliente tivesse uns 20 campos. Não da para passar String por String…
Qual seria uma forma legal de se fazer isso?
Eu pensei em algumas:
:arrow: Um Hash map com os argumentos.
:arrow: No caso de um app web (o meu caso) passar o form bean (Struts), ou managed bean(JSF).
:arrow: Começar a criar o obj na action, a parte simples tipo setNome e etc, e passar esse quase obj para o factory method terminar o serviço.
Cada um tem sua desvantagem, falta de verificação em tempo de compilação, acoplamento, e criar um obj com estado inválido.
Como vc. notou, todas têm problemas mas esta é a que eu utilizaria. Outra opção ainda é ter um construtor que receba um POJO de dados de inicialização, que, por contrato, não teria que manter nenhum tipo de consistência entre parâmetros.
De qualquer forma, acho que o problema está no requisito “objeto consistente em qualquer momento”. Este requisito parece fazer todo sentido quando visto de longe, mas não sei se faz sentido mantê-lo a qualquer custo.
Na prática, creio que basta garantir a validade de invariantes em determinados momentos, em particular nos momentos em que existe efetivamente um contrato a ser obedecido, como em uma chamada de um método de negócio. AOP neste caso vem bem a calhar, pois pode ser usada para injetar a validação do estado apenas nos momentos em que isto é relevante.
O conceito de contrato implementado em Aspectos é bem útil e relevante.
Algum tempo atrás, ainda não se usava muito AOP, e fazer validações no início de métodos para verificar se um objeto veio com todos os campos necessários para realizar tal processo era entrelaçado no código, isso rolava muito no projeto na época pq haviam muitas integrações, diversos sitemas interoperando, e os contratos foram implementados na época de forma entrelaçada …
Se fosse hoje em dia AOP era sem dúvida alguma a melhor pedida!
Exatamente. Digamos que a invariante de um obj dependa da camada de aplicação, por exemplo, um relacionamento com um objeto proveniente da camada de persistência. Então nesse caso faz muito sentido o factory method, onde a criação ficará encapsulada em um único local.
Neste caso, acho que não faz sentido criar o objeto sem o relacionamento no managed bean, e depois enviá-lo para a camada de aplicação fazer o relacionamento.
[quote=[JUZAM]]Opa, tenho uma pergunta sobre a mesma matéria, dentro do mesmo contexto.
Seguinte, no exemplo da listagem 1, tem um factory method para criar o cliente. Eu gostei muito desse approach pois caso a criação do objeto seja complexa a mesma fica bem encapsulada e cria o obj com um estado válido.
Mas como ficaria se fossem mais argumentos, ou seja, o cadastro de cliente tivesse uns 20 campos. Não da para passar String por String…
Qual seria uma forma legal de se fazer isso?
Eu pensei em algumas:
:arrow: Um Hash map com os argumentos.
:arrow: No caso de um app web (o meu caso) passar o form bean (Struts), ou managed bean(JSF).
:arrow: Começar a criar o obj na action, a parte simples tipo setNome e etc, e passar esse quase obj para o factory method terminar o serviço.
Cada um tem sua desvantagem, falta de verificação em tempo de compilação, acoplamento, e criar um obj com estado inválido.
Qual a opinião de vocês?[/quote]
Acho que para criação de objetos complexos ou até povoar objetos seria interessante usar um builder!
Repositórios são objetos de domínio e anda impede que sejam manipulados pelas Camadas de Aplicação e até Apresentação.
O ponto importante é que repositórios não são DAOs, então sua Camda de Aplicação/Apresentação deve tratar Repositórios como trata (baseado no exemplo da revista) Proposta, Emprestimo, etc.
Um erro que cometi no artigo foi colocar um método salvar() no Repositório. Idealmente Repositórios devem ter uma interface parecida com a de Collections, em vez de salvar() e apagar() pense em adicionar() e remover().
Sendo assim, você poderia fazer um Service retornar um Repository. Eu não recomendo fazer este processo sem um Service porque você perde os benefícios deste ser um Façade, que incluem ter uma interface mínima. Se você expôr os métodos do Repository como interface da Camada de Negócios vai acabar com muitos métodos que quase nunca são usados, interfaces de Camadas em geral e Services em particular devem ter granularidade grossa.
Repositórios não tem a ver com persistência. O fato de geralmente serem relacionados com a Camda de Persistência é um detalhe de implementação, como falei acima Repositórios não são DAOs.
Repositório é o lugar onde você acumula seus objetos, são coleções, listas.
Neste caso:
class Usuarios{
Set<Grupo> grupos = null;
}
É o mesmo princípio.
O ponto é que geralmente Repositórios contêm lsitas de todos os objetos de um determinado tipo, logo dificilmente um Usuario teria um RepositorioGrupos, já que este repositório não guarda apenas os eus grupos mas todos os do sistema.
Um GerenciadorUsuario, entretanto, pode ter acesso ao repositório já que ele precisa saber onde ficam todos os grupos do sistema.
O caso seria o que o JUZAM falou. Quero só obter dados para popular a tela.
O repositório não é DAO, mas é responsável pela “manutenção” dos Domain Objects como se eles estivem sempre em memória (a implementação do Repository pode ser o próprio DAO certo?). Imagine que eu queria encapsular todas as buscas no próprio repository (que é quem sabe dos dados). Veja o pseudocode.
TelaClientesModel {
.
.
// Solução A
private Cliente[] obterClientes() {
return repositorioCliente.getAll();
}
ou
// Solução B
private Cliente[] obterClientes() {
return serviceCliente.getAll(); //só delega para repositorioCliente.getAll
}
ou ainda se entendi a sua sugestão
// Solução B
private Cliente[] obterClientes() {
return serviceCliente.getRepository().getAll();
}
}
Sobre o service, não sei o que é pior, ter operações que nunca são chamadas ou fazer uma fachada que só delega coisas. Entre essas estou tendencioso a deixar as coisas mais simples. Para buscas chamar o Repository direto.
E sobre a granularidade, isso me causa dor de cabeça. Tinhamos uma prática aqui de 1 Service para cada Caso de Uso (não sei de onde surgiu). Achei essa abordagem ruim. Ela deixa claro as funcionalidades envolvidas no caso de uso, mas gera muita delegação desnecessária (principalmente para buscar as coisas).
Obrigado pelas sugestões e vamos continuar com a discussão. Aliás, tenho dúvidas de onde seria melhor colocar o controle de transação nesse caso…(muito tempo em análise faz mal).
Com o Repository na mão você não precisa expôr métodos de pesquisa no Service. De qualquer forma usar o Repository fora da Camada de Negócios só para consultas (em algums casos extremos você poderia ter uma interface que seus clientes conhecessem e usassem apenas com métodos de consulta…).
Quando a granularidade, ter um caso de uso por Service não é a única opção, tudo depende do nível que você quer. Eu refatorei um sistema que implementava Services como Commands e pelas minhas cotnas haviam mais de 220 Services (que, para piorar, eram EJBs).
Existem algumas herurísticas apra descobrir uma granularidade legal, uma das mais interessantes que já vi é a do livro UML Components, mas mesmo essa não se aplica a tudo (principalmente por se basear demais em casos de uso e seus passos e fluxos). É muito relativo.
Quanto á transações, marque-as para terminar no final do caso de uso. Usar transações programaticamente em 2006 é furada, use Spring, EJB 3.0, AOP ou alguma coisa que permita tirar este código da sua regra de negócio. Se não tiver jeito de usar um bom framework, coloque este código commit/rollback na Camada de Aplicação.
Imagino que no repository, fora adicionar(obj) e remover(obj), basicamente o resto seriam buscas que não seriam arriscadas expor para as camadas mais altas.
Estou pensando em fazer um repositório genérico que sabe guardar coisas com as operações acima e os outros repositórios terão buscas.
interface Repositorio {
adicionar(Object object);
remover(Object object);
}
interface RepositorioClientes {
Cliente[] getAll();
Cliente[] getPorIdade(Long idade);
Cliente getPorId(Long id);
}
abstract class PersistentDomainClass {
Repositorio repositorio; //
public salvar() {
repositorio.adicionar(this);
}
public excluir() {
repositorio.excluir(this);
}
}
class Cliente extends PersistentDomainClass {
Long id;
String nome;
Date dtNasc;
String rg;
RepositorioClientes repositorio;
// outros metodos
}
Dessa forma não haveria risco do RepositorioClientes estar diretamente na camada de aplicação.
Pela minha experiência acho ruim amarrar qualquer tipo de referência no código ao caso de uso (eg. ManterClienteService). Caso de uso não é componente. Esse refactorings nos façades sempre vão ter…
Sim, mas Casos de Uso delimitam as transações, componentes não. De qualquer modo você sempre pode marcar Services como REQUIRED e ter uma gestãod e transação eficiente sem precisar de muito aborrecimento. Aliás desta forma você elimina também a necessidade de métodos save() e delete(), desde que utilizando uma infra-estrutura baseada num framework moderno, como Hibernate, ou até mesmo em EJBs 2.x.
Para buscas, existe um padrão de Eric Evans/Fowler chamado Specification que basicamente verifica se o objeto em questão obedece um certo critério. Como uma Specification é um objeto de domínio você pode utilizar o padrão do Fowler chamado QueryObject para implementá-lo, como um DAO implementaria um Repository um QueryObject implementa uma Specification. Isso elimina métodos do tipo returnAll() no Repository, basta um método que aceite um Specification.
Uma transação, mesmo que não implementada diratamente no código, não seria algo inerente a regra de negócio em questão.
Pelo que eu entendi, na orientação ao objetos eu teria objetos colaborativos.
ex:
A transação faz parte do próprio escopo da operação e não é uma coisa alheia a isso.