Interface Visual do AdvPL – Parte 03

Introdução

Continuando a sequência de Interface Visual do AdvPL, vamos entender um pouco mais sobre como funcionam alguns componentes básicos para permitir entrada de dados do usuário, e na sequência como persistir e consultar estes dados em uma tabela.

Caixa de Diálogo ou Janela MODAL

Quando criamos em AdvPL uma caixa de diálogo, o acionamento dos controles de interface estão restritos à janela ativa. E, não é possível ativar mais de uma janela ao mesmo tempo. A Interface MDI do AdvPL não se encaixa neste conceito, pois cada ABA da MDI possui um processo separado, veremos esta interface em mais detalhes posteriormente. Via de regra, somente temos uma Janela ou Diálogo ativo ao mesmo tempo.

A criação da janela de diálogo é acompanhada da criação dos componentes de interface que permitirão ao usuário interagir com a interface, e por fim ativamos o diálogo (comando ACTIVATE DIALOG, por exemplo), e ao tornarmos ele ativo, o programa aguarda a interação do usuário com os componentes de interface, disparando ações pré-definidas em Blocos de Código (Code-Blocks).

Criando uma janela para entrada de dados

Imagine que você quer perguntar ao usuário, qual é o seu nome. Existem várias formas de se fazer isso. Uma das formas mais simples, e inclusive reutilizável, é criar uma janela de diálogo, colocando um objeto do tipo GET, associado a uma variável de memória, que será preenchida com o texto que o usuário digitou, seguido de pelo menos dois botões: Um para confirmar a entrada, e outro para cancelar. Vamos ao exemplo:

#include 'protheus.ch'
User Function TstNome()
Local cNome 
cNome := GetNome("Insira seu nome",50,"!@")
If empty(cNome)
   MsgStop("Nenhum nome foi digitado","Nome Digitado")
Else
   MsgInfo("["+cNome+"]","Nome Digitado")
Endif
Return

/*
Funcao para perguntar um nome
Parâmetros	cTitulo = Titulo da Pergunta 
		nTam = Tamanho máximo da string de retorno \
		cPicture = Máscara de entrada de dados para o objeto GET 
Retorno		String vazia :"Nenhum nome digitado ou o duálogo 
		foi encerrado ou cancelado
*/

STATIC Function GetNome(cTitulo,nTam,cPicture)
Local oDlg
Local cGetVar := space(nTam)
Local oGet
Local oBtn1,oBtn2
Local lOk := .F. 

DEFINE DIALOG oDlg TITLE (cTitulo) ;
    FROM 0,0 TO 100,500 ;      
    COLOR CLR_BLACK, CLR_HBLUE PIXEL

@ 05,05 GET oGet VAR cGetVar  PICTURE (cPicture) 
        SIZE (nTam*4),12 OF oDlg PIXEL 

@ 25,05  BUTTON oBtn1 PROMPT "Confirmar" SIZE 40,15 ; 
         ACTION (lOk := .T. , oDlg:End()) OF oDlg PIXEL 

@ 25,50  BUTTON oBtn2 PROMPT "Voltar" SIZE 40,15 ; 
         ACTION (oDlg:End()) OF oDlg PIXEL 

ACTIVATE DIALOG oDlg CENTER

If !lOk
   cGetVar := space(nTam)
Endif

Return cGetVar

Detalhes do Exemplo

  1. Declaramos todas as variáveis a serem usadas no exemplo como “Local”
  2. Antes de ativar a Dialog criada, adicionamos nela todos os componentes visuais.
  3. Criamos o objeto do tipo “GET” informando a variável local “cGetVar“, que vai conter o valor digitado pelo usuário na interface.
  4. O programa vai permanecer em modo de interação com a interface enquanto o ACTIVATE DIALOG estiver em execução e a janela estiver aberta. Ao fechar a janela, a execução do programa continua.
  5. OS botões criados com as ações para confirmar ou para voltar, sempre chamam a finalização ou fechamento da janela de diálogo (oDlg:End), porém o botão de confirmação seta o valor da variável bOk para .T. (Verdadeiro) antes de fechar a janela.
  6. Caso o usuário não finalize a janela através do botão “Confirmar”, isto é, o usuário clicou no botão “Voltar“, ou fechou a janela, a variável bOk será .F. — o valor originalmente declarado.
  7. Após o fechamento da janela, verificamos qual o valor de bOk, e se o mesmo estiver falso, a variável de memória é limpa antes de ser retornada

Executando o programa

Após compilar o fonte, podemos executar o exemplo acima direto pelo SmartClient, chamando a função U_TstNome. Deverá ser apresentada uma tela parecida com a tela abaixo:

TstName

Após preencher o campo e clicar no botão “Confirmar”, será mostrada a mensagem informativa com o conteúdo digitado:

SeuNome

No exemplo proposto, usamos o nome obtido na interface após o fechamento da janela ou diálogo. Porém, seria possível usar o nome digitado com a interface aberta ? Sim, é possível. Vamos acrescentar mais um botão no exemplo acima, imediatamente antes da instrução de ativação do diálogo:

@ 25,95 BUTTON oBtn3 PROMPT "Mostrar" SIZE 40,15 ; 
   ACTION (MsgInfo("["+cGetVar+"]","Mostrando o Nome")) OF oDlg PIXEL 

ACTIVATE DIALOG oDlg CENTER

Agora, vamos ver o que acontece quando apertamos o botão quando digitamos algum conteúdo no campo e pressionamos o botão “Mostrar” ?

SeuNome2

COMO ASSIM ?

Até pouco tempo atrás, eu ainda não tinha entendido COMO isso poderia ser possível… Afinal, quando o botão “Mostrar” foi criado, a variável a ser mostrada (cGetVar) , além de ser uma variável de escopo LOCAL, estava com o seu conteúdo preenchido com espaços em branco! Como é que a ação do botão, que eu apertei depois de ter digitado no GET o texto “seu nome”, fez o conteúdo atual da variável parar na tela, se o botão foi criado ANTES de eu digitar esse conteúdo?

No próprio BLOG têm a resposta — no post feito sobre Codeblocks — vide links de referência no final do post — leia a parte do Exemplo 05: O uso de uma variável local dentro de um Codeblock leva no bloco de código uma referência dessa variável. Logo, mesmo que o botão tenha sido criado no momento que a variável estava preenchida apenas com espaços em branco, no momento que tiramos o foco do componente GET, usando ENTER, TAB ou mesmo clicando em um dos botões, o valor da variável local cVarGet é atualizado, e ao clicarmos no botão “Mostrar“,  a chamada da função MsgInfo() recebe o conteúdo atualizado da variável cVarGet.

