Arquitetura de uma aplicação WEB + MVC + Frameworks action-based

27 respostas
danielbussade

Olá pessoal relendo alguns posts do GUJ, como este http://www.guj.com.br/posts/list/15/60916.java
e outros do blog da caelum como este http://blog.caelum.com.br/2007/06/09/repository-seu-modelo-mais-orientado-a-objeto entre outros lugares, comecei a me questionar sobre a maneira como tenho arquiteturado minhas aplicações WEB.

Atualmente quando vou fazer um CRUD, faço da seguinte maneira, tentando ser o mais simples possível, utilizando algum framework MVC.

REQHTTP - > Action -> DAO -> SGDB

OBS
*Quando os dados chegam na Action, o POJO já vem populado pelo meu framework, como mentawai ou VRaptor.

*Minha classe Action não tem um Dao como atributo, o mesmo é instanciado dentro de cada método, isso porque utilizo um Dao generico(antes utilizava um dao para cada classe e injetava o mesmo por IoC), mas hoje prefiro a abordagem de um Dao generico, uma vez que consegui diminuir muito os numeros de .java na minha aplicação.

Quando preciso de uma funcionalidade mais "complexa", digamos logo apos o cadastro de um usuario mandar um e-mal para o mesmo. Neste caso crio um service, como no exmplo;

class UserService {

public void save(Usuario usuario) //Usuario aqui vem populado, enviado pela action{
HibernateDao<Usuario> usuarioDao=new HibernateDAO<Usuario.class>();
usuarioDao.save(usuario);
sendMail();

}

private sendMail(){
//codigo para enviarEmail
}

}

As lógicos de negócio ficam nas classes de negócio como, a action servindo somente para recuperar os parametros da view e mandar para o model.

[code]
public class UserAction{

public changePassword(){
String oldPassword=input.getStringValue("oldPassword");
String newPassword=input.getStringValue("newPassword");


Usuario user=(Usuario)session.getAtributte("user");

user.changePassord(oldpassword,newpassword);

//try -catch -aqui tratando as excecoes da camada de negocio;

}

}

public class User

