Dúvida sobre interrupção de processos longos em arquitetura de 3 camadas [RESOLVIDO]

11 respostas
diego_qmota

Boa tarde pessoal,

A primeira seção explica a minha situação. A segunda corresponde a minha pergunta.

Situação atual
Estou desenvolvendo um sistema SWING onde, devido ao crescimento e aumento das necessidades, deixou de ser um programa básico de consulta e se tornará um sistema de gerenciamento com escopo muito maior e mais complexo.

O sistema é de duas camadas: a camada de apresentação e a de persistência. A camada de negócios está embutida na de persistência e algumas poucas vezes embutida na camada de apresentação (eu sei, fiz cagada :thumbdown: mas porquê só comecei a conhecer essa arquitetura agora ).

Está conforme o esquema abaixo:

USUÁRIO <-> CAMADA DE APRESENTAÇÃO <-> (CAMADA DE PERSISTÊNCIA + LÓGICA DO NEGÓCIO)

Quando o usuário requisita uma operação, a interface (camada de apresentação) faz:
[list]Cria uma Thread para executar a operação e os próximos passos ocorrem dentro dela;[/list]
[list]Instancia um DAO que será responsável pela lógica de negócios e a camada de persistência;[/list]
[list]O DAO recebe os valores da interface e executa a operação;[/list]
[list]Crio uma thread que a cada meio segundo, consulta o andamento do status no DAO e atualiza a interface do usuário;[/list]
[list]Através da camada da interface, após execução da lógica e persistência pelo DAO, informo se a operação ocorreu com sucesso. Recollho as exceções na camada da interface.[/list]

Alguns conceitos acima estão corretos, mas sinto uma dificuldade grande de trabalhar com essa arquitetura, devido a mistura: (CAMADA DE PERSISTÊNCIA + LÓGICA DO NEGÓCIO).

Por isso, comecei o processo de migração para 3 camadas… criei uma interface para representar os serviços, em nível superior, para ir controlando a migração.

Dúvidas
Tenho dúvidas sobre como devo gerenciar e controlar interrupções de operações de processos longos.
[list]O que devo fazer quando o usuário requisita a interrupção de uma operação de uma classe de Serviço? [/list]
[list]Qual é o melhor jeito de controlar a operação, seu retorno a interface do usuário e sua interrupção? Onde esse controle deve ser feito?[/list]
[list]Que tipos de validações devo fazer e de que forma devo interromper o processo? No DAO, lançando uma exceção, por exemplo? [/list]

Que metodologias vocês usam para fazer esse controle de processos longos? Que envolvam operações complexas e muitos métodos executados?

11 Respostas

rogelgarcia

Vou explicar de forma genérica aqui… como eu faria… (se eu fosse fazer um programa com interface gráfica, utilizaria SWT/jFace que inclusive já possui uma arquitetura pra fazer isso do jeito que to explicando)

Coloco uma barra de progresso para o usuário com um botão cancelar…

Enquanto isso a thread de serviços está executando…

Tanto o botao cancelar quando o objeto de serviços terão referencia para o mesmo objeto…

Quando o usuário clicar em cancelar… esse cancelar atualiza um status no objeto compartilhado com a camada de negócios

Quando possível, a camada de negócios testa o objeto verificando se é para cancelar ou nao…

Tem que ficar esperto sobre os pontos onde pode ser feito o cancelamento para nao deixar o sistema com dados inválidos… (use transaçoes para te ajudar)

P

Algumas alternativas para acessar a camada de negócios

  1. Usar alguma variante de WebServices

Os stubs gerados normalmente possuem um método para setar o timeout da operação

  1. Usar RMI

Vc. precisaria utilizar uma SocketFactory customizada onde os timeouts serias setados

  1. Usar JMS

Cada transação envolveria um envio e um recebimento de mensagem. O timeout pode ser setado para limitar o tempo de espera e vc. ainda tem a possibilidade de usar um mecanismo de poll.

Em qualquer caso, adote como padrão executar qualquer chamada que envolva acesso a um recurso remoto na forma de uma tarefa a ser executada em background. Uma olhada na classe java.util.ThreadPoolExecutor vale a pena se vc. puder usar Java 1.5+

diego_qmota

rogelgarcia:
Vou explicar de forma genérica aqui… como eu faria… (se eu fosse fazer um programa com interface gráfica, utilizaria SWT/jFace que inclusive já possui uma arquitetura pra fazer isso do jeito que to explicando)

Coloco uma barra de progresso para o usuário com um botão cancelar…

Enquanto isso a thread de serviços está executando…

Tanto o botao cancelar quando o objeto de serviços terão referencia para o mesmo objeto…

Quando o usuário clicar em cancelar… esse cancelar atualiza um status no objeto compartilhado com a camada de negócios

Quando possível, a camada de negócios testa o objeto verificando se é para cancelar ou nao…

Tem que ficar esperto sobre os pontos onde pode ser feito o cancelamento para nao deixar o sistema com dados inválidos… (use transaçoes para te ajudar)