COMO ASSIM , QUE CODEBLOCK ?

Isso mesmo, que Codeblock ? Ao criarmos o botão “Mostrar“, a ação a ser executada foi passada praticamente como uma string literal, inclusive ela nem precisaria estar cercada entre parênteses (força do hábito). Exatamente neste ponto existe uma declaração implícita de Codeblock, relacionada ao comando @ … BUTTON. Quando usamos diretamente a classe visual tButton do AdvPL, deve ser informado um Codeblock como ação do Botão. O que muda quando usamos diretamente a instrução @ … BUTTON, é que a expressão digitada no ACTION do botão é automaticamente declarada como um Codeblock. 

Por exemplo, para informar explicitamente um ACTION usando um CodeBlock, basta colocar  uma abertura de chave e dois pipes “{||”  e um fechamento de chave “}” englobando a expressão informada no ACTION. Por exemplo:

ACTION {|| MsgInfo(cGetVar,"Mostrando o Nome") } OF ...

Agora, quando nós não fazemos isso, e estamos usando o comando @ … BUTTON , a expressão de ACTION informada, caso não seja identificada como CodeBlock, é internamente declarada como Codeblock.

Classes de Interface encapsuladas em comandos

A documentação dos componentes e classes visuais do AdvPL documenta diretamente a classe do componente, você usa a classe diretamente no código fonte. Porém, devido ao grande número de parâmetros da classe, fica mais difícil de programar, além de dificultar a legibilidade do código. Uma vírgula a mais ou a menos, e você pode achar que está informando um CodeBlock de pré-validação do componente, quando na verdade uma vírgula a mais passou o argumento para o próximo parâmetro, que também era um Codeblock, mas este era executado na saída do componente, e não na entrada.

Os comandos ligados a interface visual do AdvPL já eram usados desde antes do Protheus, a transformação de um comando para a chamada direta da classe é feita por diretivas #command / #xcommand declaradas nos INCLUDES do Protheus. Vários comandos de interface visual estão documentados na TDN, veja nos links de referência no final do post. Existem vários comandos de interface documentados, porém a documentação os referencia como “Funções”.

Conclusão

Com este post, abrimos caminho para a utilização de componentes de interface e entrada de valores fornecidos pelo usuário. Era exatamente isso que faltava para darmos o próximo passo, um exemplo de CRUD — ou cadastro básico — em AdvPL, utilizando as funções básicas da linguagem, em breve no “Tudo em AdvPL” 😉

Desejo novamente a todos TERABYTES de sucesso !!!! 

Referências

 

 

Dicas valiosas de programação – Parte 03

Introdução

Neste post, vamos a uma dica muito importante e específica do AdvPL: Como proteger a chamada de novas funções implementadas na linguagem AdvPL — e evitar o erro “function <xxx> has compilation problems. Rebuild RPO”

Funções do AdvPL

Ao escrevermos uma aplicação em AdvPL, os desenvolvedores podem usar funções básicas da linguagem AdvPL, que por serem nativas da linguagem, estão compiladas e publicadas dentro do Application Server (executável/dlls), e as funções de Framework e Produto, compiladas dentro do RPO (Repositório de funções e classes AdvPL).

Compilation Problems …

Quando é criada uma nova função básica da linguagem AdvPL, ela estará disponível para uso apenas quando você atualizar o seu Application Server para uma build igual ou superior a qual a função foi disponibilizada. O problema é que, uma vez que você implemente uma chamada desta nova função dentro de um código AdvPL, para que esta chamada funcione, você precisa compilar o seu fonte usando o binário mais novo — que já têm a função — e executar o seu fonte com ele.

Caso você compile seu fonte com um binário mais antigo — que não tem a função — e tente executar o fonte em um binário mais novo, ou faça o contrário — compile usando um binário novo e tente executar em um binário antigo, ocorre o erro “function <xxx> has compilation problems. Rebuild RPO”. E este erro não ocorre durante a execução do fonte, mas ocorre durante a carga da função na memória para ser executada.

Este comportamento é uma amarração de segurança, para evitar que inadvertidamente uma função AdvPL compilada no RPO possa conflitar — ou mesmo tentar sobrescrever — uma função básica da linguagem AdvPL.

Onde isso pode ser um problema ?

Imagine que você dá manutenção em um código já existente e que funciona, e em uma build mais nova do Application Server, uma função nova foi publicada, que pode tornar o seu processo mais rápido.  A mão “coça” pra implementar a chamada da função, porém você não sabe se esse fonte será compilado com um binário mais velho ou novo, nem como garantir que o seu cliente vá atualizar a Build do Application Server para rodar o código novo. Neste caso, você precisa que seu código continue funcionando em uma build antiga ou nova, e independentemente se ele foi compilado em uma build nova ou não.

Como fazer a implementação protegida ?

São apenas alguns passos simples, mas que precisam ser feitos desta forma.

  • Verifique se a nova função existe em tempo de execução, chamando a função FindFunction(), passando como parâmetro entre aspas o nome da função.  Caso a função exista na build em uso, ela retorna .T.
  • Coloque o nome da função nova da linguagem AdvPL que você quer chamar, dentro de uma variável local caractere — por exemplo cFuncName
  • Após verificar se a função existe, faça a chamada para esta função usando o operador de macro-execução, com a seguinte sintaxe:

[<retorno> := ] &cFuncName.([parm1][,parm2][,…])

Por exemplo, vamos imaginar que você quer usar uma nova função do AdvPL, chamada FindClass() — que serve para dizer se uma determinada classe existe no ambiente onde a função está sendo executada.

user function tstfindcls()
Local cFnName := 'findclass'
Local lExist := .F.
If FindFunction(cFnName)
   lExist := &cFnName.("TGET")
   If lExist
      MsgInfo("A Classe TGET existe")
   Else
      MsgStop("A classe TGET nao existe")
   Endif
Else
   MsgStop("Função "+cFnName+" não encontrada", ;
           "Atualize o Application Server")
Endif
Return

Dessa forma, mesmo que você compile esse fonte AdvPL em uma build antiga ou nova, ele vai rodar corretamente em uma build nova, e caso seja executado em uma build antiga — que ainda não tenha a função FindClass — o fonte não vai apresentar erro de carga, mas vai identificar que a função não existe e mostrar a mensagem desejada.

Conclusão

Espero que esta dica, mesmo que  “curtinha”, seja de grande valia para todos os desenvolvedores AdvPL. Desejo novamente a todos TERABYTES de sucesso !!! 😀

Referências

 

 

Dicas valiosas de programação – Parte 02

Introdução