public changePassword(String olPassword, String newPassword){

if(senha.equals(oldPassword){
  setSenha(newPassword);
}else{
 throw new BussinessException("Senha invalida"); //Exceção qualquer de negocio
}

}

Uma nova abordagem que estou pensando a utilizar é a seguinte:
REQHTTP -> Action -> Modelo de Negocio > Repository(interface) > Dao> SGB

Como neste artigo http://blog.caelum.com.br/2007/06/09/repository-seu-modelo-mais-orientado-a-objeto onde o autor fala ao inves de ter um codigo como este:

public class Logica { // extends Action? ;)
  public void mostraFornecedorEContasPagas() {
    Long id = request.getParameter("id"); 
    Fornecedor f = fornecedorDAO.load(id)
    List<Conta> contasPagas = contasDAO.listaContasPagasDoMes(fornecedor);
    // ejeta tudo para mostrar na view. No século passado seria:
    request.setAttribute("fornecedor", fornecedor);
    request.setAttribute("contasPagas", contasPagas);
  }
}

Pode ser substituido por


Onde Fornecedor tem um repository de contas!

Realmente fica bem mais OO, mais vejo algumas desvantagens, isso não seria uma violação de camada ou seja a view, está acessando diretamente o DAO, pode argumentar que repository é um conceito de negocio, tudo bem, mas no caso seria so um delegate para o DAO, isso não seria violar camadas?

Estou pensando em usar a segunda abordagem, mas antes queria ouvir sugestões. Qual estrutura geralmente vcs usam para acessar um SGDB:

REQHTTP -> Action ->Dao > SGB
REQHTTP -> Action -> Modelo de Negocio > Repository(interface) > Dao> SGB

ou ainda

req HTTP -> Action -> Modelo -> Repository -> Implementação concreta de Repository -> DAO -> Implementação concreta de UserDAO -> Banco de dados

Valeu

27 Respostas

saoj

Eu mesmo já embazei diversas vezes o WEB -> Action -> Service/Logic -> DAO/Repository -> DB

Mas muitas vezes isso é um overkill, um tédio de redundancia e classes desnecessárias.

Dependendo do projeto eu hoje faço WEB -> Action -> DAO -> DB. Teoricamente isso não é o mais certo, mas na prática e disparado o mais conveniente. Muitos projetos RoR são feitos assim.

Se vc está preocupado com acoplamento, dê uma olhada nesse artigo:

http://www.theserverside.com/news/thread.tss?thread_id=48304

Rubem_Azenha

saoj:
Eu mesmo já embazei diversas vezes o WEB -> Action -> Service/Logic -> DAO/Repository -> DB

Mas muitas vezes isso é um overkill, um tédio de redundancia e classes desnecessárias.

Dependendo do projeto eu hoje faço WEB -> Action -> DAO -> DB. Teoricamente isso não é o mais certo, mas na prática e disparado o mais conveniente. Muitos projetos RoR são feitos assim.

Se vc está preocupado com acoplamento, dê uma olhada nesse artigo:

http://www.theserverside.com/news/thread.tss?thread_id=48304

Eu também acho que as vezes podemos chamar o DAO\Repository direto da Action (telas de pesquisa, por exemplo), mas isso acaba sendo um problema sério para manutenção.

Mesmo que você diga que se um dia precisar de mais coisas você vai refatorar e colocar num objeto de negócio, muitas vezes isso não é feito, é muito mais fácil fazer certo da primeira vez do que deixar pra depois “quando precisar”.

Geralmente mesmo para CRUDs, as operações de “update” eu prefiro colocar em classes de negócio. Sempre tem uma verificação, uma validação, um fluxo, um workflow que deve ser executado antes ou depois de criar ou alterar uma entidade e fazer dao.save na action é medir pra vir um code monkey depois e colocar código na action que não deveria estar na action… Já deixar tudo separado corretamente orienta melhor quem for dar manutenção no código depois.

L

Depende do estilo que você quer fazer.

Se a lógica de sua aplicação estão em classes comumente chamadas “de negócio” (não gosto desse termo, prefiro classes de serviço). Então é natural pensar que toda Action sempre invoca um método da classe de Serviço.

Agora, ao colocarmos as lógicas nas próprias entidades (e, segundo alguns, deixando mais OO), então as classes de serviço desaparecem e não é nada de errado a Action obter uma entidade de um repositório e depois invocar um método desse objeto retornado.

É importante frisar que DAO não é repositório. DAO possui uma abstração puramente de persistência e repositório é uma abstração de negócio. Pode-se implementar o Repositório como uma interface da camada de negócio e o DAO como uma classe que a implementa da camada de persistência. Mas não é o único jeito e não precisa ser assim por toda a sua aplicação.

Como relação ao fato de, se a view chamar algo do objeto do domínio, então ocorreria alguma violação, a resposta é não. Pelo MVC é permito a view acessar o model. Pela arquitetura em camadas, a camada de apresentação (sua action e seu jsp) pode ter acesso à camada de negócio (suas entidades, repositórios, eventuais serviços e factories).

Rubem_Azenha

Leonardo3001:

É importante frisar que DAO não é repositório. DAO possui uma abstração puramente de persistência e repositório é uma abstração de negócio. Pode-se implementar o Repositório como uma interface da camada de negócio e o DAO como uma classe que a implementa da camada de persistência. Mas não é o único jeito e não precisa ser assim por toda a sua aplicação.

Por favor, não vamos voltar nessa ladainha de que DAO não é repositório. Já foi inumeras vezes discutidas aqui e ficou claro que o ponto não é esse.

danielbussade

Rubem Azenha:
saoj:
Eu mesmo já embazei diversas vezes o WEB -> Action -> Service/Logic -> DAO/Repository -> DB

Mas muitas vezes isso é um overkill, um tédio de redundancia e classes desnecessárias.

Dependendo do projeto eu hoje faço WEB -> Action -> DAO -> DB. Teoricamente isso não é o mais certo, mas na prática e disparado o mais conveniente. Muitos projetos RoR são feitos assim.

Se vc está preocupado com acoplamento, dê uma olhada nesse artigo:

http://www.theserverside.com/news/thread.tss?thread_id=48304

Eu também acho que as vezes podemos chamar o DAO\Repository direto da Action (telas de pesquisa, por exemplo), mas isso acaba sendo um problema sério para manutenção.

Mesmo que você diga que se um dia precisar de mais coisas você vai refatorar e colocar num objeto de negócio, muitas vezes isso não é feito, é muito mais fácil fazer certo da primeira vez do que deixar pra depois “quando precisar”.

Geralmente mesmo para CRUDs, as operações de “update” eu prefiro colocar em classes de negócio. Sempre tem uma verificação, uma validação, um fluxo, um workflow que deve ser executado antes ou depois de criar ou alterar uma entidade e fazer dao.save na action é medir pra vir um code monkey depois e colocar código na action que não deveria estar na action… Já deixar tudo separado corretamente orienta melhor quem for dar manutenção no código depois.

Rubem, só para ficar claro para mim, quando você disse “classes de negócio” quer dizer tipo UsuarioService, ou quis dizer a propria entidade Usuario?

Rubem_Azenha

Hum… digamos que tanto faz. Se a regra de negócio precisa ser “orquestrada”, UsuarioService, caso contrario, vai na própria entidade Usuario mesmo.

danielbussade

Rubem Azenha:
danielbussade:

Rubem, só para ficar claro para mim, quando você disse “classes de negócio” quer dizer tipo UsuarioService, ou quis dizer a propria entidade Usuario?

Hum… digamos que tanto faz. Se a regra de negócio precisa ser “orquestrada”, UsuarioService, caso contrario, vai na própria entidade Usuario mesmo.

Supondo então que a regra de negócio não precise ser orquestrada. Ai você coloca as operacoes de CRUD todas na classes do seu Domain?

E no domain você acesso o que, uma interface Dao? Ou coloca a implementação Dao direto?

L

danielbussade:
Supondo então que a regra de negócio não precise ser orquestrada. Ai você coloca as operacoes de CRUD todas na classes do seu Domain?

E no domain você acesso o que, uma interface Dao? Ou coloca a implementação Dao direto?

Talvez eu leve uma segunda “patada” por desviar o tópico, mas enfim… vamos lá.

Se você tem apenas CRUD, então toda a arquitetura é desnecessária, e basta fazer uma tela refletir as operações do banco. Mas a realidade não é essa, CRUD é superenfatizado, e na realidade, operações estritamente CRUDs são mais raras do que muita gente imagina. Afinal, não é tudo que pode excluído, atualizações podem exigir regras de carência, inserções são seguidas de validações e só podem ser feitas por um seleto grupos de pessoas, e etc.

Mas entendo a sua dúvida, você quer saber se a própria entidade deve lidar com sua própria persistência. Pois bem, certas coisas são impossíveis - exemplo: o SELECT - porque um objeto não pode consultar a si mesmo, se este ainda não existe. É necessário um outro objeto pra isso, o Repositório. As outras operações até podem ser feitas pelo próprio objeto, mas como é difícil injetar infraestrutura em objetos de domínio (você dependeria de factories), é melhor ter o repositório fazer as operações de escrita também.

danielbussade

Leonardo3001:
danielbussade:
Supondo então que a regra de negócio não precise ser orquestrada. Ai você coloca as operacoes de CRUD todas na classes do seu Domain?

E no domain você acesso o que, uma interface Dao? Ou coloca a implementação Dao direto?

Talvez eu leve uma segunda "patada" por desviar o tópico, mas enfim… vamos lá.

Se você tem apenas CRUD, então toda a arquitetura é desnecessária, e basta fazer uma tela refletir as operações do banco. Mas a realidade não é essa, CRUD é superenfatizado, e na realidade, operações estritamente CRUDs são mais raras do que muita gente imagina. Afinal, não é tudo que pode excluído, atualizações podem exigir regras de carência, inserções são seguidas de validações e só podem ser feitas por um seleto grupos de pessoas, e etc.

Mas entendo a sua dúvida, você quer saber se a própria entidade deve lidar com sua própria persistência. Pois bem, certas coisas são impossíveis - exemplo: o SELECT - porque um objeto não pode consultar a si mesmo, se este ainda não existe. É necessário um outro objeto pra isso, o Repositório. As outras operações até podem ser feitas pelo próprio objeto, mas como é difícil injetar infraestrutura em objetos de domínio (você dependeria de factories), é melhor ter o repositório fazer as operações de escrita também.

Leonardo , obrigado pelas respostas. Estava com esta idéia de colocar metodos CRUD na Entity,mas ainda acho muito esquisito as Entity, terem metodos de CRUD, acho melhor as entity refletir estritamente o Negócio, tendo somente atributos e metodos referentes ao negocio.

Da maneira como faço:
REQHttp -> Action - > DAO -> SGBD

A minha unica duvida é se ao invés de chamar um Dao direto da action, chamar um Façade como GerenciadorUsuario onde teria um metodo create(parametros_do_usuario) e neste FAçade chamaria o Dao, ou ate mesmo um Service, se precisa de alguma "orquestração", como obserevou o rubem.

Este Dao seria uma interface assim não acoplo nem minha action, nem o Façade a infraestrutura de persistencia.

Ao inves de tornar isso padrão, so crio um service ou um Façade, quando preciso de algo a "mais" assim evito delegação de camadas desnecessária.

renanreismartins

Rubem Azenha:
danielbussade:

Rubem, só para ficar claro para mim, quando você disse “classes de negócio” quer dizer tipo UsuarioService, ou quis dizer a propria entidade Usuario?

Hum… digamos que tanto faz. Se a regra de negócio precisa ser “orquestrada”, UsuarioService, caso contrario, vai na própria entidade Usuario mesmo.

Como assim orquestrada ?

otimo topico galera… bom sempre fiz acesso aos DAOs pela action mesmo, mas ultimamente tenho criado os service. Acho que desta forma fica até mais facil de desenvolver os testes.

danielbussade

Pense em “orquestrada”, como o seguinte. Digamos que tenha uma regra de Negocio em duas classes diferentes e você precisa executar as duas regras de negocio para formar uma terceira.

Ai usamos um Service, pois ele não tem estado apenas fornece um serviço , que não é responsabilidade “unica” de ninguem, mas sim responsabilidades combinadas, entre duas ou varias classes;

Qualquer duvida, pergunte!

Valeu

renanreismartins

muito obrigado pela explicação e atençao amigo.

seguindo este ponto de vista, como vc gerencia transacoes ?

como pode usar 2 regras para formar uma terceira entao o melhor seria abrir e fechar transacoes no inicio e fim dos metodos das actions, estou certo?

abraços

danielbussade

Olá, na minha opinião o correto seria ter um filtro para controlar a transação, visto que qualquer uma das operações que falhasse teria que efetuar rollback na outra.

renanreismartins

mas ai eu abriria uma transacao para cada request ? isso nao seria ruim ?

abraços

L

Essa é a melhor alternativa que eu vejo.

Imagine se a transação fosse menor, ou seja, um para cada método. E num request, fosse chamado dois métodos, onde cada um tivesse sua transação. Agora, pense o que aconteceria se, ao chamar o segundo método, acontecesse uma exceção qualquer. Bom, o segundo método não comitaria, mas o primeiro sim, gerando inconsistência nos dados. Se a transação fosse por request, a exceção do segundo método causaria rollback no primeiro método deixando o banco de dados válido, no mesmo estado anterior.

Agora imagine se a transação fosse maior, ou seja, um para sessão. Apesar de desejável, é pouquíssimo prático, pois no mundo real, a quantidade de conexões ao banco disponíveis é escasso. A solução é usar esquemas de lock otimista (uma coluna date, ou revision, que teria um número incremental), para tratar acessos concorrentes.

renanreismartins

Oi leonardo, obrigado pela atençao,

é o seguinte: a transacao nao ficaria nos metodos de negocio, mas sim nas actions, logo dentro de uma action, poderia chamar quantos metodos fossem necessarios.

entendeu ?

abraços

fantomas

Meu tostão neste papo.

renanreismartins,

Bacana esta saída, o problema que vejo é que em um sistema maior em termos de serviços poderia fazer com que havesse redundancia; aqueles serviços que são praticamente os mesmos mas que não daria para reaproveitar por conta da presença da transação. Outro ponto é a inclusão do código de infraestrutura em um código de serviço.

O spring (framework) oferece uma solução bastante interessante para esta questão, inclusive dizem que é melhor que a solução encontrada no EJB3.

Vcs já consideraram utilizar algum componente como este para resolver este problema?

flws

aleck

fantomas:
Meu tostão neste papo.

renanreismartins,

Bacana esta saída, o problema que vejo é que em um sistema maior em termos de serviços poderia fazer com que havesse redundancia; aqueles serviços que são praticamente os mesmos mas que não daria para reaproveitar por conta da presença da transação. Outro ponto é a inclusão do código de infraestrutura em um código de serviço.

O spring (framework) oferece uma solução bastante interessante para esta questão, inclusive dizem que é melhor que a solução encontrada no EJB3.

Vcs já consideraram utilizar algum componente como este para resolver este problema?

flws

Concordo que existem soluções mais escaláveis, porém a arquitetura proposta é para aplicações pequenas, não me vejo utilizando ejb 3 ou spring para aplicações pequenas.

renanreismartins

fantomas:
Meu tostão neste papo.

renanreismartins,

Bacana esta saída, o problema que vejo é que em um sistema maior em termos de serviços poderia fazer com que havesse redundancia; aqueles serviços que são praticamente os mesmos mas que não daria para reaproveitar por conta da presença da transação. Outro ponto é a inclusão do código de infraestrutura em um código de serviço.

O spring (framework) oferece uma solução bastante interessante para esta questão, inclusive dizem que é melhor que a solução encontrada no EJB3.

Vcs já consideraram utilizar algum componente como este para resolver este problema?

flws

Fantomas, mto obrigado pela atençao amigo. Seguinte, minhas transacoes nao ficam nos servicos e sim nas actions onde posso ter inumeros serviços sendo chamados. Vc ve algum problema nessa abordagem ?

aleck, valeu cara. Que abordagem voce utiliza com relaçao as transacoes ?

abrasss a tds

aleck

renanreismartins:

aleck, valeu cara. Que abordagem voce utiliza com relaçao as transacoes ?
abrasss a tds

Em sistemas pequenos o uso de transação é mínimo, quando preciso costumo implementar userTransaction.

Se userTransaction não atender então é hora de pensar em outra arquitetura, pois as coisas já estão ficando complicadas.

fantomas

Isto sugere a velha resposta “Depende!”.

Vejo problemas se:

  1. Suas actions estão redundantes em função do seu controle de transação.
  2. O código nas suas actions está muito complexo por conta da construção do fluxo da transação.
  3. Os mesmos serviços ou diferentes combinações de serviços serão utilizados por outro tipo de tecnologia (web services, rmi, componentes de integração, etc…)

Por isso sugeri que vc utilizasse algum componente para lhe ajudar, porque na maioria das situações fica difícil de vc prever quais / como serão as transações que irão surgir pela frente.

P.S Controle de transações não é um assunto trivial (na minha opinião).

flws

saoj

Na grande maioria dos casos controle de transação é algo totalmente trivial, pois tudo que vc precisa e deseja é ter uma action atômica. Não há porque complicar isso.

No Mentawai, por exemplo, tudo que vc tem que fazer é aplicar um TransactionFilter (filtro de transação) na sua action. Ele fará com que sua action seja atomica, dando commit se ela retornar SUCCESS ou rollback se retornar outra coisa ou exception.

fantomas

saoj:
Na grande maioria dos casos controle de transação é algo totalmente trivial, pois tudo que vc precisa e deseja é ter uma action atômica. Não há porque complicar isso.

No Mentawai, por exemplo, tudo que vc tem que fazer é aplicar um TransactionFilter (filtro de transação) na sua action. Ele fará com que sua action seja atomica, dando commit se ela retornar SUCCESS ou rollback se retornar outra coisa ou exception.

hahahahahah!

Lógico…quando vc tem um componente ou um framework que faz o serviço sujo obviamente que fica trivial né!?

Não estudei o Menta prá dizer se o controle de transações é bom e aguenta o tranco. Mas imagino que sim, quando li sobre ele na revista Java Magazine o Mundo OO (não lembro agora) me passou uma boa impressão.

Se o autor do post puder utiliza-lo neste problema talvez seja a melhor resposta, quem sabe até o VRaptor seja também uma opção.

Parabéns a equipe do Menta.

flws

saoj

Via filtros (ou interceptors) a coisa fica interessante. Mas o framework tem que ser full-stack para atacar todas as partes do problema.

A sequencia no Mentawai é o seguinte:

  1. Filtro de Conexão pega uma connection do pool e coloca disponível para a action no Input. Note que vc não precisa se preocupar em devolver a conexao para o pool (try / finally) pois o filtro garante isso pra vc.

  2. A action então pega essa connection e faz o que quiser. Vai em várias tabelas e faz INSERT / UPDATE / DELETE.

Aí vc pensa. No passo 2) eu preciso que haja uma transação ou seja, se falhar o insert na última tabela eu quero dar um rollback nas alterações. Então como vc faz?

