Páginas

segunda-feira, 8 de junho de 2015

Introduzindo a camada 'Service' entre as camadas 'Controller' e 'DAO' de sua aplicação.

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

Veja também

Related Posts Plugin for WordPress, Blogger...