Continuando na linha de boas práticas e afins, vamos abordar nesse post mais uma dica de ouro, válida para qualquer aplicação que pretende ser escalável, porém com foco no AdvPL

Procure fazer transações curtas

Parece simples, e se olharmos bem, não é assim tão complicado. Primeiro, vamos conceituar uma transação. Na linguagem AdvPL, temos os comandos BEGIN TRANSACTION e END TRANSACTION, que por baixo utilizam recursos do Framework do ERP Microsiga, junto com recursos da linguagem AdvPL, para identificarmos e protegermos uma parte do código responsável por realizar inserção ou atualização de dados em uma tabela.

Quando utilizamos um Banco de Dados relacional no AdvPL, através do DBAccess, proteger a parte do fonte AdvPL que faz alterações no banco de dados é importante, pois evita que, caso ocorra um erro (de qualquer origem)  durante a execução deste trecho, e ele não chegue ao final da transação, nenhuma das operações realizadas com os dados desde o BEGIN TRANSACTION são realmente efetivadas no Banco de Dados, mas sim descartadas. Isso evita por exemplo dados órfãos na base de dados, ou registros incompletos ou inconsistentes.

Por que transações curtas ?

Cada alteração de um registro de qualquer tabela no AdvPL, exige que seja feito um LOCK (ou bloqueio) explícito no registro, para que o mesmo possa ser alterado, e este bloqueio, uma vez adquirido, somente pode ser solto no final da transação. Quanto maior (mais longa) for a duração da transação, por mais tempo este registro permanecerá bloqueado para os demais processos do sistema.

Erros comuns que devem ser evitados

  • “Não abrirás interface com o usuário no meio de uma transação”

Antes de abrir uma transação, a aplicação deve realizar todas as validações possíveis nos conteúdos que estão sendo manipulados. Se qualquer um deles não passar na validação, o programa nem abre a transação. O objetivo da transação é pegar os dados em memória e transferi-los para as tabelas pertinentes, sem interrupções. Ao abrir uma tela ao usuário no meio de uma transação, á transação somente continua depois que o usuário interagir com a interface. Se o operador do sistema submeteu uma operação, virou as costas e foi tomar café, os registros bloqueados pelo processo dele assim permanecerão até que ele volte. Isso pode prejudicar quaisquer outros processos que precisem também atualizar estes mesmos registros.

  •  “Evitarás processamentos desnecessários dentro da transação”

Eu adoro o exemplo do envio de e-mail. Imagine que, durante a gravação de um pedido, exista a necessidade de emissão de um e-mail para inciar um WorkFlow ou um aviso ao responsável pelo setor de compras que um item vendido atingiu um estoque mínimo. Como já visto anteriormente, a solução mais elegante é delegar o envio desse e-mail para um outro processo, preferencialmente notificado de forma assíncrona. Se, em último caso, você precisa que esse email seja disparado na hora, e isso impacta seu processo, não dispare esse email com a transação aberta, lembre-se do tempo que os registros da sua transação vão ficar bloqueados … Grave em um dos campos de STATUS que o envio do e-mail está pendente, e finalize a transação. Então, tente enviar o e-mail, e ao obter sucesso, bloqueie novamente o registro e atualize apenas o campo de STATUS.

  • “Evitarás DEADLOCK”

Ao realizar atualizações concorrentes (mesmo registro tentando ser atualizado por mais de um processo), procure estabelecer uma ordem de obtenção dos bloqueios dos registros, para não fazer a sua aplicação entrar em deadlock — duas transações, que vamos chamar de A e B, já tem cada uma um registro bloqueado de uma determinada tabela. A transação A têm o lock do registro 1, e a transação B têm o lock do registro 2, porém a transação A precisa do lock do registro 2, e a transação B precisa do lock do registro 1, e enquanto nenhuma delas desistir, uma fica esperando a outra e as duas transações “empacam” (efeito conhecido por DEADLOCK).

Se as duas transações ordenassem os registros por uma chave única em ordem crescente, e tentasse obter os locks nesta ordem , o primeiro processo a falhar ainda não teria conseguido pegar o bloqueio de nenhum registro de valor superior ao que o outro processo já tenha obtido, ele apenas esperaria um pouco e tentaria pegar o lock novamente, o que será possível assim que a transação que já tem o lock seja finalizada.

Conclusão

O arroz com feijão é apenas isso. Mas apenas isso já tira um monte de dor de cabeça de DBAs e Administradores de Sistemas, por que mesmo com todo o ferramental disponível, rastrear quem está segurando o lock de quem, em ambientes que passam de centenas de conexões, tome abrir monitor do DBAccess, do Protheus, do SGDB, e caçar a thread que está esperando uma liberação de registro, e a outra que está segurando o lock, não é uma tarefa fácil …

Desejo a todos TERABYTES de SUCESSO 😀

Referências

Entendendo e minimizando deadlocks

DEADLOCK. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2018. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Deadlock&oldid=51816852>. Acesso em: 15 abr. 2018.

 

 

 

 

Orientação a Objetos em AdvPL – Parte 01

Introdução

Para quem ainda não sabe o que é ou porquê a Orientação a Objetos, também conhecida por OOP (Object Oriented Programming), nesse post vamos dar uma passada mais a fundo neste tema, cobrindo algumas lacunas dos posts anteriores, para depois entrar nas recentes melhorias e implementações a serem disponibilizadas na próxima Build Oficial do TOTVS Application Server, chamada de “Lobo Guará”.

Programação Estruturada

A programação tradicional em AdvPL é conhecida por ser estruturada. Na prática, o sistema inteiro é criado a partir de funções ou sub-rotinas, usando estruturas de decisão (IF/CASE) e repetição (FOR/WHILE).

Normalmente as funções criadas declaram e usam variáveis de escopo local — vistas apenas dentro do corpo da função — e podem usar escopos mais abrangentes para compartilhar valores entre funções do mesmo fonte (STATIC), funções na pilha de chamadas (PRIVATE) e ainda entre qualquer função do sistema (PUBLIC), sem a necessidade de passagem de parâmetros.

A restrição de acesso à variáveis e funções normalmente é feita pelo escopo. As funções declaradas em um fone AdvPL por default são públicas — qualquer fonte pode chamar qualquer função — e podemos declarar a função como STATIC FUNCTION, restringindo sua visibilidade apenas para as demais funções presentes no mesmo código fonte onde a função foi declarada.

Programação Orientada a Objetos