1.5) Aplica um filtro de transação na action. O que ele faz? Ele pega a connection que está no Input (passo 1), dá um setAutoCommit(false) e espera a execução da action. Se a action retorna SUCCESS ele chama commit e volta com o valor antigo do setAutoCommit. Se a action dá uma exception ou retorna outra coisa que não SUCCESS ele dá um rollback e volta com o valor antigo do setAutoCommit. Vc tb pode configurar outros valores além do SUCCESS para o commit acontecer.

Feito isso vc tem uma action atomica, ou seja, ou toda ela executa ou nada executa. Isso é o que vc precisa na grande maioria dos casos, eu diria em 95% dos casos. Claro que podem existir casos mais complicados com dois bancos de dados (duas connections diferentes), two-phase commit, etc. mas eu sinceramente, em todos os projetos webs que fiz, nunca me deparei com uma situação que necessitasse de um esquema mais complicado.

Mais uma vez: não que a situação mais complexa não existe. Ela existe sim, mas é exceção, é rara, mas mesmo assim tem gente que usa bazooka para matar mosca quando o assunto é controle transacional.

danielbussade

Muito interessante, o filtro, mas e se no caso eu nãp quiser o Filtro de transação em todo a minha action, tipo dentro da action só tenho uma innerAction, que precise de transação como faria?
Tem como aplicar o filtro de transação somente em uma innerAction?

danielbussade

Atualmente, estou trabalhando com JSF, e não quero usar o Spring(não gosto) para controlar transações, então estou desenvolvendo, um filtro de transações com base em Annotations, semelhante a este do Mentawai.

saoj

vc pode configurar um filtro como global, de uma action (classe inteira, isto eh, metodo execute e todas as inner actions) ou apenas de uma inner action.

http://www.mentaframework.org/inneraction.jsp?loc=pt

Criado 10 de maio de 2009
Ultima resposta 20 de mai. de 2009
Respostas 27
Participantes 7