Eu acredito que possa usar, um objeto Observer, com a referência passada para a área de negócios, para notificar a interrupção do processo. Mas vou ter que estudar o funcionamento dessa classe antes.

Hoje, como citei, crio uma thread que inicia a execução da camada de negócios e outra para ficar checando de meio em meio segundo se foi solicitada a interrupção.

Eu não gostaria de tornar a classe de Serviços filha da classe Thread, porquê o mesmo terá muitos métodos e para executá-los terei que ficar setando um atributo enum antes de chamar o método run() (para que ele saiba qual método deve executar ao rodar a thread). Mas não será possível se não for assim, né?
Talvez se eu disparasse uma InterruptedException do método da classe de serviços, mesmo ela não sendo uma thread. Aí voltava para a camada da interface e ela informava o usuário da interrupção da operação. Seria elegante, dessa forma?

diego_qmota

psevestre:
Algumas alternativas para acessar a camada de negócios

  1. Usar alguma variante de WebServices

Os stubs gerados normalmente possuem um método para setar o timeout da operação

  1. Usar RMI

Vc. precisaria utilizar uma SocketFactory customizada onde os timeouts serias setados

  1. Usar JMS

Cada transação envolveria um envio e um recebimento de mensagem. O timeout pode ser setado para limitar o tempo de espera e vc. ainda tem a possibilidade de usar um mecanismo de poll.

Em qualquer caso, adote como padrão executar qualquer chamada que envolva acesso a um recurso remoto na forma de uma tarefa a ser executada em background. Uma olhada na classe java.util.ThreadPoolExecutor vale a pena se vc. puder usar Java 1.5+

Para alguns processos poderia usar uma das opções que você passou, psevestre. Mas o aplicativo é Desktop e a execução de uma transação (ou operação) completa são feitas em uma estação.
As opções que você apresentou são mais válidas para aplicativos web e sistemas distribuídos. Mas obrigado pelas dicas! :slight_smile:

rogelgarcia

Bem… eu nao acho que a sua classe de serviços precise extender nada…

É só ela ter referencia para esse observer que vc falou… e nem precisa de uma thread ficar checando de 5 em 5 segundos… pq “matar” o serviço pode deixar o sistema em estado inválido o que nao é bom…

No seu método de serviço faz mais ou menos assim:

public void calculaX(int a, int b, Progress p){
   //faz calculos
   if(p.isCanceled()){ //geralmente processos longos tem for.. coloque um código desses dentro do for...
          return;
   }
}
diego_qmota

rogelgarcia:
Bem… eu nao acho que a sua classe de serviços precise extender nada…

É só ela ter referencia para esse observer que vc falou… e nem precisa de uma thread ficar checando de 5 em 5 segundos… pq “matar” o serviço pode deixar o sistema em estado inválido o que nao é bom…

No seu método de serviço faz mais ou menos assim:

public void calculaX(int a, int b, Progress p){
   //faz calculos
   if(p.isCanceled()){ //geralmente processos longos tem for.. coloque um código desses dentro do for...
          return;
   }
}

Essa é uma opção. Mas pensei bem e preferi disparar uma InterruptedException, sem estender a classe Thread na camada de serviço.
Acho que essa abordagem me ajuda a parar toda a pilha de execução (sem ter que ficar validando o retorno dos métodos na camada de interface gráfica (com return)) ou parar a thread da interface gráfica (colocando em estado incosistente).
Vou lançar uma exceção de interrupção para a interface gráfica, se identificar pelo objeto compartilhado ou observer, que o usuário pediu a interrupção.

//Neste código só vou ilustrar o procedimento de interrupção - omitindo código do Observer e/ou thread de atualização

public class InterfaceGrafica extends javax.swing.JFrame  {

   public void botaoExecucaoProcesso (ActionEvent evt) {

      Thread operacao = new Thread() {

         public void run() {

            try {

               VendasService servico = new VendasServiceImpl();
               servico.consolidarFaturamento(); 

            } catch (InterruptedException e) {
   
               //Tratamento para interrupção do processo:
               //Informar usuário que o processo foi interrompido por ele.
               //...
   
            } catch (Exception e) {
               //Tratamento para as outras exceções
            }

         } //fim do método run

      } //fim da declaração da thread
      operacao.start();

   } //fim do método

} //fim da classe

interface VendasService {

   public void consolidarFaturamento() throws InterruptedException, Exception; 

} //fim da interface

class VendasServiceImpl implements VendasService {


   public void consolidarFaturamento()
      throws InterruptedException, Exception
   {   
      //faz processamento
      if(p.isCanceled()){ //geralmente processos longos tem for.. coloque um código desses dentro do for...   
            throw new InterruptedException ();   
      }   
   }  


}  //fim da classe

[size=18]Muito obrigado pelos dicas! :D[/size]

rogelgarcia

Concordo… acho que a exceção vai ficar melhor que o return mesmo…

P

Havia entendido um ambiente Swing chamando serviços remotos. De fato, em um cenário auto-contido, estas opções não fazem muito sentido. O uso do ThreadPoolExecutor para submeter os jobs ainda faz sentido.

diego_qmota