Também presente no AdvPL, a orientação a objetos consiste em criar um agrupamento de dados e funções, chamada de “CLASSE”, onde cada variável da classe é chamada de “propriedade”, e cada função da classe é chamada de “método”. Basicamente, os métodos são funções aplicáveis apenas para a classe na qual foram declarados, eles podem ter parâmetros e retornos, e eles normalmente têm acesso às propriedades da classe. Em AdvPL, dentro de um método, você tem acesso às propriedades daquela instância da classe usando a variável especial “self”.

Como a classe em si é apenas uma definição, você precisa criar um contexto daquela classe para utilizá-la. Para isso, chamamos um método do tipo “construtor”, que pode ou não receber argumentos, e seu retorno é chamado de “instância” da classe, e essa instância é do tipo AdvPL “O” Objeto.

A classe por si só já representa um “container” de informação e manutenção de informação, mas ela permite ainda um recurso de reaproveitamento e organização de funcionalidades chamada HERANÇA. Isto permite que você crie uma classe que tenha acesso aos métodos e propriedades da classe pai, e permite que você implemente propriedades e métodos que atendam a sua necessidade, usando ou substituindo as implementações da classe pai, conforme a necessidade da sua especializacao.

Escopo de propriedades, métodos e heranças

Por hora, desde as primeiras versões do Protheus Server, a orientação a objetos do AdvPL não tinha restrição de escopo, isto é, tudo é público — propriedades e métodos da classe atual, e em caso de herança, também temos acesso a propriedades e métodos da classe pai informada na herança. Logo, ao criar uma instância de uma classe, qualquer propriedade ou método desta classe pode ser visto e acessado a partir da variável que armazena a instância da classe.

Classes em AdvPL na TDN

Existe uma árvore de documentos na TDN dedicada às classes AdvPL nativas do TOTVS Application Server, incluindo classes visuais e não-visuais, disponível no link Advpl – Classes, e na seção “Como Fazer – Classes AdvPL”, a documentação de como fazer classes em AdvPL sem e com herança.

Classes em AdvPL no Blog

Complementando a documentação da TDN, no próprio BLOG já tem quatro posts sobre Classes em AdvPL, com detalhes e exemplos, vide links abaixo:

https://siga0984.wordpress.com/2014/12/01/classes-em-advpl-parte-01/
https://siga0984.wordpress.com/2014/12/02/classes-em-advpl-parte-02/
https://siga0984.wordpress.com/2014/12/03/classes-em-advpl-parte-03/
https://siga0984.wordpress.com/2014/12/06/classes-em-advpl-parte-04/

TL++

Batizada de TL++ (Totvs Language Plus Plus), a criação de classes em AdvPL passa a ter os recursos de uma orientação a objetos tradicional, como modificadores de acesso, escopo de propriedades e métodos, até mesmo declaração de tipagem. Para não conflitar com as declarações já existentes, foi adotada uma nova extensão de arquivo fonte (.tlpp) para utilizar estes recursos, que estarão disponíveis apenas na liberação oficial na nova Build do TOTVS Application Server, chamada de “Lobo Guará”. Para ver esta documentação na TDN, acesse o link A Linguagem TL++

F.A.Q.

Qual a diferença entre Classe, Instância e Objeto ?

Classe = Definição da estrutura, dividida em propriedades e métodos.
Instância = Corresponde a uma área de memória alocada para uso da classe
Objeto = Tipo da variável que contêm uma instância de uma classe

Eu posso criar uma classe sem métodos ?

Sim, inclusive isso é conhecido por “estrutura”, isto é, apenas um agrupamento de dados em propriedades. Porém, no AdvPL, você precisa criar pelo menos um método New(), para servir de construtor da estrutura. Existe uma classe diferenciada, chamada WSSTRUCT, usada em WebServices. Esta classe não precisa de construtor, porém a criação de uma nova instância de uma estrutura de WebServices é criada pela função WSCLASSNEW(), que faz o papel do construtor, porém sem receber parâmetros — e neste caso todas as propriedades da estrutura são inicializados com “NIL”.

Eu posso passar uma instancia de uma classe como parâmetro para um JOB ou RPC ?

Não, não pode. Uma instância da classe está internamente amarrada ao processo que criou a instância. Os tipos AdvPL “O” Objeto e “B” CodeBlock têm esta característica restritiva. O que você pode fazer é, por exemplo, criar uma nova instancia da classe, passar as propriedades da instância encapsulado em uma string ou array, e reatribuir as propriedades na nova instância. Neste caso, você terá uma instância “clone” da instância do processo original, e não uma “referência” dela.

Existe certo ou errado entre criar funções ou classes ?

Cada paradigma foi criado para atender a uma necessidade. É mais simples e intuitivo programar de forma estruturada, funções genéricas que não dependem diretamente de contexto ou não precisam ter o nível de controle oferecido pela Orientação a Objetos não precisam necessariamente serem feitos usando OOP.

Quando optamos por usar Orientação a Objetos, precisamos ter em mente as responsavilidades de cada parte do código, tempo de vida da instância, necessidade ou não da existência de propriedades, necessidade ou não de uso de herança.

Normalmente os dois paradigmas usam-se mutuamente. Isto é, voce cria uma função com estruturas e controles, que cria por sua vez instâncias de classes, e as manipula ou chama sua execução para obter um resultado, onde os próprios métodos são funções estruturadas. Existem várias formas de se fazer alguma coisa, a melhor é aquela que atende a sua necessidade hoje, e pode ser expandida facilmente para atender uma necessidade maior amanhã.

Para não criarmos monstros, seja programando estruturalmente, ou orientado a objeto, é importante deixar claro o papel de cada função, classe ou método. A coisa começa a ficar confusa e sem sentido quando uma classe resolve fazer mais coisas do que ela foi originalmente projetada, ou você começa a engordar a classe com métodos especialistas desnecessários.

Conclusão

Pra assimilar o conceito da utiliação de Orientação a Objetos, o melhor a fazer é colocar a mão na massa e implementar uma funcionalidade que assim a utilize, e procurar basear-se em bons exemplos de uso.

Desejo a todos TERABYTES de sucesso !!!

Referências

TOTVS – Central de Atendimento – AppServer Lobo Guará

PROGRAMAÇÃO ESTRUTURADA. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2018. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Programa%C3%A7%C3%A3o_estruturada&oldid=52590881>. Acesso em: 6 jul. 2018.

ORIENTAÇÃO A OBJETOS. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2018. Disponível em: <https://pt.wikipedia.org/w/index.php?title=Orienta%C3%A7%C3%A3o_a_objetos&oldid=52638985>. Acesso em: 11 jul. 2018.

Executando Jobs em AdvPL

Introdução

