Todos nós sabemos
da importância da camada 'DAO', ou seja, classes especializadas na
comunicação com banco de dados. Isso promove uma série de
benefícios: reutilização de código, isolamento etc. Porém é
comum encontrarmos na internet exemplos de código onde as classes da
camada 'Controller' (podem ser Servlets, ManagedBeans do JSF,
Controllers do Spring MVC etc) acessam diretamente as classes DAO.
Nesse artigo vou introduzir a importância da camada 'Service' entre
as camadas 'Controller' e 'DAO'.
Vamos considerar o
exemplo tradicional de transações bancárias. Nesse exemplo, será
demonstrada a parte do sistema que faz uma transferência entre
contas. Para essa operação, é preciso obedecer a seguinte regra de
negócio: 'Antes de creditar na conta destino, deve-se verificar se
há saldo disponível no valor da transferência na conta a ser
debitada'.
Vejamos o código:
Entidade: Conta.java
1 2 @Entity 3 @Table(name = "de_conta") 4 public class Conta { 5 6 @Id 7 @GeneratedValue(strategy = GenerationType.AUTO) 8 private Long id; 9 private BigDecimal saldo; 10 11 //construtor padrao 12 public Conta() { 13 } 14 15 //construtor com parametro 16 public Conta(Long id) { 17 this.id = id; 18 } 19 20 //getters e setters 21 } 22
DAO: ContaDAO.java
1 2 public class ContaDAO { 3 4 private EntityManager em;//injetado ou obtido pelo construtor 5 6 public Conta loadById(Long id) { 7 return em.find(Conta.class, id); 8 } 9 10 public void atualizarSaldo(Conta conta) { 11 em.merge(conta); 12 } 13 } 14
Servlet
ContaController.java
1 2 public class ContaController extends HttpServlet { 3 4 private ContaDAO dao;//injeta ou instancia o DAO 5 6 protected void service(HttpServletRequest request, HttpServletResponse response) 7 throws ServletException, IOException { 8 //carrega os saldos 9 Conta destinatario = dao.loadById(Long.parseLong(request.getParameter("dest_id"))); 10 Conta remetente = dao.loadById(Long.parseLong(request.getParameter("rem_id"))); 11 12 //verifica se tem saldo disponivel no remetente 13 BidDecimal valorATransferir = new BigDecimal(request.getParameter("valor")); 14 if (remetente.getSaldo().compareTo(valorATransferir) == -1) { 15 //saldo insuficiente 16 request.getRequestDispatcher("saldo_insuficiente.jsp").forward(request, response); 17 } else { 18 //saldo disponivel! debita o valor da conta do remetente 19 remetente.setSaldo(remetente.getSaldo.subtract(valorATransferir)); 20 //credita na conta do destinatario 21 destinatario.setSaldo(destinatario.getSaldo().add(valorATransferir)); 22 23 //atualiza os novos saldos no banco 24 dao.atualizarSaldo(remetente); 25 dao.atualizarSaldo(destinatario); 26 request.getRequestDispatcher("sucesso.jsp").forward(request, response); 27 } 28 } 29 } 30
Temos aí a nossa
aplicação funcionando. Porém temos alguns problemas:
- A lógica de
negócio está bem atrelada a API de Servlets (há várias chamadas
aos métodos de HttpServletRequest) tornando traumático a
implantação posterior de algum framework web nesse projeto.
- Não é possível
realizar testes nesse código sem que esteja num ambiente web.
- Não podemos
reutilizar esse código (verificação do saldo do remetente) em
outros 'Controllers' (Servlets). Se quisermos por exemplo além da
internet, permitir transferencias via aplicativos mobile com chamadas
em nosso servidor java, teremos que 'copiar e colar' esse código no
servlet que atenderá essa nova chamada e possivelmente responderá
com um json, ao invés de encaminhar para uma página .jsp.
Ou seja, embora
aparentemente o desenvolvimento seja 'mais rápido' fazendo o
controller acessar diretamente o dao, os problemas dessa arquitetura
aumentarão em muito o custo de manutenção e expansão de nosso
sistema.
Vamos agora
introduzir a camada Service (A Entidade e DAO não sofrem alterações):
Service: ContaService.java
1 public class ContaServce { 2 3 private ContaDAO dao;//injeta ou instancia DAO 4 private Conta remetente; 5 private Conta destinatario; 6 private BigDecimal valorATransferir; 7 8 public void transferir() throws Exception { 9 destinatario = dao.loadById(destinatario.getId()); 10 remetente = dao.loadById(remetente.getId()); 11 12 //verifica se hรก saldo disponivel no remetente 13 if (remetente.getSaldo().compareTo(valorATransferir) == -1) { 14 //saldo insuficiente 15 throw new Exception(); 16 } else { 17 //saldo disponivel! debita o valor da conta do remetente 18 remetente.setSaldo(remetente.getSaldo.subtract(valorATransferir)); 19 //credita na conta do destinatario 20 destinatario.setSaldo(destinatario.getSaldo().add(valorATransferir)); 21 22 //atualiza os novos saldos no banco 23 dao.atualizarSaldo(remetente); 24 dao.atualizarSaldo(destinatario); 25 26 //tudo ok 27 } 28 } 29 //getters e setters 30 } 31
Servlet: ContaController.java (alterado)
1 public class ContaController extends HttpServlet { 2 3 private ContaService service;//injeta ou instancia Service 4 5 protected void service(HttpServletRequest request, HttpServletResponse response) 6 throws ServletException, IOException { 7 //informa o Service os dados que ele precisa para executar a lรณgica 8 service.setRemetente(new Conta(Long.parseLong(request.getParameter("rem_id")))); 9 service.setDestinatario(new Conta(Long.parseLong(request.getParameter("dest_id")))); 10 service.setValorATransferir(new BigDecimal(request.getParameter("valor"))); 11 try { 12 service.transferir(); 13 request.getRequestDispatcher("sucesso.jsp").forward(request, response); 14 } catch (Exception e) { 15 request.getRequestDispatcher("saldo_insuficiente.jsp").forward(request, response); 16 } 17 } 18 } 19
Pronto! Vamos aos
benefícios dessa arquitetura:
- Agora seu service
pode ser chamado independente da camada web! Seja Servlets, JSF, GWT,
Vaadin etc. Não importa. A logica em Service desconhece os detalhes
da camada externa. Assim você pode implantar e alternar entre vários
Frameworks Web sem ter que alterar sua logica de negócio em Service.
- Voce pode
reutilizar seu Service em diversos Servlets, aumentando assim a
reusabilidade de seu código e futuras expansões.
- Voce pode
inclusive invocar outros Services dentro de um Service.
- Você pode dentro
de uma unidade de teste instanciar o Service e testar sua lógica,
mesmo sem estar num ambiente web.
Convencido dessa
arquitetura? Não é a toa que o padrão JEE (especificamente EJBs)
forcem de certa maneira essa arquitetura. Você coloca sua lógica de
negócio em EJBs (nossos Services) e esses EJBs podem ser injetados
em Servlets ou ManagedBeans via @EJB. Porém mesmo em um container
simples como o Tomcat, você pode se valer dos benefícios dessa
arquitetura.
Porém ainda podemos
melhorar ainda mais essa arquitetura. Num próximo artigo vamos
introduzir a utilização de Interfaces para nossos DAOs e Services.
Porque fazer nossos DAOs e Services implementarem interfaces?
Att,
Gustavo Marques