Para o envio das atualizações da classe Service para a atualização de status na interface gráfica, estou implantando o modelo anexo, sujeito a alterações.
Espero que dê certo…

diego_qmota

psevestre:
diego_qmota:

Para alguns processos poderia usar uma das opções que você passou, psevestre. Mas o aplicativo é Desktop e a execução de uma transação (ou operação) completa são feitas em uma estação.
As opções que você apresentou são mais válidas para aplicativos web e sistemas distribuídos. Mas obrigado pelas dicas! :slight_smile:

Havia entendido um ambiente Swing chamando serviços remotos. De fato, em um cenário auto-contido, estas opções não fazem muito sentido. O uso do ThreadPoolExecutor para submeter os jobs ainda faz sentido.

Sim, é claro. Eu implantei um pool de threads que implementa ExecutorService há duas semanas atrás, para execução de processos muito longos que precisei dividir o processamento entre várias threads assíncronas:

final int TOTAL_TAREFAS_SIMULTANEAS = 6;

//Instancia objeto que será responsável por controlar o pool de tarefas
ExecutorService threadPool = Executors.newFixedThreadPool(TOTAL_TAREFAS_SIMULTANEAS);

Estou utilizando esse recurso onde um processo rodando sozinho leva muito tempo.
Os processos que rodam dentro de um tempo satisfatório (a maioria), estou rodando em uma thread só. No caso do processo que citei, não é necessário entrar nesse âmbito (é longo mas não a ponto de ser um problema de desempenho - leva de 2 a 4 min. e para o objetivo do processo e o usuário que utiliza, é aceitável). Mesmo assim, para um processo nesse intervalo, têm que haver notificação de status na interface gráfica, já é um tempo que exige exibição de status…
Para processos que levam de 10 min. para cima, eu estou usando Pools.

diego_qmota

A solução do Observer que apresentei também deu certo.

Segue exemplo em dois níveis: A classe de serviços notificando a interface gráfica do progresso da operação.
Quando necessário, posso também fazer a inclusão da DAO (que em métodos muito longos, notificaria o progresso do processo a classe de Serviços que, por sua vez, notificaria a classe de interface gráfica).

import java.util.Observable;
import java.util.Observer;

//Esta classe é uma que pode ser observada por outra
//Omiti o código que a torna observadora também por questões de legibilidade, mas ela pode desempenhar as duas tarefas: observar e ser observada.
public class ServiceImpl extends Observable {                    

//Construtor registra observador
public ServiceImpl(
            Observer observador)
{
        this.addObserver(observador);
}

public void operacaoBlacklist () {

            //Início do método...

            //Defini uma classe StatusService que representa o status do processo atual
            //Eu uso ela para passar informações à interface, como os passos executados, se o processo foi interrompido, texto de status, log de alterações...
            setStatusProcesso( new StatusService () );
            getStatusProcesso().incrementarAndamento();
            getStatusProcesso().setStatus("Recuperando datas que restam para analisar");
            getStatusProcesso().appendToLog("Recuperando datas que faltam analisar se há elegibilidade para entrar na BLACKLIST");

            //aqui eu notifico o objeto observador (no caso, a interface gráfica) passando o objeto que contém dados de status do processo.
            notifyObservers(getStatusProcesso());
            setChanged();

            daoBlackList = new ClienteBlackListDAO(
                    ConexaoFactory.getNovaConexao(dadosAcesso, SERVER.ACCESS_TESTES),
                    getDadosAcessoOracle(),
                    this);

            List<Date> listaDatasAnalisar = daoBlackList.getDatasParaAnalisarEntradaBLACKLIST();

            // continua executando o processo
}

} //fim da classe

//A interface gráfica implementa a interface Observer
public class JPanelProcessos_Blacklist
        extends JPanelProcessos
        implements Observer {

  public void botaoExecutarPressionado() {

   // código ... 

      ServiceImpl servicoBlackList = new ServiceImpl(this);  //passo a instancia do painél para o serviço identificá-lo como classe observadora

   // código ... 

 }    


    //Este método é da implementaçãod a interface Observer - responsável pelas respostas a alterações na classe observada
    @Override
    public void update(Observable o, Object arg) {

        StringBuilder log = new StringBuilder();
        try {

            Service servicos = (ServiceImpl) o;
            ServiceStatus status = (ServiceStatus) arg;

            //Recuperando os dados do status do processo para atualizar a interface gráfica
            log.append(status.getLogProcesso().toString());
            jLabelProcessos_Blacklist_Status.setText(status.getStatus());
            jProgressBarProcessos_Blacklist_Progresso.setValue(status.getAndamento());

        } catch (Exception e) {
            log.append("<p>Falha ao atualizar interface gráfica</p>");
            log.append("<p>Pilha de rastreamento de exceção</p>");
            log.append(RelatorioErros.getStackTraceAsString(e));
            jLabelProcessos_Blacklist_Status.setText("Erro ao atualizar interface");
        }

        jTextPaneProcessos_Blacklist_Log.setText(log.toString());

    }
}
Criado 20 de julho de 2010
Ultima resposta 23 de jul. de 2010
Respostas 11
Participantes 3