Em todos os posts anteriores sobre escalabilidade, desempenho e afins, sempre aparece o “tal” do JOB. No AdvPL, genericamente damos o nome de “Job” para um processamento de uma função AdvPL iniciada em um ambiente sem interface com o usuário — ou seja, sem conexão com o SmartClient). Neste post, vamos ver em detalhes algumas formas que o AdvPL permite a execução de JOBs.

JOBS “ONSTART”

No arquivo de configurações do Servidor de Aplicação Protheus Server, conhecido por “appserver.ini”, podemos definir um ou mais JOBS, que por default são iniciados apenas uma vez, no momento que o serviço do Protheus é iniciado.

Basta criar uma nova seção nomeada no appserver.ini para cada JOB ou programa que deve ser executado em JOB — lembrando de usar um nome intuitivo para o job, e nao usar nenhuma palavra reservada de configuração — , depois criar uma seção chamada [ONSTART], e dentro dela colocar a lista de jobs a serem executados no início do serviço do Protheus Server dentro da configuração JOBS, separados por vírgula.

Exemplo 01

[ONSTART]
JOBS=JOBMSG1

[JOBMSG1]
main=conout
environment=envp12
nparms=1
parm1="Meu Primeiro Job"

No exemplo acima, configuramos um JOB para ser executado apenas uma vez, na subida do serviço do Protheus Server. O JOB foi configurado para chamar a função “Conout” do AdvPL, para emitir uma mensagem no log de console. Também foi configurado para passar uma string de parâmetro para a função conout(), contendo o texto “Meu Primeiro Job”.

A passagem de parâmetros para um JOB é opcional, as configurações obrigatórias são:

  • MAIN = Identifica o nome da função a ser chamada
  • ENVIRONMENT = Identifica o ambiente de execução desta função no Protheus.

Exemplo 02

[ONSTART]
JOBS=JOBMSG2

[JOBMSG2]
main=conout
environment=envp12
instances=3
nparms=2
parm1="Meu Primeiro Job Multiplo"
parm2="-------------------------"

No exemplo acima, apenas acrescentamos a configuração INSTANCES=3, para indicar para o Protheus Server que ele deve subir três processos (ao invés de apenas um) e executar em cada um deles a mesma função conout(), porém agora com dois parâmetros (a string “Meu Primeiro Job Múltiplo”, seguido de uma string de mesmo tamanho com uma sequencia de ‘-‘). O Resultado no log de console do AppServer deve ser algo parecido com:

Meu Primeiro Job Multiplo
-------------------------
Meu Primeiro Job Multiplo
-------------------------
Meu Primeiro Job Multiplo
-------------------------

Passagem de parâmetros ao JOB

Como vimos nos exemplos acima, podemos passar um ou mais parâmetros para os JOBS, identificando a quantidade de parâmetros a ser enviada usando a configuração NPARMS, e depois cada parâmetro como uma string, identificada como PARM1, PARM2, etc… Os parâmetros neste caso sempre serão passados para as funções a serem executadas como “C” Caractere. Caso os conteúdos dos parâmetros sejam informados entre aspas duplas, as aspas serão removidas automaticamente dos parâmetros para a passagem do valor ao AdvPL.

Exemplo 03

Vamos criar uma função simples para ser usada no lugar do conout() dos exemplos acima. Compile no seu repositõrio a função abaixo:

