Manual de Técnicas
Construção de DBOs 2.0
Junho/2005
Versão 2.0
direito de efetuar alterações sem aviso prévio. A DATASUL S.A não assume nenhuma responsabilidade pelas conseqüências de quaisquer erros ou
inexatidões que possam aparecer neste documento.
DATASUL S.A.
i
Índice
CAPÍTULO 1 Conceitos ... 5
CAPÍTULO 2 Concepção do DBO ... 9
DBO de Entidade ... 11
DBOs X APIs ... 19
Qual a função do DBO e da API... 19
Como ficam as APIs existentes atualmente ... 20
Quando criar API ou DBO... 20
CAPÍTULO 3 Como construir o DBO ... 21
Acesso a Banco de Dados ... 21
Arquitetura do DBO ... 21
Definições ... 21
Objetos de trabalho ... 22
Métodos Básicos ... 22
Métodos de Negócio ... 22
Override de Métodos ... 23
Construção do DBO ... 25Criar programa e include de DBO a partir do template ... 25
Acertar os preprocessadores padrão ... 25
Definição da temp-table de comunicação ... 26
Definição da Query ... 27
Definição das Aberturas de Query e setConstraint ... 27
Criação do método goToKey ... 30
Criação do método linkTo<Description> ... 31
Criação do método getKey ... 32
Verificar ocorrência de chave duplicada ou outras validações
referentes somente a criação de registros ... 33
Criação de erros ... 34
Definição das Aberturas de Query e setConstraint com Join entre
Tabelas ... 36
Criação do método setSecurityConstraint ... 37
XML no DBO ... 38
Informar os pré-processadores para XML Producer ... 38
Informar os pré-processadores para XML Receiver ... 39
Utilizando Serviços no DBO ... 40
Serviço de Banco ... 40
Serviço de Autenticação ... 41
Serviço de Customização ... 41
Serviço de Segurança ... 43
Redução do Tamanho do DBO ... 43
Uso de sub-programas ... 44
Considerações Gerais ... 44
CAPÍTULO 4 Convertendo BO 1.1 para DBO 2.0 ... 46
Relação entre métodos ... 46
Criar o novo DBO... 48
Transferir lógicas dos métodos básicos do DBO 1.1 para o DBO
2.0 ... 48
CAPÍTULO 5 Convertendo SmartObjects para DBO 2.0 ... 57
Criar o novo DBO... 57
Transferir lógicas dos métodos dos SmartObjects para o DBO 2.0
... 57
CAPÍTULO 6 Serviços Padrão ... 67
Serviço de Banco ... 68
Serviço de Customização ... 68
Serviço de Erros ... 69
Serviço de Segurança ... 71
Serviço de Message Broker ... 73
sendMessage ... 73
getSendMode ... 74
iii
CAPÍTULO 7 Técnicas ... 77
5
CAPÍTULO 1
Conceitos
O objetivo deste documento é apresentar os métodos para codificação de regras de negócio, a fim de que possamos utilizá-las nas seguintes situações: Internamente: as equipes de desenvolvimento podem acessar regras de
negócio de outros módulos;
Clientes/Parceiros: podem acessar estas regras sem necessidade de conhecimento profundo da base de dados;
Diferentes Interfaces: com a necessidade do desenvolvimento para WEB surgiu a necessidade do reaproveitamento das regras de negócio entre GUI e WEB. Além disso, devem surgir outras interfaces que também viriam a reutilizar estas regras de negócio.
Para codificação destas e posterior reaproveitamento foi definida uma arquitetura de desenvolvimento de aplicações chamada de Datasul Business Objects (DBO). Neste manual, estaremos descrevendo o que é o DBO, a relação com APIs atuais, como construir, etc.
A arquitetura DBO foi definida com o objetivo de separar a camada de lógica de apresentação (interface com usuário) da camada de lógica da aplicação (regras de negócio). Assim, podemos usar a mesma lógica de aplicação para diferentes interfaces, as quais destacamos: GUI, CHUI, WEB e Java. O DBO é um programa Progress que contém a lógica de negócio e acesso a dados para uma tabela do banco de dados.
Introdução
WEB
GUI
CHUI
Java
BO Database
Interface Lógica Dados
As características desta arquitetura são:
A camada cliente pode ser escrita em Progress 4GL, Java ou qualquer linguagem que trabalhe com ActiveX. Esta camada pode ser escrita tanto pela Datasul como por qualquer outra empresa ou pessoa, por isso não é possível confiar na integridade dos dados fornecidos por ela ao DBO; O DBO deverá ser a forma da camada cliente ter acesso ao banco de dados
e por este motivo é função dele garantir a segurança e integridade dos dados:
Segurança: Significa que o DBO deve usar um mecanismo seguro de autenticação para determinar quais registros podem ser acessados pelo cliente, ou em outras palavras não é o cliente que determina ao DBO os registros que podem ou não ser acessados;
Integridade: Como o cliente acessa os dados através do DBO, este é responsável por verificar se as informações passadas por ele estão consistentes e podem ser gravados no banco de dados. Ele deve também realizar o trabalho de garantia da integridade referencial.
Arquitetura
Características da Arquitetura
CAPÍTULO 1 Conceitos 7
As características gerais de um DBO são:
É uma evolução das APIs dos produtos Datasul (adiante é explicada a diferença entre DBO e API);
Permite o reaproveitamento de regras de negócio;
contém a regra de negócio referente a um objeto, podendo este objeto ser uma tabela ou um processo;
Normalmente, trabalha com temp-tables para troca de informações com a interface;
É um programa Progress executado de forma persistente;
Um DBO deve fornecer a relação de seus métodos (procedures internas que contém as regras de negócio).
O que são as regras de negócio que queremos reaproveitar ?
São todas as operações que podemos realizar em um registro. Estas operações podem ser, por exemplo:
Validações do campo: não pode ser espaços, não pode ser 0, can-find em outra tabela, faixa de valores;
Validações no registro: validações que envolvam mais de um campo no registro;
Transações: conjunto de alterações no banco de dados que devem ser feitas por completo ou não;
Validações no registro por causa do Dataserver: por exemplo, o banco de dados Progress não tem restrição quanto ao tamanho do campo, mas no caso do Dataserver Oracle se deve fazer esta validação nos registros. Estas regras não podem estar na interface (tela). Devem estar no DBO permitindo o reaproveitamento. Na interface ficam apenas as chamadas a estas regras de negócio e apresentação do resultado (este resultado pode vir em diversos formatos: Sim/Não, temp-table de erros etc).
Características
9
CAPÍTULO 2
Concepção do DBO
Neste capítulo estão informações para que o analista possa projetar o DBO, a fim de permitir que o desenvolvedor possa construir um DBO de forma mais segura, integra, rápida e eficiente.
O analista deve definir a estrutura de DBOs do sistema. Para tanto, ele pode seguir a seguinte linha:
1 Como regra inicial podemos ter 1 (um) DBO por tabela de banco de dados, são os DBOs de entidade;
1.1 Para tabelas que contém muitos campos (ex.: item, emitente, etc.) pode-se definir mais de um DBO. Por exemplo: teríamos para a tabela Item vários DBOs de acordo com o grupo de informações organizadas, DBO Item Básico, DBO Item-Estoque, DBO Item-Compras, etc. Neste caso, a criação do registro deve ser feita somente no DBO Básico. Os outros apenas atualizam as informações pertencentes à área.
2 Para processos também podem ser definidos DBOs. Por exemplo: processo de fechamento contábil, atualização de movimento, etc..
Podemos dizer que cada API é um DBO. Nestes casos, escolhe-se a tabela mais importante do DBO para nomeá-lo.
O nome do DBO deve ser: BOxxxxx.p onde:
xxxxx = DumpName da Tabela
Descrição
Definição
Quando for necessário criar um novo DBO, deve ser usado o DumpName da tabela principal para qual está sendo criada esta BO + uma letra (A, B, C, D...). O DBO deve sempre estar relacionado à tabela principal da mesma.
Exemplo: boad001a.p, boad001b.p ...
Além do DBO, deve se criar um include com o mesmo nome do programa e terminação “.i”. Este include contém a definição da temp-table de
comunicação dos programas que vão utilizar o DBO. Exemplo: boad001.i, boad002.i ...
O nome do DBO e o nome dos diretórios devem estar em letra minúscula.
Nos casos onde já existe o BO 1.1 para determinada tabela, e alguns módulos desejarem utilizá-lo como DBO 2.0 e a equipe responsável pela conversão da BO 1.1 para DBO 2.0 não tiver disponibilidade para conversão da mesma, pode-se criar a DBO 2.0, utilizando a nomenclatura BOXX999O.P, onde:
XX999 = dump-name da tabela;
O = letra que indicará que essa DBO é uma DBO que contém a funcionalidade desejada.
A construção do DBO 2.00 por outra equipe deve ser comunicada à equipe responsável pela sua manutenção, e ter a aprovação desta, a qual deverá incluir um comentário no BO 1.1 indicando a existência de uma versão 2.00. O DBO 2.00 deverá ser extremamente simples e ter apenas (por enquanto) métodos de leitura de registros. É importante lembrar que a equipe responsável pela tabela do DBO, deverá, no futuro, complementar a sua construção com as regras de negócio e padrões de arquitetura.
Deve-se ter 1 (um) diretório de DBOs por aplicativo. Exemplo: ADBO
INBO UNBO DIBO
Para o desenvolvimento de DBOs deve ser utilizado a partir do Progress versão 8.2. Só não é aconselhado a utilização de características disponíveis somente para a versão 9.0, já que os DBOs podem ser utilizados em versões diferentes de Progress. Convivendo com BO 1.1 x DBO 2.0 Estrutura de Diretórios Versão do Progress para o DBO
CAPÍTULO 2 Concepção do DBO 11
DBO de Entidade
Estes são os DBOs mais comuns e que normalmente estão relacionados a uma determinada tabela do banco de dados.
São chamados de entidades os objetos do mundo real como Clientes, Fornecedores, Títulos a Pagar etc. Uma entidade na maioria das vezes é representada por uma tabela no banco de dados, quando este está totalmente normalizado.
Quando uma entidade possui no banco de dados mais de uma tabela, é porque o modelo lógico (entidade) é diferente do modelo físico (tabela). Isto ocorre geralmente para ganhar performance no acesso ao banco de dados.
Nota: Uma tabela deve ser mantida por um único DBO.
O DBO normalmente se relaciona a uma única tabela e, portanto, esta é uma etapa bastante simples. Quanto houver mais de uma tabela, devem ser todas relacionadas, e descritos os campos que as relacionam.
O DBO comunica-se com os seus clients recebendo e enviando um conjunto de campos (atualmente através de uma temp-table). Nesta etapa devem ser definidos os campos que fazem parte do registro. Normalmente os campos são os mesmos da tabela (LIKE tabela). Eventualmente alguns campos poderão ser campos calculados que não estão presentes na tabela do banco de dados. Estes devem então ser relacionados juntamente com a sua fórmula de cálculo. Exemplo: Valor Total do Item do Pedido Valor Unitário * Quantidade.
Definir as validações para os campos definidos na etapa anterior. Estas validações podem ser:
Valores permitidos para o campo;
Obrigatoriedade dos campos serem diferentes de branco ou zero; Existir registro com a chave informada (chave estrangeira); Impedir a alteração de um determinado campo;
Outros. Entidade Definindo a(s) tabela(s) Definindo os campos do registro Definindo as validações
Definir quais os processos e atualizações devem ser executados quando um registro é criado, alterado ou eliminado. A integridade pode ser para: Atualizar campos calculados da tabela (ex. valor total);
Eliminar tabelas filhas; Outros.
O DBO fornece ao client, quando solicitado, um registro com os valores iniciais definidos na tabela. Algumas vezes para determinar o valor inicial de um campo é necessário realizar um calculo devendo isto ser especificado. Estes cálculos são necessários, por exemplo, quando o valor inicial é:
Determinado por um valor de outra tabela. Exemplo: Alíquota de imposto inicial de um item é a alíquota da família do item;
Determinado a partir de um cálculo envolvendo várias tabelas.
O DBO é a forma do client ter acesso aos dados que estão na base de dados. Por este motivo o DBO deverá determinar quais registros poderão ser acessados pelo client (segurança) e fornecer meios de acesso que tenham boa performance.
As constraints são restrições de acesso aos registros e podemos classificá-las em 4 tipos principais:
Security (Segurança)
Dependendo de quem é o usuário que está logado no sistema, ele poderá ter acesso a apenas alguns registros da tabela. Ex: Um representante de vendas só poderá ver os pedidos dele.
Deve ser indicado como será determinada a faixa de registros com base no usuário. Essa determinação poderá ser direta, comparando o código do usuário logado a um campo do registro, ou indireta através de uma tabela auxiliar que determine para cada usuário a faixa de registro que ele tem acesso.
Exemplos:
Direta: Pedido.Representante = <usuário_logado>
Indireta: Titulo.Empresa = <usuário_logado> em Empresa_Usuario
Definindo a integridade Valores iniciais calculados Constraints (Restrições)
CAPÍTULO 2 Concepção do DBO 13
Importante: A constraint de segurança deve ser utilizada em todas as queries.
Range (Faixa)
Permite que o usuário selecione valor inicial e final para um determinado campo reduzindo com isso os registros em que será possível navegar. Essa constraint é normalmente utilizada por programas de Zoom, e os campos são os mesmos das classificações do DBO.
Um bom método para determinar as constraints de range é criar uma para cada classificação (by) que o DBO terá.
Parent or Foreing Key (Pai ou chave estrangeira)
O DBO poderá ter relacionamento com outros DBOs sendo este filho ou a tabela chave estrangeira daquele. Este tipo de constraint é usado para que sejam selecionados apenas os registros correspondentes ao DBO pai. Elas poderão ser constraints de range com valor inicial e final, ou de igualdade utilizando apenas o código da chave do pai. A escolha entre range e igualdade deve ser feita no momento da implementação considerando que:
Como range pode já ter sido definida como uma de range em função da classificação, reduzindo assim o número de métodos de constraint; Como igualdade pode-se conseguir uma melhor performance pela melhor
utilização dos índices.
Miscelaneous (Diversos)
Podemos ter outros tipos de constraints que permitem reduzir o número de registros selecionados no banco e, portanto, melhorar a performance tanto na comunicação "DBO DB" como "Client DBO". Esse tipo de constraints são normalmente as que permitem realizar filtros nos registros, ou seja, selecionar registros através de campos lógicos ou indicadores que estão na tabela. São utilizados também em programas de Zoom e escolhidos pela seleção de toggle-boxes ( [x] ).
Uma técnica para tornar estas constraints mais flexíveis é fazer um método, que recebe um valor lógico, para cada toggle, ou seja, para cada valor possível do campo indicador ou lógico.
Os registros poderão ser retornados pelo DBO em várias ordenações diferentes, e são estas ordenações e seus respectivos campos que devem ser determinados nesta etapa. Devem ser determinadas também quais as constraints que se aplicam a cada ordenação, pois podemos ter várias constraints para o DBO, porém cada order poderá utilizar apenas algumas. Cada ordenação definida terá pelo menos uma query correspondente no DBO, podendo ter mais de uma para que se obtenha melhor performance de acordo com os valores determinados para as constraints da query.
Para estas definições, devemos levar em conta as seguintes considerações: Order: Haverá sempre uma classificação pela chave única e uma pela chave primária, quando estes não são o mesmo. As outras classificações devem fazer sentido em um programa de Zoom (por nome, por descrição, por <tabela_pai>) e preferencialmente possuir índice com estes campos; Constraints da Order: Para cada order deve ser aplicada a constraint que
tem os mesmos campos da order. Normalmente deverão ser aplicadas também as constraints de Parent, cuidando para não prejudicar a performance pelo mal uso dos índices da tabela. As demais constraints deverão ser incluídas apenas se não prejudicarem o uso do índice, sendo que as de filtro costumam ser resolvidas no próprio DBO (client) e não no BD (server).
Queries: Para cada order haverá uma query, porém o uso de várias constraints em uma única query pode comprometer a eficácia do índice. Neste caso pode-se, na implementação do método OpenQuery, ter de fato várias queries e através do teste dos valores de constraint abrir aquela que melhor utiliza o índice. Na definição do DBO é importante indicar se o programador deverá considerar ou não a necessidade de implementar várias queries.
Métodos de negócio são aqueles que realizam uma ação específica para aquela entidade, ou DBO, e geralmente são cálculos ou validações específicas como, por exemplo, a análise de crédito do cliente. O nome de um método de negócio deve ser em português e seguir a estrutura Verbo + Substantivo (ex:
VerificaCredito).
Estes métodos devem preferencialmente trabalhar apenas com um único registro do DBO, ou seja, com o registro que foi posicionado com os constraints e a query, pois desta forma será respeitada a segurança imposta pela constraint de segurança.
Orders
(Classificações)
Definindo métodos de negócio
CAPÍTULO 2 Concepção do DBO 15
No caso do método trabalhar com vários registros, inclusive recebendo-os via parâmetro, deve-se utilizar a constraint de segurança no próprio método. Assim como os métodos padrão, os métodos de negócio só podem ser
executados se o usuário tiver permissão para executar o tipo de função que ele realiza. As funções padrão são: Navigation, Create, Update e Delete, sendo que é extremamente aconselhável que uma das 3 últimas seja utilizada pelo método de negócio, de acordo com o que o método realiza. É possível também definir uma nova função de segurança para o método, no entanto isso deve ser feito apenas em último caso, pois aumenta a complexidade de administração da segurança do sistema.
Portanto, para cada método de negócio deve ser definido o seu nome, os seus parâmetros, a lógica que ele deve executar e a função de segurança que ele deve obedecer.
Neste exemplo com uma tabela de pedidos de venda (ORDER) é possível verificar cada uma das etapas de definição do DBO. Para tornar este exemplo claro e prático, além das definições é apresentado também o código
correspondente a elas. INFORMAÇÕES PRELIMINARES
Tabela.: ORDER
Campo Tipo Observação
Order-Num Integer Chave única
Cust-Num Integer Chave estrangeira da
tabela Customer
Sales-Rep Character Nome do Representante
Status Integer Situação. Admite os
seguintes valores: 1 = Open 2 = Calculated 3 = Delivered ENTIDADE ORDER Exemplo de DBO de entidade
A entidade é a própria tabela.
TABELA
ORDER
CAMPOS
LIKE ORDER
STATUS-DESC Baseado no valor de Status: 1 = Open; 2 = Calculated; 3 = Delivered.
Todos os campos da tabela mais o campo status-desc que é calculado.
VALIDAÇÕES
Campo Validação
Order-Num Maior que zero; Não pode ser
alterado.
Cust-Num Deve existir na tabela ou DBO de
Customer Sales-Rep
Status Apenas valores de 1 a 3.
INTEGRIDADE
Sales-Rep é sempre o usuário logado.
Nota: Está sendo assumido, para tornar mais simples o exemplo, que o usuário logado é sempre um representante (Sales-Rep).
VALORES INICIAIS
CAPÍTULO 2 Concepção do DBO 17
Cust-Num Buscar o cliente principal do
representante na tabela Sales-Rep.
CONSTRAINTS
Tipo Campos Implementação
Security Sales-Rep = usuário logado /* Main Block */ {svc/autentic/autentic.i &vUserName=cSalesRep) Range Order-Num Cust-Num SetConstraintOrder (INPUT
iOrderNumStart, INPUT iOrderNumFinish) SetConstraintCustomer (INPUT
iCustNumStart, INPUT iCustNumFinish)
Parent / Foreing Key
Cust-Num SetConstraintCustomer (INPUT iCustNumStart, INPUT iCustNumFinish) Miscelaneous Status SetConstraintStatusOpen (INPUT
lStatusOpen) /* YES / NO */
SetConstraintStatusCalculated (INPUT lStatusCalculated) /* YES / NO */ SetConstraintStatusDelivered (INPUT lStatusDelivered) /* YES / NO */ Obs.: É utilizado um método para cada situação para que seja possível ter várias combinações.
ORDERS
Order Campos Constraints
Implementação (Query)
ByOrder Order-num Sales-Rep (Security)
Order-num (Range)
Cust-num (Parent)
OpenQuerybyOrder:
OPEN QUERY qr{&TableName} FOR EACH Order NO-LOCK
WHERE Order.Sales-Rep = cSales-Rep
AND Order.Order-Num >= iOrderStart AND Order.Order-Num <= iOrderFinish AND Order.Cust-Num >= iCustNumStart
AND Order.Cust-Num <= iCustNumFinish
AND ( lStatusOpen AND Order.Status = 1 OR lStatusCalculated AND Order.Status = 2 OR lStatusDelivered AND Order.Status = 3) BY Order.Order-Num.
ByCustomer Cust-num Sales-Rep (Security)
Cust-num (Range) Status (Miscelaneous) OpenQuerybyCustomer:
OPEN QUERY qr{&TableName} FOR EACH Order NO-LOCK
WHERE Order.Sales-Rep = cSales-Rep
AND Order.Cust-Num >= iCustNumStart AND Order.Cust-Num <= iCustNumFinish
AND ( lStatusOpen AND Order.Status = 1 OR lStatusCalculated AND Order.Status = 2 OR lStatusDelivered AND Order.Status = 3) BY Order.Cust-Num.
ByStatus Status Sales-Rep (Security)
Status (Miscelaneous) OpenQuerybyStatus:
OPEN QUERY qr{&TableName} FOR EACH Order NO-LOCK
WHERE Order.Sales-Rep = cSales-Rep
AND ( lStatusOpen AND Order.Status = 1 OR lStatusCalculated AND Order.Status = 2 OR lStatusDelivered AND Order.Status = 3) BY Order.Status.
Nota: A constraint de security é obrigatória em todas as queries.
MÉTODOS DE NEGÓCIO
Método Parâmetros
Função Segurança Lógica
CAPÍTULO 2 Concepção do DBO 19
CalculateOrder Nenhum
Update
Verificar se o pedido tem Status = 1 (Open)
Calcular o valor total do pedido somando o valor total de cada item Somar o valor do pedido ao valor total de pedidos do representante em
SalesRep
Alterar o Status para 2 (Calculated)
DBOs X APIs
Tecnicamente, o DBO é um programa Progress que é executado de forma persistente e que tem métodos que são executados. A API é um programa Progress que é executado diretamente, recebendo e devolvendo parâmetros, e que executa uma função específica.
Os DBOs devem utilizar as APIs que já existem atualmente no produto.
Programas de
Interface DBO API
Observe neste diagrama que as APIs podem ser acessadas pelos DBOs.
Qual a função do DBO e da API
No DBO temos os métodos de navegação e também os métodos com regras de negócio. Se houver alguma API, o DBO deve reutilizá-la, conforme diagrama acima. Neste caso, a lógica de negócio deve estar na API. Isto evita a
duplicação de código.
O DBO tem por função ser a nossa interface com outros produtos. É a nossa camada de negócio.
Como ficam as APIs existentes atualmente
Devem ser aproveitadas pelos DBOs que estão sendo construídos. Ou seja, devem ser chamadas pelos DBOs. Se houver algum outro programa que use a API, ela deve ser mantida.
Quando criar API ou DBO
Se for um novo desenvolvimento (módulo ou funcionalidade) devem ser construídos somente DBOs. Nos módulos já existentes devem ser escritos DBOs que utilizem as APIs já existentes, ou seja, o DBO vai ter os métodos básicos mais métodos que façam a chamada à API.
Normalmente, as APIs são programas mais complexos que tem um
processamento mais longo e que, talvez, possam até ser executados em outro equipamento via RPC ou RPW.
Outro critério, a ser levado em consideração, é não deixar o código do DBO (e consequentemente o .r) muito grande.
CAPÍTULO 3 Como construir o DBO 21
CAPÍTULO 3
Como construir o DBO
Neste capítulo estão informações para que o desenvolvedor possa construir um DBO de forma que possa ser reutilizado. Por exemplo: quais os métodos obrigatórios, qual a assinatura dos métodos e várias outras situações que devem ser observadas no desenvolvimento do DBO.
Acesso a Banco de Dados
Os acessos a base de dados pelo DBO, devem estar relacionados à tabela principal do DBO. Por exemplo: para buscar algum valor da tabela conta contábil, deve usar o DBO específico da mesma e não replicar este acesso em DBOs de outras tabelas.
Pode-se acessar outras tabelas dentro do DBO desde que estejam no mesmo aplicativo. Esta é a única exceção. Do contrário o DBO somente acessa sua tabela principal.
Arquitetura do DBO
Um DBO é constituído das seguintes seções:
Definições
No início do DBO defini-se:
&DBOName Nome do DBO &DBOVersion Versão do DBO
&DBOCustomFunctions Nome das funções customizadas
&TableName Nome da tabela principal do programa &TableLabel Desc./Label da tabela principal do programa &QueryName Nome da query principal do programa
Objetos de trabalho
Temp-table RowObject: Esta é uma Temp-table de comunicação. Cada DBO deve definir uma temp-table para troca de dados com os programas que o estão utilizando. Normalmente, esta temp-table é definida como a tabela principal do DBO (LIKE <TableName>).
Temp-table RowErrors: Esta é a temp-table padrão de erros do DBO. Quando for necessário retornar algum erro, deve-se usar esta temp-table que pode conter um ou mais erros.
Esta temp-table é definida automaticamente para o DBO. E possui a definição a seguir:
Campo Tipo Descrição
ErrorSequence Integer Indica a seqüência do erro
ErrorNumber Integer Contém o número do erro
ErrorDescription Character Contém a descrição do erro ErrorParameters Character Contém os parâmetros do erro
ErrorType Character Indica o tipo do erro
ErrorHelp Character Contém o help do erro
ErrorSubType Character Indica o sub-tipo do erro
Métodos Básicos
Procedures internas padrão do DBO que contém o comportamento básico esperado de um DBO.
Métodos de Negócio
Os métodos de negócio são definidos pelo analista, para atendimento de funções específicas relacionadas ao objeto que está se trabalhando. No DBO os métodos de negócio são procedures internas criadas pelo
desenvolvedor. As regras para criação destes métodos estão descritas a seguir: Este método deve agir preferencialmente sobre o registro corrente e não
num conjunto de registros. Assim, o programa chamador tem mais flexibilidade em controlar a operação. Por exemplo: um DBO da tabela item pode ter um cálculo. Esta lógica estaria num método que atuaria
CAPÍTULO 3 Como construir o DBO 23
somente no registro corrente. Para processar novo item deve-se posicionar no próximo (getNext);
Este método deve ser uma procedure interna e deve ter um comentário em seguida, explicando o que faz o método. Esta descrição deve ser mais que uma característica técnica, ou seja, deve ajudar ao programador identificar o que faz o método;
O nome do método deve ter como base: verbo + substantivo(s) + sujeito. A primeira palavra deve estar em minúsculo e as outras devem ter a primeira letra em maiúsculo (mesmo padrão adotado na linguagem java). Além disso, o nome deve estar na língua portuguesa. Desta forma, deve ficar mais bem documentado do que se trata o método;
Exemplo: calculaMedia, insereOrder, salvaCustomer, etc.
O método deve retornar valores em parâmetros ou temp-tables de saída (output param). Deve ser utilizado ‘return-value’ para indica se o método foi processado com sucesso (OK/NOK). Quando ocorrem erros, estes devem ser inclusos na temp-table RowErrors, se necessário.
Override de Métodos
Em muitos casos faz-se necessário a customização de métodos básicos, tais como: createRecord, deleteRecord, updateRecord etc, a fim de facilitar este tipo de customização foi desenvolvida a técnica de Override de Métodos. Esta técnica executa dois outros métodos definidos pelo desenvolvedor a partir dos métodos básicos (a seguir está listada uma tabela com os métodos básicos que possuem override).
A tabela a seguir indica quais os métodos básicos que possuem override e quais os parâmetros a serem definidos:
Método Parâmetro(s) getFirst não há
getLast não há
getNext não há
getPrev não há
repositionRecord INPUT pRowid AS ROWID
Método Parâmetro(s) deleteRecord* não há updateRecord* não há getRecord não há setRecord não há newRecord não há copyBuffer2TT* não há copyTT2Buffer não há
getRowid INPUT-OUTPUT pRowid AS ROWID
getBatchRawRecords** não há
getBatchRawRecordsPrev** não há
getBatchRecords** não há
getBatchRecordsPrev** não há
*Nestes métodos, caso o retorno da procedure customizada seja "NOK" a transação pode ser desfeita.
**Nestes métodos os pontos de override compreendem apenas a transferência de cada registro individualmente para a temp-table auxiliar.
Além disso a procedure de override definida pelo desenvolvedor deve seguir a regra abaixo de nomenclatura:
<before/after><nome-do-método-básico>
Exemplo: Para customizar o método createRecord, deve-se definir as procedures a seguir conforme a necessidade.
PROCEDURE beforecreateRecord :
/*--- Purpose: Override do método createRecord (before)
Parameters: Notes: ---*/ RETURN "OK":U. END PROCEDURE. PROCEDURE aftercreateRecord : /*--- Purpose: Override do método createRecord (after)
Parameters: Notes:
CAPÍTULO 3 Como construir o DBO 25
---*/ RETURN "OK":U.
END PROCEDURE.
Neste exemplo, a primeira procedure (beforecreateRecord) é executada antes do código principal do método createRecord, e a segunda procedure
(aftercreateRecord) é executada após o código principal do método createRecord.
Além disso, nestes procedures deve-se utilizar o comando RETURN "OK":U para indicar que o método principal não deve ser cancelado, e o comando RETURN "NOK":U para indicar que o método principal deve ser cancelado.
Construção do DBO
Para o desenvolvimento do DBO deve ser utilizado o UIB (AppBuilder, quando utilizado versão 9) do Progress.
Para criação de um DBO o desenvolvedor deve seguir os seguintes passos (estaremos construindo como exemplo um DBO de manutenção na tabela Customer da base de dados Sports do Progress):
Criar programa e include de DBO a partir do template
O template do programa é o DBO Program, o template do include é o DBO Temp-Table. Opta-se por estes templates ao criar-se um novo objeto no UIB (AppBuilder, quando utilizado versão 9).
Observação: O identificador :T no inícios de alguns comentários, é usado para a tradução dos fontes dos templates para outros idiomas. Este identificador não afeta em nada a funcionalidade do programa.
Acertar os preprocessadores padrão
DBO Program:
DBOName: Este preprocessador recebe o nome do programa DBO; DBOVersion: Este preprocessador recebe a versão do programa DBO; DBOCustomFunctions: Este preprocessador recebe o nome das funções
customizadas (definidas pelo desenvolvedor), separadas por " , " (vírgula). Este preprocessador é utilizado nos includes padrão do DBO;
TableName: Este preprocessador recebe a tabela principal do programa. Este preprocessador é utilizado nos includes padrão do DBO;
TableLabel: Este preprocessador recebe a descrição (label) da tabela principal do programa. Este preprocessador é utilizado nos includes padrão do DBO;
QueryName: Este preprocessador recebe o nome da query principal do programa. Este preprocessador é utilizado nos includes padrão do DBO.
Exemplo: Temos no início do programa o código a seguir.
/* ********************** Definitions *********************** */ /*--- Diretrizes de definição ---*/
&GLOBAL-DEFINE DBOName boxx9999
&GLOBAL-DEFINE DBOVersion 1.00.00.000
&GLOBAL-DEFINE DBOCustomFunctions
&GLOBAL-DEFINE TableName Customer
&GLOBAL-DEFINE TableLabel Customer
&GLOBAL-DEFINE QueryName qr{&TableName}
Nota: Caso seja definido que na utilização do DBO será permitido criar registros fora da query aberta, deve ser incluído o preprocessador
NewRecordOffQuery. Lembrando que com a inclusão deste preprocessador não será apresentado erro de reposicionamento de registros na query. &GLOBAL-DEFINE NewRecordOffQuery YES
Definição da temp-table de comunicação
Colocar o nome correto do include com a temp-table de comunicação do programa. Este include deve estar no mesmo diretório do DBO e recebe como parâmetro o nome da temp-table de comunicação, como padrão RowObject. Só é alterada a terminação (de “.p” para “.i”).
Exemplo: A chamada ao include de definição da temp-table RowObject. /*--- Include com definição da temp-table RowObject ---*/
CAPÍTULO 3 Como construir o DBO 27
Definição da Query
A definição da query padrão está no include method/dboqry.i. Porém, havendo a necessidade de customizar a definição da query, deve-se retirar a chamada ao include e definir manualmente a query (lembrando de utilizar o preprocessor {&QueryName} para defini-la).
Exemplo: A chamada ao include de definição da query {&QueryName}. /*--- Include com definição da query para tabela {&TableName} ---*/ /*--- Em caso de necessidade de alteração da definição da query, pode ser retirada a chamada ao include a seguir e em seu lugar deve ser feita a definição manual da query ---*/
{method/dboqry.i}
Neste caso, a definição da query não foi customizada. Porém, a seguir está um exemplo no qual a definição da query foi alterada:
Exemplo: Customização da definição da query {&QueryName}.
/*--- Include com definição da query para tabela {&TableName} ---*/ /*--- Em caso de necessidade de alteração da definição da query, pode ser retirada a chamada ao include a seguir e em seu lugar deve ser feita a definição manual da query ---*/
DEFINE QUERY {&QueryName}
FOR Customer FIELDS (Cust-Num Name) SCROLLING.
Mesmo quando a definição da query for alterada, deve-se sempre utilizar uma única tabela e, também, utilizar a opção SCROLLING.
Observação: Para os casos onde é utilizado banco de dados Oracle deve-se verificar o capítulo de Técnicas / Melhorar Performance em Bancos Oracle.
Definição das Aberturas de Query e setConstraint
Pode haver uma relação entre setConstraint<Description> e
openQuery<Description>, pois quem vai usar o DBO pode chamar openQuery após setConstraint. Assim, podemos ter setConstraint e openQuery isolados para navegações e seleções específicas.
Vale salientar que para realizar a abertura da query deve ser utilizado o método openQueryStatic, passando como parâmetro a descrição da query a ser aberta. A seguir são descritas as situações previstas:
Para a navegação padrão de uma tabela (sem constraints e sem outras classificações):
PROCEDURE setConstraintMain: RETURN "OK":U.
END PROCEDURE.
PROCEDURE openQueryMain:
OPEN QUERY {&QueryName} FOR EACH {&TableName} NO-LOCK. RETURN "OK":U.
END PROCEDURE.
Quem usar esta versão do DBO vai chamar openQueryStatic(INPUT "Main":U).
Para criar uma nova classificação para query, exemplo: por nome:
Exemplo: PROCEDURE setContraintMain: RETURN "OK":U. END PROCEDURE. PROCEDURE setConstraintByName: RETURN "OK":U. END PROCEDURE. PROCEDURE openQueryMain:
OPEN QUERY {&QueryName} FOR EACH {&TableName} NO-LOCK. RETURN "OK":U.
END PROCEDURE.
PROCEDURE openQueryByName:
OPEN QUERY {&QueryName} FOR EACH {&TableName} NO-LOCK BY Customer.Name.
RETURN "OK":U. END PROCEDURE.
Neste caso, o desenvolvedor vai ter opção de executar
openQueryStatic(INPUT "Main":U) ou openQueryStatic("ByName":U) e então, navegar por código (índice primário) ou por nome (By customer.name). Constraints para toda tabela.
Exemplo:
DEFINE VARIABLE cSalesRep LIKE Customer.Sales-Rep NO-UNDO. PROCEDURE setConstraintMain:
DEFINE INPUT PARAMETER pSalesRep LIKE Customer.Sales-Rep NO-UNDO.
ASSIGN cSalesRep = pSalesRep. RETURN "OK":U.
END PROCEDURE.
PROCEDURE setConstraintByName:
CAPÍTULO 3 Como construir o DBO 29
ASSIGN cSalesRep = pSalesRep. RETURN "OK":U.
END PROCEDURE.
PROCEDURE openQueryMain:
OPEN QUERY {&QueryName} FOR EACH {&TableName} NO-LOCK WHERE Customer.Sales-Rep = cSalesRep.
RETURN "OK":U. END PROCEDURE.
PROCEDURE openQueryByName:
OPEN QUERY {&QueryName FOR EACH {&TableName} NO-LOCK WHERE Customer.Sales-Rep = cSalesRep
BY {&TableName}.Name. RETURN "OK":U.
END PROCEDURE.
Neste caso, está sendo feita uma seleção por representante (sales-rep). Este DBO deve estar preparado para navegar somente nos registros de Customer de um determinado sales-rep que foi informado ao chamar as procedures
setConstraintMain e setConstraintByName. Elas apenas armazenam o parâmetro recebido na variável que vai ser usada pela openQueryStatic. Constraints específicos para uma query
Exemplo:
DEFINE VARIABLE cSalesRep LIKE Customer.Sales-Rep NO-UNDO. DEFINE VARIABLE deCreditLimit LIKE Customer.Credit-Limit NO-UNDO. PROCEDURE setConstraintMain:
DEFINE INPUT PARAMETER pSalesRep LIKE Customer.Sales-Rep NO-UNDO.
ASSIGN cSalesRep = pSalesRep. RETURN "OK":U.
END PROCEDURE.
PROCEDURE setConstraintByName:
DEFINE INPUT PARAMETER pSalesRep LIKE Customer.Sales-Rep NO-UNDO. DEFINE INPUT PARAMETER pCreditLimit LIKE Customer.Credit-Limit NO-UNDO.
ASSIGN cSalesRep = pSalesRep deCreditLimit = pCreditLimit. RETURN "OK":U.
END PROCEDURE.
PROCEDURE openQueryMain:
OPEN QUERY {&QueryName} FOR EACH {&TableName} NO-LOCK WHERE Customer.Sales-Rep = cSalesRep.
RETURN "OK":U. END PROCEDURE.
PROCEDURE openQueryByName:
OPEN QUERY {&QueryName FOR EACH {&TableName} NO-LOCK WHERE Customer.Sales-Rep = cSalesRep AND
Customer.Credit-Limit >= deCreditLimit BY {&TableName}.Name.
RETURN "OK":U. END PROCEDURE.
Neste caso, a query ByName além da Constraint referente ao sales-rep temos a restrição do credit-limit (só mostra os registros maiores que determinado crédito). Neste caso, somente a query ByName é afetada
(openQueryStatic(INPUT "ByName":U)). Se for utilizada
openQueryStatic(INPUT "Main":U) não deve haver esta verificação do credit-limit.
Observação:
Ao realizar a construção dos métodos de openQuery e setConstraint,
normalmente, é mantido um relacionamento 1 – 1. Então é aconselhado que ao alterar-se uma ou mais restrições de um openQuery seja criado um novo método de abertura.
Isto é devido ao fato de não impedir o correto funcionamento das interfaces que fazem uso do antigo método de abertura. Além disso pode-se optar pela criação de valores iniciais para as restrições para que as antigas interfaces continuem a funcionar com as novas restrições do openQuery.
Criação do método goToKey
Para o índice único que melhor pode ser utilizado para localizar o registro diretamente deve ser criado um método no DBO que receba os campos, execute o find e posicione no registro, através do método repositionRecord. Se não for encontrado insere uma mensagem de erro na temp-table RowErrors, através da include method/svc/errors/inserr.i, e retorna um flag "NOK":U.
Exemplo:
PROCEDURE goToKey:
/*--- Purpose: Reposiciona registro através de índice único Parameters:
Notes:
---*/ DEFINE INPUT PARAMETER pCustNum LIKE Customer.Cust-Num NO-UNDO.
FIND bfCustomer WHERE bfCustomer.Cust-Num = pCustNum NO-LOCK NO-ERROR.
CAPÍTULO 3 Como construir o DBO 31
IF NOT AVAILABLE bfCustomer THEN DO: {method/svc/errors/inserr.i &ErrorNumber="" &ErrorType="" &ErrorSubType= "ERROR" &ErrorParameters="''"} RETURN "NOK":U. END.
RUN repositionRecord IN THIS-PROCEDURE (INPUT ROWID(bfCustomer)). IF RETURN-VALUE = "NOK":U THEN
RETURN "NOK":U.
RETURN "OK":U. END PROCEDURE.
Criação do método linkTo<Description>
Quando existe a necessidade de comunicação entre DBOs, seja através do client ou do próprio DBO, deve-se utilizar o método linkTo<Description>. Este deve receber o handle do DBO Pai e executar o método getKey neste DBO, a fim de receber os valores dos campos do índice único do DBO Pai. Após receber o retorno destes campos, o DBO Filho então, pode executar um método do tipo setConstraint para setar uma restrição de abertura para a query. A nomenclatura deste método consiste em utilizar os termos linkTo +
<Descrição que identifique o DBO Pai>. Exemplo:
Exemplo:
DEFINE VARIABLE iCustNum LIKE Customer.Cust-Num NO-UNDO. PROCEDURE linkToItem:
/*--- Purpose: Recebe handle do DBO Item e execute método getKey Parameters: recebe handle de um DBO
Notes:
---*/ DEFINE INPUT PARAMETER pHandle AS HANDLE NO-UNDO.
RUN getKey IN pHandle (OUTPUT iCustNum).
RUN setConstraintCustomer IN THIS-PROCEDURE (INPUT iCustNum).
RETURN "OK":U. END PROCEDURE.
Criação do método getKey
Deve ser utilizado em conjunto com o método linkTo<Description>.
Para o índice único deve ser criado um método no DBO que retorna os valores dos campos do registro corrente.
Exemplo:
PROCEDURE getKey:
/*--- Purpose: Retorna valores dos campos do índice único
Parameters: retorna cust-num Notes:
---*/ DEFINE OUTPUT PARAMETER pCustNum LIKE Customer.Cust-Num NO-UNDO.
IF AVAILABLE Customer THEN
ASSIGN pCustNum = Customer.Cust-Num. ELSE
ASSIGN pCustNum = ?.
RETURN "OK":U. END PROCEDURE.
Fazer validações comuns ao create e update do registro
No método validateRecord devem ser feitas todas as validações pertinentes ao DBO. Mas para identificar qual o tipo de validação a ser executa deve-se utilizar o parâmetro pType.
Caso exista algum erro deve-se criar uma mensagem de erro padrão na temp-table RowErrors, através da include method/svc/errors/inserr.i.
As validações devem ser feitas preferencialmente sobre os campos da temp-table RowObject.
Exemplo:
PROCEDURE validateRecord:
/*--- Purpose: Valida temp-table RowObject
Parameters: recebe o tipo de validação
---*/ DEFINE INPUT PARAMETER pType AS CHARACTER NO-UNDO.
IF pType = "Create":U OR pType = "Update":U THEN DO: IF RowObject.Name = "":U THEN
{method/svc/errors/inserr.i &ErrorNumber="" &ErrorType=""
&ErrorSubType="ERROR" &ErrorParameters="''"}
CAPÍTULO 3 Como construir o DBO 33 IF RowObject.Credit-Limit < 0 THEN {method/svc/errors/inserr.i &ErrorNumber="" &ErrorType="" &ErrorSubType="ERROR" &ErrorParameters="''"} END. IF CAN-FIND(FIRST RowErrors
WHERE RowErrors.ErrorSubType = "ERROR":U) THEN RETURN "NOK":U.
RETURN "OK":U. END PROCEDURE.
Verificar ocorrência de chave duplicada ou outras validações
referentes somente a criação de registros
Antes de proceder a criação do registro, deve-se verificar se existe chave duplicada no método validateRecord. Em caso de chave duplicada ou outro erro qualquer, deve-se inseri-lo na temp-table RowErrors e retornar "NOK":U.
Exemplo:
/*--- Purpose: Valida temp-table RowObject
Parameters: recebe o tipo de validação Notes:
---*/ DEFINE INPUT PARAMETER pType AS CHARACTER NO-UNDO.
IF pType = "Create":U THEN DO: IF CAN-FIND(Customer
WHERE Customer.Cust-Num = RowObject.Cust-Num) THEN {method/svc/errors/inserr.i &ErrorNumber="" &ErrorType="" &ErrorSubType="ERROR" &ErrorParameters="''"} END.
IF pType = "Create":U OR pType = "Update":U THEN DO: IF RowObject.Cust-Num <= 0 THEN {method/svc/errors/inserr.i &ErrorNumber="" &ErrorType="" &ErrorSubType="ERROR" &ErrorParameters="''"} END. IF CAN-FIND(FIRST RowErrors
RETURN "NOK":U.
RETURN "OK":U. END PROCEDURE.
Vale salientar, que existindo a necessidade de realizar validações específicas para a alteração/eliminação de registros deve ser utilizado o método
validateRecord, porém deve-se verificar o valor do parâmetro pType a fim de identificar qual o tipo de validação a ser executada.
Criação de erros
Quando o desenvolvedor possui a necessidade da inclusão de erros na temp-table RowErrors, deve ser utilizado o include method/svc/errors/inserr.i. O include method/svc/errors/inserr.i é utilizado para realizar a inclusão de erros Progress ou erros de Produto, e ainda contempla a possibilidade de inclusão de erros manuais. Para inclusão de Erros Progress e de Produto, recebe os parâmetros a seguir:
ErrorNumber: número do erro, quando o erro for do tipo Outros; ErrorType: tipo do erro, podendo ter o valor Progress, Outros, EMS
ou HR (qualquer valor pode ser utilizado nesta descrição); ErroSubType: sub-tipo do erro, podendo ter o valor Error,
Information ou Warning; o registro incluso somente é considerado um erro quando seu sub-tipo é Error, caso seja Information ou Warning, o procedimento continuará;
ErrorParameters: parâmetros do erro, quando o erro for do tipo Outros estes parâmetros irão ser utilizados na chamadas das mensagens padrão do Produto.
Exemplo: Inclusão de erros do produto. {method/svc/errors/inserr.i
&ErrorNumber="1" &ErrorType="EMS"
&ErrorSubType="ERROR"
&ErrorParameters="'Customer'"}
Este exemplo traz um erro do produto EMS, e recebe parâmetros “Customer”, sendo assim, na interface pode-se executar a mensagem padrão do produto para o erro 1, e passar para a mensagem o parâmetro “Customer”, além disso o sub-tipo do erro indica ERROR, então o procedimento será abortado.
CAPÍTULO 3 Como construir o DBO 35 {method/svc/errors/inserr.i &ErrorNumber="32" &ErrorType="HR" &ErrorSubType="ERROR" &ErrorParameters="'Order~~~~ ' + STRING(TODAY)"} {method/svc/errors/inserr.i &ErrorNumber="42" &ErrorType="HR" &ErrorSubType="WARNING" &ErrorParameters="'Order~~~~Customer'"}
Nestes exemplos temos erros de produto, onde o segundo exemplo traz um sub-tipo WARNING, sendo assim, a interface poderá tratar e mostrar a mensagem de erro ao usuário, mas o procedimento não será abortado.
Exemplo: Inclusão de erros Progress. CREATE Customer NO-ERROR.
IF ERROR-STATUS:ERROR THEN {method/svc/errors/inserr.i
&ErrorType="PROGRESS" &ErrorSubType="ERROR"} DELETE Customer NO-ERROR.
IF ERROR-STATUS:ERROR THEN {method/svc/errors/inserr.i
&ErrorType="PROGRESS" &ErrorSubType="ERROR"}
O include pode ser utilizado para realizar a inclusão de erros manuais, ou seja todos as informações referentes ao erro são informadas pelo desenvolvedor. Desta forma o include recebe os parâmetros a seguir:
ErrorNumber: número do erro, quando o erro for do tipo Outros; ErrorType: tipo do erro, podendo ter o valor Outros (qualquer valor
pode ser utilizado nesta descrição);
ErrorSubType: sub-tipo do erro, podendo ter o valor Error, Information ou Warning; o registro incluso somente é considerado um erro quando seu sub-tipo é Error, caso seja Information ou Warning, o procedimento continuará;
ErrorDescription: descrição do erro, na descrição os parâmetros já devem estar substituídos;
ErrorHelp: help do erro, no help os parâmetros já devem estar substituídos;
ErrorParameters: parâmetros do erro, quando o erro for do tipo Outros.
Quando utilizar o parâmetro ErrorDescription, deve-se utilizar o ErrorSubType.
Exemplo: Inclusão de erros do produto. IF Customer.Cust-Num <= 0 THEN {method/svc/errors/inserr.i &ErrorNumber="112" &ErrorType="EMS" &ErrorSubType="ERROR"
&ErrorDescription="Valor inválido para o Cust-Num" &ErrorHelp="Informe valor maior que 0 para o Cust-Num"} DELETE Customer NO-ERROR.
IF ERROR-STATUS:ERROR THEN {method/svc/errors/inserr.i &ErrorNumber="12" &ErrorType="EMS" &ErrorSubType="ERROR"
&ErrorDescription="Erro ao eliminar registro"}
Note-se que a utilização do parâmetro ErroDescription deve ser feita quando o erro a ser notificado não for um erro já cadastrado no Produto.
Definição das Aberturas de Query e setConstraint com Join entre
Tabelas
Quando for necessário fazer uma query em um DBO e esta query possui como restrição (constraint) um conjunto de registros de outra tabela, ou seja, é uma construção EACH, EACH, deve ser criado um DBO exclusivo para cada query. A nomenclatura padrão para estes DBOs é boxx999Qyy.p, onde xx999 é o dump-name da tabela filha e yy é um número de 01 à 99. As diferenças na construção desta DBO em relação às DBOs normais são:
Definição da Query: deve ser definida manualmente, pois terá mais de uma tabela;
Definição das Aberturas de Query e setConstraint: nos métodos openQuery o comando OPEN QUERY utilizará mais de uma tabela no FOR EACH pois a query foi definida com mais tabelas; as queries deverão
CAPÍTULO 3 Como construir o DBO 37
utilizar sempre o mesmo conjunto de tabelas e seguir a mesma seqüência; se for necessário criar uma query com tabelas diferentes é necessário criar um novo "DBO de Join" pois cada um suporta apenas uma única definição de query que é onde se definem as tabelas e sua ordem de encadeamento; pode haver um ou mais métodos setConstraint, dependendo do número de campos utilizados nos WHERE do Open Query; a diferença neste caso é que os campos do setConstraint são utilizados para restrições de várias tabelas;
gotoKey e getKey: normal, lembrando apenas que se aplicam a tabela filha;
linkTo<parent>: não construir;
Validações: não precisam ser construídas pois o DBO é apenas para leitura.
Criação do método setSecurityConstraint
Este método é usado para definir a parte obrigatória da clausula WHERE que é montada no uso de Query Dinâmica. É através da Security Constraint que se controla a quais registros o cliente do DBO tem acesso, geralmente baseado no usuário logado.
Este é um método opcional. Se não informado, o client do DBO poderá navegar em todos os registros da tabela do DBO.
Para o índice único deve ser criado um método no DBO que retorna os valores dos campos do registro corrente.
Exemplo:
PROCEDURE getSecurityConstraint:
/*---
Purpose: Retorna o conteúdo da SecurityConstraint para ser usado na QueryDinamica
Parameters: pSecurityConstraint Notes:
---*/ DEFINE OUTPUT PARAM pSecurityConstraint AS CHAR NO-UNDO.
ASSIGN pSecurityConstraint = "customer.cust-num >= 2":U. RETURN "OK":U.
XML no DBO
O DBO pode atuar como um produtor (Producer) de mensagens XML, bem como um receptor (Receiver) delas.
O DBO vai produzir mensagens XML quando um registro for incluído, alterado ou eliminado através de seus métodos padrão. A mensagem será enviada ao serviço de Message Broker (ver adiante Serviço de Message Broker)
O DBO vai receber mensagens através do método receiveMessage as quais vão disparar ações de inclusão, alteração, eliminação e leitura da base de dados. Neste caso o DBO não utiliza o serviço de Message Broker.
Os dois comportamentos podem ser ativados separadamente, apesar disso ser raro. Para ativá-lo, deve-se informar um conjunto de pré-processadores conforme visto a seguir.
Informar os pré-processadores para XML Producer
DBO Program:
XMLProducer: Informar apenas YES para ativar todo o código de envio de mensagens nos métodos padrão. Obs: É necessário que o serviço de Message Broker esteja configurado também;
XMLTopic: Tópico da Mensagem enviada ao Message Broker, geralmente o nome da tabela;
XMLTableName: Nome da tabela que deve ser usado como TAG no XML;
XMLTableNameMult: Nome da tabela no plural. Usado para agrupar vários registros da tabela;
XMLPublicFields: Lista dos campos (c1,c2) que podem ser enviados via XML. Geralmente ficam fora desta lista os campos de especialização da tabela;
XMLKeyFields: Lista dos campos chave da tabela (c1,c2). Campos que são enviados sempre;
CAPÍTULO 3 Como construir o DBO 39
XMLExcludeFields: Lista de campos a serem excluídos do XML. Deve ser informado apenas se XMLPublicFields = "".
Exemplo:
/* ********************** Definitions *********************** */ &GLOBAL-DEFINE XMLProducer YES
&GLOBAL-DEFINE XMLTopic CUSTOMER
&GLOBAL-DEFINE XMLTableName CUSTOMER &GLOBAL-DEFINE XMLTableNameMult CUSTOMERS &GLOBAL-DEFINE XMLPublicFields
&GLOBAL-DEFINE XMLKeyFields CUST-NUM &GLOBAL-DEFINE XMLSender SPO
&GLOBAL-DEFINE XMLExcludeFields
Informar os pré-processadores para XML Receiver
DBO Program:
XMLReceiver: DBO atua como receiver de mensagens enviadas pelo Message Broker (método receiveMessage);
KeyField1, KeyField2: Informar os campos da chave da tabela quando o Progress não conseguir resolver find {&TableName} OF RowObject, gerando o erro “** More than one index found for <table> OF <table> -- use WHERE, not OF. (446)”. Isso geralmente acontece em tabelas que não tem índice único ou que tem mais de um índice único.
Exemplo:
/* ********************** Definitions *********************** */
&GLOBAL-DEFINE XMLReceiver YES &GLOBAL-DEFINE KeyField1 CUST-NUM
Nota: Provisoriamente é necessário setar o pré-processador DYNAMIC-QUERY-ENABLED para ativar os métodos de query dinâmica. Após o período de beta-teste. Ele estará disponível a todos os DBOs.
Utilizando Serviços no DBO
O que são serviços? Nada mais são do que programas/procedures internas que podem estar disponíveis para o DBO conforme a configuração feita para o Produto.
Os serviços padrão disponíveis ao DBO são:
Implementações conforme tipo de Banco de Dados; Customização para Parceiros/Clientes;
Erros do Produto;
Tratamento de erros Progress; Segurança para o programa DBO;
Segurança aos métodos do programa DBO.
Estes serviços são criados e manutenidos por uma equipe de administração do produto.
A utilização de alguns destes serviços pode ser feita diretamente pelo desenvolvedor.
Serviço de Banco
O serviço de Banco pode ser utilizado pelo desenvolvedor para realizar implementações específicas para um banco de dados.
O uso deste é feito através do preprocessador DBType, que possui o tipo de banco de dados utilizado pelo DBO.
Exemplo: Impedir que o comando GET LAST seja utilizado quando o banco não for Progress.
PROCEDURE afterNewRecord:
&IF "{&DBType}":U = "PROGRESS":U &THEN
GET LAST {&QueryName} NO-LOCK.
&ENDIF
END PROCEDURE.
Nesse exemplo foi exposto uma maneira do desenvolvedor impedir que o comando GET LAST seja utilizado quando o banco de dados não for Progress, através do uso do preprocessador DBType.
CAPÍTULO 3 Como construir o DBO 41
Serviço de Autenticação
O serviço de Autenticação pode ser utilizado pelo desenvolvedor a fim de obter o valor de algumas variáveis de contexto.
O uso deste é feito através do include method/svc/autentic/autentic.i que recebe os parâmetros a seguir:
Parâmetro Tipo Descrição
vUserAccessType Integer Contém o tipo de acesso do usuário corrente
vUserEnterprise Integer Contém o código da empresa do usuário corrente
vUserName Character Contém o código do usuário corrente
vUserCountryTax Integer Contém o código do imposto do país do usuário corrente
vUserGroupSecurityList Character Contém a lista dos grupos de segurança do
usuário
vHRProgramSecurity Handle Handle do programa de segurança do produto HR
Exemplo: Setar automaticamente uma constraint com o valor do usuário corrente e da empresa do usuário corrente.
/* Definitions --- */ DEFINE VARIABLE iUserEnterprise AS INTEGER NO-UNDO.
DEFINE VARIABLE cUserName AS CHARACTER NO-UNDO.
/* Main Block --- */
{method/svc/autentic/autentic.i
&vUserEnterprise="iUserEnterprise" &vUserName="cUserName"}
RUN setConstraintSecurity IN THIS-PROCEDURE (INPUT iUserEnterprise,
INPUT cUserName).
Nesse exemplo foi utilizado o serviço de autenticação para setar automaticamente um constraint que indica o usuário corrente.
Serviço de Customização
O serviço de Customização pode ser utilizado pelo desenvolvedor para incluir novos pontos para que Clientes/Parceiros possam customizar o processamento do DBO.
Para os DBOs existem dois pontos pré-definidos nos métodos createRecord, updateRecord e deleteRecord. Estes pontos são executados no início e no final
do método e, ainda, estão dentro da transação do método, permitindo que a customização possa cancelar a transação.
Quando o desenvolvedor analisar que um método específico deve possuir um ponto para customização, deve utilizar o include method/svc/custom/custom.i. Além disso, o desenvolvedor deve verificar se o método pode ser cancelado. A utilização do include é simples, bastando o desenvolvedor informar o valor do parâmetro &Event, que indica o nome do evento de customização.
Aconselha-se que a definição do include seja feita nos pontos inicial e final do método que se deseja customizar. E a nomenclatura para o parâmetro Event deve ser <before/after><nome-do-método>.
Exemplo: Inclusão de pontos de customização para o método calculateOrder. PROCEDURE calculateOrder: {method/svc/custom/custom.i &Event="beforeCalculateOrder"} ... {method/svc/custom/custom.i &Event="afterCalculateOrder"} END PROCEDURE.
Nesse exemplo foram inclusos dois pontos para customização. Estes pontos não permitem que o customizador cancele o método.
Caso o desenvolvedor opte por definir os pontos de customização com opção de cancelamento, deve ser tratado o RETURN-VALUE após a chamada ao include e, ainda, a variável lCustomExecuted a fim de identificar se o problema de customização foi executado.
Exemplo: Inclusão de pontos de customização para o método calculateOrder e tratamento do retorno.
PROCEDURE calculateOrder: DO TRANSACTION:
{method/svc/custom/custom.i &Event="beforeCalculateOrder"} IF lCustomExecuted AND RETURN-VALUE = "NOK":U THEN
UNDO, RETURN "NOK":U.
...
{method/svc/custom/custom.i &Event="afterCalculateOrder"} IF lCustomExecuted AND RETURN-VALUE = "NOK":U THEN UNDO, RETURN "NOK":U.
END. END PROCEDURE.
CAPÍTULO 3 Como construir o DBO 43
Serviço de Segurança
O serviço de Segurança divide-se em dois estilos: segurança para o DBO, feita automaticamente, e segurança para método.
Somente a segurança de métodos pode ser utilizada pelo desenvolvedor. O uso deste é feito através do include method/svc/security/permit.i que recebe o parâmetro &Method, que contém o nome do método ou um nome que identifique um grupo de métodos.
Os métodos de navegação (getFirst, getPrev, getNext, getLast e
repositionRecord) e de update (createRecord, deleteRecord e updateRecord) já possuem segurança padrão definida com os nomes de Navigation, Create, Delete e Update.
Exemplo: Implementação de segurança para o método calculateOrder.
/* Definitions --- */
&GLOBAL-DEFINE DBOCustomFunctions calculateOrder
PROCEDURE calculateOrder:
{method/svc/permit/permit.i &Method="calculateOrder"}
... END PROCEDURE.
Quando é utilizado a segurança de métodos, deve-se preencher o preprocessador DBOCustomFunctions com os valores passados para o parâmetro &Method, separados por vírgula.
Além disso, a chamada ao include deve ser feita fora da transação do método, pois o include somente faz o cancelamento do método, através do comando RETURN "NOK":U.
E, ainda, pode-se utilizar os valores Navigation, Create, Delete ou Update para a passagem de parâmetro.
Redução do Tamanho do DBO
Para evitar que o programa fonte do DBO fique muito grande e gere um executável também grande. Deve-se procurar fazer algumas otimizações. Se tivermos um executável muito grande poderemos perder performance ao executar o programa (por causa do tempo de carga).
Uso de sub-programas
O método do DBO pode estar codificado em sub-programas evitando um fonte muito grande e reduzindo o executável. O analista deve levar em consideração também a necessidade de utilização do método. Por exemplo: o método createRecord deve ser bastante usado num programa de digitação. Ao contrário de um método de cálculo que deve ser utilizado em funções específicas.
Por exemplo: Um método de cálculo sem sub-programa: PROCEDURE calculateAlgumaCoisa:
FOR EACH table: ASSIGN ... END.
END PROCEDURE.
Utilizando sub-programa para otimizar o DBO: PROCEDURE calculateAlgumaCoisa: RUN xxbo/boxx999x.p (<parameters>). END PROCEDURE.
Assim, a lógica deste método deve estar neste sub-programa que somente deve ser carregado para memória quando o método for chamado evitando assim demora no tempo de carga do DBO;
O analista deve levar em consideração a passagem de parâmetros e que este sub-programa tenha código considerável para ser um sub-programa (como sugestão no mínimo 60 linhas);
Os sub-programas construídos devem seguir as regras existentes para DBOs, ou seja, devem receber parâmetros via temp-table e realizar o retorno de erros da mesma forma. Não podem usar variáveis globais e nem fazer interação com a tela.
Considerações Gerais
Recomenda-se que o nível de transação (Progress) fique limitado ao método. Cuidando-se para não transformar todo o DBO em uma única transação (afetando assim o escopo e bloqueio de registro).
Por exemplo: os métodos createRecord, updateRecord e deleteRecord
constituem-se em 3 (três) transações separadas. E seus métodos override estão neste mesma transação.
Controle de Transação
CAPÍTULO 3 Como construir o DBO 45
Alertamos para o fato de que as transações dos DBOs podem ser afetadas pelas transações dos programas que utilizam os DBOs.
As regras de negócio devem estar em DBOs e APIs. Não se deve utilizar Triggers para regras de negócio. Os Triggers podem vir a ser usados para processos técnicos, mas isto é uma definição da equipe de apoio ao desenvolvimento.
As regras de negócio existentes atualmente devem ser transferidos gradualmente para DBOs e APIs.
CAPÍTULO 4
Convertendo BO 1.1 para DBO 2.0
Este capítulo é destinado ao desenvolvedor que utiliza DBO 1.1 e deseja convertê-lo para DBO 2.0.
Para converter um DBO 1.1 devem ser seguidos os seguintes passos: Criar um novo DBO 2.0;
Transferir lógicas dos métodos básicos do DBO 1.1 para o DBO 2.0; Mas, antes de detalhar estes passos, é importante saber como fazer a relação entre os métodos antigos e os novos métodos.
A seguir são detalhados os passos:
Relação entre métodos
A maior parte dos métodos básicos possuem métodos que são correspondentes no novo DBO, sendo assim descreve-se a seguinte relação:
compareVersion -
Não há método correspondente no novo DBO; endMethod e startMethod -
Estes métodos eram utilizados para execução de EPCs, porém a execução de EPCs no novo DBO é feita de forma automática. Sendo assim, não há método correspondente no novo DBO;
findRowidShow -
Deve-se executar primeiramente o método emptyRowObject e logo após o método repositionRecord;