USER FUNCTION MYJOB1( cMsg1, cMsg2 ) 
conout("Thread ["+cValToChar(ThreadID())+"] executando ... ")
conout("Thread ["+cValToChar(ThreadID())+"] cMsg1 = "+cValToChar(cMsg1))
conout("Thread ["+cValToChar(ThreadID())+"] cMsg2 = "+cValToChar(cMsg2)
sleep(1000)
conout("Thread ["+cValToChar(ThreadID())+"] saindo ... ")
return

Agora, usando a configuração do Exemplo 02, troque na configuração main= conout por main=U_MYJOB1, compile o fonte acima, pare e suba o Servidor de Aplicação novamente, e vamos ver o resultado no LOG de console:

Thread [7048] executando ...
Thread [7048] cMsg1 = Meu Primeiro Job Multiplo
Thread [7048] cMsg2 = -------------------------
Thread [16432] executando ...
Thread [16432] cMsg1 = Meu Primeiro Job Multiplo
Thread [16432] cMsg2 = -------------------------
Thread [16508] executando ...
Thread [16508] cMsg1 = Meu Primeiro Job Multiplo
Thread [16508] cMsg2 = -------------------------
Thread [7048] saindo ...
Thread [16432] saindo ...
Thread [16508] saindo ...

Exemplo 04

Agora, vamos mexer um pouco na função MYJOB1, para ela “nao sair” …

USER FUNCTION MYJOB1( cMsg1, cMsg2 ) 
conout("Thread ["+cValToChar(ThreadID())+"] executando ... ")
conout("Thread ["+cValToChar(ThreadID())+"] cMsg1 = "+cValToChar(cMsg1))
conout("Thread ["+cValToChar(ThreadID())+"] cMsg2 = "+cValToChar(cMsg2)]
While !killapp()
  conout("Thread ["+cValToChar(ThreadID())+"] Hora Atual = "+time())
  sleep(1000)
Enddo
conout("Thread ["+cValToChar(ThreadID())+"] saindo ... ")
return

O resultado esperado no log de console será algo parecido com isso:

Thread [2928] executando ...
Thread [2928] cMsg1 = Meu Primeiro Job Multiplo
Thread [2928] cMsg2 = -------------------------
Thread [2928] Hora Atual = 01:00:21
Thread [13224] executando ...
Thread [13224] cMsg1 = Meu Primeiro Job Multiplo
Thread [13224] cMsg2 = -------------------------
Thread [13224] Hora Atual = 01:00:21
Thread [15056] executando ...
Thread [15056] cMsg1 = Meu Primeiro Job Multiplo
Thread [15056] cMsg2 = -------------------------
Thread [15056] Hora Atual = 01:00:21
Thread [2928] Hora Atual = 01:00:22
Thread [13224] Hora Atual = 01:00:22
Thread [15056] Hora Atual = 01:00:22
Thread [2928] Hora Atual = 01:00:23
Thread [15056] Hora Atual = 01:00:23
Thread [13224] Hora Atual = 01:00:23
Thread [2928] Hora Atual = 01:00:24
Thread [15056] Hora Atual = 01:00:24
Thread [13224] Hora Atual = 01:00:24

A função KillApp() somente retornará .T. caso o processo atual seja derrubado pelo Protheus Monitor, ou caso o serviço do Protheus Seja finalizado. No nosso exemplo, cada job fica em LOOP mostrando a hora atual no log de console, dorme por 1 segundo, e recomeça o loop.

Vale lembrar que um JOB é colocado no ar apenas na instância do Protheus Server na qual ele foi configurado no arquivo de inicialização. Colocar um JOB na seção [ONSTART] do Aplication Server configurado para ser, por exemplo, o servidor Master para Balanceamento de Carga, fará o JOB rodar apenas naquela instância do Protheus Server.

Evolução dos Exemplos

Conhecendo um pouco mais das funções de comunicação entre processos do AdvPL, como por exemplo as funções IPCWAITEX e IPCGO, com mais algumas linhas de código podemos criar um mecanismo de processamento em JOB assíncrono, onde cada job pode ficar esperando uma requisição de IPC usando um mesmo identificador nomeado, e receber notificações de processamento de um ou mais programas sendo executados neste mesmo Serviço do Protheus.

Exemplo 05

Vamos mexer mais um pouco no nosso JOB, para ele ser capaz de receber o nome de uma função de usuário para ser executada em modo assíncrono. Criamos uma função “Cliente”, que solicita o processamento de uma função especifica, para contar de 1 a 10. Colocamos três jobs no ar disponíveis para realizar as chamadas, e executamos a aplicação client, solicitando um novo envio de processamento a cada 1 segundo, por quatro vezes, e vamos ver o que acontece.

USER Function MyClient()
While MsgYesNo("Envia um processamento a um JOB ?")
  If IpcGo("MYJOB_IPC","U_MYTASK",1,10)
    MsgInfo("Processamento enviado.")
  Else
    MsgSTop("Nao foi possivel enviar a requisicao. Nao há jobs disponiveis.")
  Endif
Enddo
Return

USER FUNCTION MYJOB1( cMsg1, cMsg2 ) 
Local cFN,p1,p2

conout("Thread ["+cValToChar(ThreadID())+"] iniciando ... ")

While !killapp()
  cFN := NIL 
  p1 := NIL
  p2 := NIL 
  conout("Thread ["+cValToChar(ThreadID())+"] Aguardando um chamado ... ")
  If IpcWaitEx("MYJOB_IPC",5000,@cFN,@p1,@p2)
    conout("Thread ["+cValToChar(ThreadID())+"] Executando " + p1)
    &cFN.(p1,p2)
  Endif
Enddo

conout("Thread ["+cValToChar(ThreadID())+"] saindo ... ")
return


USER Function MYTASK(p1,p2)
Local nX
conout("Thread ["+cValToChar(ThreadID())+"] --- Inicio da tarefa ...")
For nX := p1 TO p2 
  conout("Thread ["+cValToChar(ThreadID())+"] --- Contando "+cValToChar(nX)+" ( "+cValToChar(p1)+" a "+cValToChar(p2)+" )")
  Sleep(1000)
Next
conout("Thread ["+cValToChar(ThreadID())+"] --- Final da tarefa ...")
Return

Log de execução

Thread [9664] iniciando ...
Thread [9664] Aguardando um chamado ...
Thread [7368] iniciando ...
Thread [7368] Aguardando um chamado ...
Thread [11852] iniciando ...
Thread [11852] Aguardando um chamado ...
Thread [9664] Aguardando um chamado ...
Thread [7368] Aguardando um chamado ...
Thread [11852] Aguardando um chamado ...
Thread [9664] Executando U_MYTASK
Thread [9664] --- Inicio da tarefa ...
Thread [9664] --- Contando 1 ( 1 a 10 )
Thread [9664] --- Contando 2 ( 1 a 10 )
Thread [9664] --- Contando 3 ( 1 a 10 )
Thread [7368] Executando U_MYTASK
Thread [7368] --- Inicio da tarefa ...
Thread [7368] --- Contando 1 ( 1 a 10 )
Thread [9664] --- Contando 4 ( 1 a 10 )
Thread [7368] --- Contando 2 ( 1 a 10 )
Thread [9664] --- Contando 5 ( 1 a 10 )
Thread [11852] Aguardando um chamado ...
Thread [7368] --- Contando 3 ( 1 a 10 )
Thread [9664] --- Contando 6 ( 1 a 10 )
Thread [11852] Executando U_MYTASK
Thread [11852] --- Inicio da tarefa ...
Thread [11852] --- Contando 1 ( 1 a 10 )
Thread [7368] --- Contando 4 ( 1 a 10 )
Thread [9664] --- Contando 7 ( 1 a 10 )
Thread [11852] --- Contando 2 ( 1 a 10 )
Thread [7368] --- Contando 5 ( 1 a 10 )
Thread [9664] --- Contando 8 ( 1 a 10 )
Thread [11852] --- Contando 3 ( 1 a 10 )
Thread [7368] --- Contando 6 ( 1 a 10 )
Thread [9664] --- Contando 9 ( 1 a 10 )
Thread [11852] --- Contando 4 ( 1 a 10 )
Thread [7368] --- Contando 7 ( 1 a 10 )
Thread [9664] --- Contando 10 ( 1 a 10 )
Thread [11852] --- Contando 5 ( 1 a 10 )
Thread [7368] --- Contando 8 ( 1 a 10 )
Thread [9664] --- Final da tarefa ...
Thread [9664] Aguardando um chamado ...
Thread [11852] --- Contando 6 ( 1 a 10 )
Thread [7368] --- Contando 9 ( 1 a 10 )
Thread [11852] --- Contando 7 ( 1 a 10 )
Thread [7368] --- Contando 10 ( 1 a 10 )
Thread [11852] --- Contando 8 ( 1 a 10 )
Thread [7368] --- Final da tarefa ...
Thread [7368] Aguardando um chamado ...
Thread [11852] --- Contando 9 ( 1 a 10 )
Thread [11852] --- Contando 10 ( 1 a 10 )
Thread [9664] Aguardando um chamado ...
Thread [11852] --- Final da tarefa ...
Thread [11852] Aguardando um chamado ...
Thread [7368] Aguardando um chamado ...
Thread [9664] Aguardando um chamado ...
Thread [11852] Aguardando um chamado ...
Thread [7368] Aguardando um chamado ...
Thread [9664] Aguardando um chamado ...
Thread [11852] Aguardando um chamado ...

O programa cliente enviou três requisições com sucesso, onde cada uma ocupou um dos processos em espera. Cada processo deve ficar ocupado por 10 segundos, logo se o programa cliente tentou enviar uma quarta requisição em menos de 10 segundos depois de ter enviado a primeira, ela não vaio encontrar nenhum JOB aguardando em IPCWAITEX, e a função IPCGO() retorna .F. pois não conseguiu enviar a chamada para frente.

O resultado é lindo, a função cliente apenas disparou um IPCGO() com o que precisa ser feito, e a função foi executada em um job pré-configurado, sem que o cliente precisasse esperar pelo retorno ou pelo fim do processamento. Isso para um teste ou uma prova de conceito, até funciona. Mas no mundo real, normalmente é necessário saber se aquele processamento foi concluído com sucesso, ou se aconteceu alguma coisa durante o processamento, e o que aconteceu.

Fatiando processos

Mas, por hora vamos focar em um ponto importante: — Você não precisa e nem deve preocupar-se com “quantas instancias” serão colocadas no ar para dividir ou dimensionar seu trabalho.

Sim, é isso mesmo. É natural você pensar imediatamente em registros por processos e número de processos, por exemplo: Se eu tenho 50 mil títulos para serem processados, e vou subir 10 jobs, então eu vou mandar 5 mil títulos para cada JOB … Porém isso traz algumas consequências desagradáveis.

A primeira delas é: Mesmo que a velocidade média do processamento de um título seja igual entre os processos, cada job vai ficar no ar ocupado por muito tempo processando um lote muito grande de informações. Qualquer variação no tempo de processamento pode fazer um job terminar a sua parte muito rápido, enquanto outros jobs podem demorar mais tempo para terminar a sua parte … e o seu processamento somente está COMPLETO quando todos os JOBS terminarem. Por exemplo, imagina que a média de processamento de um título seja entre 1,5 e 1 segundo. Isto significa que, um processamento de 5 mil títulos pode demorar de 41 a 82 minutos.

Para evitar isso, o ideal é definir um lote de títulos de um determinado tamanho, de modo que o processamento de um lote demore de 30 a 120 segundos. Depois, você deve subir uma quantidade de jobs no ambiente que não prejudiquem o desempenho deste servidor de aplicação, e esse numero varia de ambiente para ambiente. Mantendo cada JOB ocupado por um período mais curto de tempo, eles vão passar mais tempo rodando em paralelo.

Considerando o caso anterior, vamos simplificar o exemplo dividindo 10 mil registros em 2 jobs, cada um deles já pegou a sua “faixa” de registros para processar … embora os dois tenham iniciados juntos, um deles terminou a parte dele em 45 minutos, e o outro demorou mais 20 minutos, e o processo “pai” teve que esperar todos os processos filho terminarem para obter um resultado. Criando um lote de processamento de, por exemplo 60 títulos por vez, cada job ficará alocado de 30 segundos a um minuto para processar o seu lote, ficando disponível novamente para pegar o próximo lote. Neste caso, os dois jobs ficariam quase todo o tempo rodando em paralelo, ficando um deles no máximo 30 segundos “sozinho” terminando de processar o ultimo lote.

Controlando a execução

Voltando um pouco antes da parte de “Fatiar” um processo, é importante saber se todas as requisicoes enviadas para processamento foram realmente processadas com sucesso. Existem várias formas de se fazer isso, inclusive durante a execução do processo, ou no final dele.

Por exemplo, sabendo que cada processo dedicado deve pegar um lote de 30 títulos — fixos ou determinados por uma faixa de intervalo — o programa principal (pai) deste processamento pode criar uma tabela registrando todas as requisições que foram distribuídas, e cada programa que finaliza o processamento de uma requisição, atualiza o Flag dela na tabela de controle. Assim, no final do processo, quando todos os jobs tiverem terminado de processar e não houver nada mais pendente, o LOG de operação deve ser verificado para ver se está tudo em ordem

Inclusive, existem erros graves que não são interceptados por rotinas de tratamento e recuperação de erro, que geram apenas um log local. Quando pensamos em múltiplos jobs, temos estes pontos a considerar.

A função STARTJOB

Utilizando a função STARTJOB() do AdvPL, podemos subir uma instância para rodar uma função qualquer em tempo de execução. O novo processo será colocado no ar no mesmo serviço do Protheus em uso pelo processo “pai” que iniciou o JOB. Para subir múltiplas instâncias, basta fazer um FOR .. NEXT, e podemos passar nenhum, um ou vários parâmetros para a função a ser executada. Veja a documentação da função na TDN, nas referências no final do post.

Existe JOB remoto ?

Sim, existe. Quando usamos uma conexão RPC nativa do AdvPL entre serviços do Protheus Server, podemos iniciar um JOB no servidor alvo da conexão RPC. Logo, uma rotina em AdvPL, onde você possa mapear e fazer RPC para os serviços do Protheus que devam fazer parte de um grande processamento, seria capaz de subir jobs remotos via RPC, e através do RPC, fazer uma distribuição de tarefas para os jobs dinamicamente, usando por exemplo um algoritmo round robin — pega a lista de objetos RPC conectados, passa uma tarefa para um job do primeiro servidor, vai para o próximo servidor, se a tarefa anterior foi passada com sucesso pega uma nova, senão tenta passar para o servidor atual, acabou a lista de conexões, volta para a primeira, mantem dentro desse loop até acabarem as tarefas, coloca um SLEEP de um segundo caso ele tenha varrido todo o array de objetos RPC e não foi encontrado nenhum job livre, espera os jobs remotos terminarem as suas tarefas, e finaliza a rotina.

Existem outros tipos de JOB ?

Sim, também configurados no appserver.ini, existem JOBS que são configurados como Pools de Working Threads, com ou sem inicialização automatica pela seção ONSTART. Por exemplo, o JOB TYPE=WEBEX, utilizado para Working Threads de Portais, WEBEX (AdvPL ASP) e WebServices.

Scheduler do ERP

Vale lembrar que o ERP Microsiga possui uma funcionalidade chamada “Scheduler”, que permite a execução programa de tarefas agendadas, com muito mais detalhes e controles. O objetivos dos exemplos deste post são direcionados a mostrar formas mais simples e nativas da linguagem AdvPL e do TOTVS Application Server de executar processos em JOB.

Conclusão

Cada linguagem de programação oferece um nível de flexibilidade para criarmos uma solução, o segredo está em buscar o uso de alternativas simples, e usar as alternativas mais complexas onde elas são necessárias, buscando implementá-las também de forma simples. Eu acredito que esse é o desafio constante do desenvolvedor de software.

Desejo a todos(as) TERABYTES de SUCESSO 😀

Referências

ONSTART 
IPCWAITEX
IPCGO 
Round Robin

Dicas valiosas de programação – Parte 01

Introdução

Ao longo do tempo, cada analista de sistemas e programador adquire experiência e proeficiência em algoritmos e soluções de software para atender a necessidade de seus clientes. Normalmente cada linguagem têm os seus “pulos do gato”, muitos são conhecidos e são independentes da linguagem de programação. Neste post vamos abordar os mais conhecidos, e que sempre devem ser lembrados, com ênfase no “porquê” é melhor fazer assim do que assado.

Separe o Processamento da Interface

Basicamente, você escreve um pouco mais de código, visando criar no mínimo duas etapas de processamento em códigos distintos. O primeiro código, responsável pela camada de interface, deve criar para o usuário uma interativa de fornecer os dados e parâmetros para um processamento.

Por exemplo, uma inclusão de novo cliente, ou a emissão de um relatório. Você constrói uma interface onde o usuário possa preencher os campos de cadastro ou os parâmetros a serem considerados na emissão do relatório.

O segundo código é responsável pela realização efetiva do processamento. Porém, este código não deve ter acesso a interface. Ele deve receber os dados para serem processados como argumento da função, e o retorno de sua execução deve indicar se o processamento foi executado com sucesso, e em caso de falha, os detalhes pertinentes da falha — como por exemplo parametrização inválida, tentativa de incluir cliente já existente, etc…

Por que fazer dessa forma ?

  1. Uma rotina escrita para não depender de interface pode ser executada SEM interface, em processamentos do tipo “JOB”. Isso torna possível, por exemplo, a criação de JOBs dedicados para realizar várias inclusões ao mesmo tempo, ou por exemplo a execução do relatório através de um programa do tipo “Agendador de Tarefas” (ou Scheduler) sem a dependência de ser iniciado pelo usuário.
  2. A rotina responsável pelo processamento deve possuir as validações adequadas da parametrização envolvida na sua execução, assim você escreve uma única função de processamento, e pode fazer a chamada dela a partir de várias interfaces ( SmartClient, Telnet, WebService, HTML, REST, etc ), onde a regra de negócio é mantida em apenas um código.
  3. As interfaces de chamada da função de processamento não necessariamente precisam apresentar uma tela ao usuário. Por exemplo, a sua função de processamento recebe parâmetros em array, no formato chave=valor. Com algumas linhas de código é possível escrever uma classe de Web Services Server, responsável por publicar uma funcionalidade e tratar os parâmetros (SOAP ou REST) recebidos, converter em array para a chamada da função, e retornar o status da tarefa.
  4. Mesmo utilizando uma arquitetura Client-Server, como o caso do AdvPL, o contexto de execução de um processo de interface — SmartClient por exemplo — somente é mantido no ar enquanto a aplicação Client está sendo executada. Logo, se durante um processamento de código AdvPL no servidor, sendo executado em um contexto (ou Thread) iniciada a partir de um SmartClient, se o APPlication Server perder o contato com o SmartClient — normalmente por instabilidade na rede ou mesmo um problema na máquina onde o Smartclient está sendo executado ), o processo em execução no servidor é encerrado imediatamente — afinal sua existência é baseada na premissa da interface gráfica com o usuário.
  5. Justamente em processamentos mais longos, como por exemplo processos em lote — ou a emissão de um relatório que demora duas horas … Não vale a pena deixar a interface aberta e travada com uma tela de “aguarde”, e rodar o relatório neste processo. Imagine que falta 10 minutos pro relatório efetivamente ser emitido, e a energia “piscou” no terminal, e o terminal perdeu por um instante a conexão de rede .. lá se foi uma hora e 50 minutos de trabalho jogados fora, em uma infra-estrutura onde o servidor de aplicação normalmente está em um ambiente com rede redundante, alimentação de energia redundante, alta disponibilidade e afins .. o relatório teve que ser emitido novamente, e iniciar “do zero” por que a energia “piscou” na sala onde estava o terminal do sistema.

Então, vamos mudar tudo pra JOB ?

Não, não é assim que funciona. Chamar um processo separado do processo atual para realizar uma tarefa têm custo, exige mais controle(s), tratamento(s) de erro, normalmente exige um algoritmo de gerenciamento de processos, para não deixar o sistema subir mais processos do que ele “aguenta” processar. Um erro de lógica neste mecanismo ou a ausência dele pode levar um sistema a um estado de esgotamento de recursos.

O que colocar em JOB ?

Via de regra, normalmente é recomendável colocar em JOB os seguintes tipos de processamento:

  1. Emissões de relatórios pesados e/ou demorados. Aquele resumo financeiro do dia anterior, que só pode ser executado depois da meia note, que demora pelo menos meia hora para ser emitido, e que você precisa ver no dia seguinte as 8 horas da manhã … coloque em um agendador de tarefas (Scheduler)  para rodar as 5 ou 6 da manhã. Quando você entrar no sistema e abrir o Spool de Impressão,  seu relatório deve estar lá. Se não estiver, o Scheduler deve ter registrado alguma falha.
  2. Tarefas complementares de processamentos, onde não existe a necessidade imediata do resultado para a continuidade do processo. Por exemplo, um envio de e-mail informativo de uma determinada operação. Quando utilizadas em pequenas quantidades, subir um jobs adicional na execução do programa é suficiente. Agora, caso múltiplas rotinas de envio de email sejam chamadas ao mesmo tempo, sob pena de haver um esgotamento de recursos, é prudente criar uma fila de envio de emails — por exemplo uma tabela na aplicação contendo os emails pendentes de envio, e um ou mais JOBS dedicados ao envio de email. Assim, enquanto os processos vão inserindo os emails a serem enviados em uma tabela do banco de dados, um ou mais processos dedicados vão enviando esses emails, um a um, sem sobrecarregar o servidor atual com muitos jobs, e sem sobrecarregar o próprio provedor de email.
  3. Tarefas especializadas concorrentes, onde múltiplos processos concorrem pelo mesmo recurso, que só pode ser usado por um processo por vez. Normalmente estes casos são exemplificados por rotinas de atualização que exigem o bloqueio temporário de um registro. Neste caso, seria mais elegante criar um e apenas um JOB dedicado para fazer esta atualização, e este JOB pegar os dados que devem ser atualizados de uma fila. Assim, outros processos que precisem fazer esta tarefa não precisam esperar o processo dedicado terminar de rodar a requisição.

Conclusão

Escrever a rotina de processamento sem depender de interface, além de naturalmente permitir a execução dessa rotina dentro de um processo de interface, lhe abre a oportunidade de executá-la em um processo separado, mas não o obriga a fazer isso. Se uma rotina já existe, e foi escrita para depender de uma interface, você deve decidir se usar um JOB é a melhor alternativa para as suas necessidades, e avaliar o custo de fazer essa separação. Em linhas gerais, para não sofrer amanhã, comece a separar processamento da interface hoje, nas novas funcionalidades que você implementa, assim a utilização delas em Job, se assim conveniente ou necessário for, será menos traumático.

Desejo a todos(as)  TERABYTES de SUCESSO 😀