MILLENNIUM NETWORK Millennium SDK 2.0 (beta) Documentação Técnica (draft) 09/2013 Este documento contém as instruções para a utilização da API wtsDataSet que se presta à comunicação de aplicativos de terceiros com o servidor de aplicações Millennium. Conteúdo 1 Introdução ..................................................................................................................................... 3 2 Tipos de Chamadas ....................................................................................................................... 3 2.1 2.2 3 API Procedural ..............................................................................Erro! Indicador não definido. 2.1.1 Descrição ............................................................................................................... 3 2.1.2 Exemplos ............................................................................................................... 4 API de Entidades........................................................................................................................ 6 2.2.1 Introdução ............................................................................................................. 6 2.2.2 Sintaxe BNF simplificada da EQL ........................................................................... 9 2.2.3 Selecionando informações .................................................................................... 6 2.2.4 Agregações ............................................................................................................ 7 2.2.5 Restrições .............................................................................................................. 8 2.2.6 Parâmetros ............................................................................................................ 8 2.2.7 Exemplos ............................................................................................................... 8 API OData/REST ............................................................................................................................. 9 3.1 Configuração ............................................................................................................................. 9 3.2 Utilização ................................................................................................................................... 9 4 API WtsClientX............................................................................................................................. 11 4.1 Configuração ........................................................................................................................... 11 4.2 Interfaces................................................................................................................................. 11 4.3 4.2.1 IwtsDataSet ......................................................................................................... 11 4.2.2 IwtsCollection ...................................................................................................... 12 4.2.3 IwtsCollectionItem .............................................................................................. 12 Utilização ................................................................................................................................. 14 1 Introdução O Servidor de aplicação Millennium (wtsBroker) é um serviço que permite a invocação de procedimentos de negócios (chamados de transações) executando em um computador servidor por um programa client. Para permitir a chamada das transações expostas pelo servidor Millennium por qualquer linguagem de programação do Windows, a Millennium desenvolveu um componente ActiveX capaz de manipular e invocar as transações no servidor de aplicações por meio de TCP/IP utilizando um protocolo extremamente compacto e eficiente. A versão 2.0 do SDK possui também um gateway que permite a comunicação via OData/REST com o servidor. Este protocolo utiliza apenas padrões abertos da internet, permitindo a qualquer linguagem capaz de fazer chamadas HTTP acesso às funções do servidor. Para maior adequação às várias necessidades de integração, foram expostas duas formas comunicação com o servidor: i) Procedural, baseada em chamadas estruturadas ao servidor (RPC) e ii) Declarativa, baseada em uma linguagem de consultas que age sobre o universo de entidades do sistema. Pedido Cliente Codificação ‘ Itens SERVIDOR MILLENNIUM TCP/IP, HTTP Millenium.Pedido_Venda.Incluir Decodificação Select Cliente.Nome, List(Cliente.Enderecos .Logradouro) Invocação Validação EQL Serialização Decodificação Resultado (ou) Exceção TCP/IP, HTTP Codificação Figura 1: Esquema da comunicação Client/Server em 3 camadas do Millennium Os capítulos a seguir detalharão as formas de comunicação OData/REST e ActiveX com detalhes de configuração e exemplos em diversas linguagens. 2 Tipos de Chamadas 2.1 Métodos 2.1.1 Descrição O paradigma utilizado para programar junto ao servidor é próximo ao de stored procedures, já tradicionais nos bancos de dados relacionais. No Millennium, estas procedures se chamam métodos e são divididas por objetos (produtos, clientes, vendas etc). A implementação destes métodos reside em um servidor de aplicações, sendo que para o client, o que existe é apenas um documento que possui parâmetros e resultados. Uma característica dos métodos utilizados no Millennium é que eles suportam composição. Por exemplo, uma transação de “inclusão de pedido de venda” possui um campo “produtos”, que por sua vez é outra transação especial, chamada “registro”. Esta transação não pode ser invocada, funcionando apenas como estrutura de dados. Esta capacidade permite o preenchimento de todas as informações relevantes a um método de uma só vez, possibilitando sua execução em uma só chamada ao servidor. Esta chamada ao servidor Millennium é executada dentro de um mesmo contexto no banco de dados (transação), melhorando o desempenho e garantindo a consistência dos dados. Os parâmetros e resultados das transações podem ser inspecionados utilizando-se a ferramenta ieditor.exe (Editor de Transações) fornecida no pacote. É importante ressaltar que o catálogo de transações (*.wts) deve estar disponível tanto no client quanto no server envolvidos no processo como consta no capítulo 2.1 – Configuração. Geralmente é necessário requisitar esta biblioteca junto ao responsável pelo servidor, pois ela varia conforme a versão instalada do Millennium. 2.1.2 Exemplos Lista simples: Var o = new wtsDataSet(); o.Transaction = “millenium.bancos.lista”; o.Host = “localhost”; o.ApplyDefaults(); o.Refresh(); while (!o.Eof()) { Console.Print(o.Fields.ByName[“DESCRICAO”].Value); o.Next() } Incluir um pedido: o = CreateObject(“wtsclientx.wtsdataset”) o.Transaction = 'millenium.pedido_venda.inclui' ‘quando o parâmetro é do tipo “R”, seu ‘valor é um outro dataset aninhado, ou seja ‘um dataset “filho” ds = o.Params.ByName(“PRODUTOS”).Value() ds.New() ds.Fields.ByName(“PRODUTO”).Value = 9090 ds.Fields.ByName(“COR”).Value = 1 ds.Fields.ByName(“ESTAMPA”).Value = 1 ds.Fields.ByName(“TAMANHO”).Value = “P” ds.Fields.ByName(“QUANTIDADE”).Value = 20 ds.Add; ‘adiciona registro de produto em dataset filho ‘armazena o dataset filho no dataset pai o.Params.ByName(“PRODUTOS”).Value = ds ‘invoca o servidor de aplicação o.Refresh ‘lê o Id do pedido de venda inserido Debug.Print o.Fields.ByName(“PEDIDOV”).Value 2.2 Entidades 2.2.1 Introdução O acesso às informações do Millennium através da API procedural, além de ser de fácil compreensão e ter ótimo desempenho, organiza bastante o contrato entre cliente e servidor. Apesar disto, existem situações onde é necessária maior flexibilidade, principalmente nas consultas que na maioria dos casos não se beneficiam de uma interface padronizada, procedural. Para estas situações, foi desenvolvida a EQL (Entity Query Language), uma linguagem de consultas declarativa que expõe grande parte das entidades do Millennium de forma similar à popular linguagem SQL. Esta linguagem permite maior flexibilidade, já que não se baseia em um contrato pré-definido. Desta forma, é possível obter acesso às entidades do sistema sem que um método novo tenha que ser criado para isto. Atualmente, a EQL é restrita apenas à consulta de entidades. Para gravação, utilize a API procedural. 2.2.2 Selecionando informações A estrutura da EQL se baseia nos segmentos Select e Where da SQL. No segmento Select são listados os atributos das entidades que se deseja consultar na forma Entidade[{.Entidade},...].Atributo [as Alias]. Por convenção, os nomes das entidades são no singular. O aplicativo equery.exe permite a navegação pelas entidades e a criação de consultas que podem ser utilizadas posteriormente como argumento EQL. Para consultar o nome de um cliente, por exemplo, declara-se: Select Cliente.Nome as Nome Uma entidade relacionada também pode ser consultada de forma similar utilizando-se o ponto (.) para indicar o relacionamento. Assim é possível acessar a descrição da categoria de um produto da seguinte forma: Select Produto.Categoria.Descricao as DescCateg Uma diferença notória para o SQL é a ausência da cláusula FROM. Isto ocorre porque no EQL todas as ligações entre entidades são conhecidas pelo sistema, tornando a ligação explícita entre elas desnecessária. A possibilidade de acesso a relacionamentos de forma explícita, como no exemplo acima, também torna possível obter controle sobre o contexto da consulta sem a cláusula FROM. Se a ligação entre as entidades não for encontrada, um erro será gerado. Existem casos onde a ligação entre entidades é inferida pelo sistema dependendo dos elementos envolvidos. Veja alguns exemplos: Esta consulta gera um erro, pois Cliente e Representante não são diretamente relacionados. Select Cliente.Nome, Representante.Nome Esta consulta não gera erro, porque o contexto do pedido de venda é utilizada para ligar cliente e fornecedor no pedido. Select PedidoVenda.CodPedidoV, Cliente.Nome, Representante.Nome Esta consulta também não gera erro, porque Cliente.Representante é o representante padrão do cliente e está sendo indicado explicitamente. Select Cliente.Nome, Cliente.Representante.Nome Existem situações, onde se deseja consultar entidades e suas devidas coleções como, por exemplo, um produto e suas cores disponíveis. Na EQL, uma coleção é retornada como um “Record” do modelo procedural, ou seja o campo Cores do DataSet (IwtsDataSet) retornado pode ser lido como um outro DataSet (IwtsSimpleDataSet) que possui uma lista de cores. Isto pode ser feito através da expressão: Select Produto.Descricao as DescProd, List(Produto.Cores.Descricao as DescCor) as Cores 2.2.3 Agregações As agregações também funcionam de maneira semelhante ao SQL, suportando as funções Sum, Min, Max, Avg, Count e CountD. A função CountD permite a contagem distinta de itens, de forma similar ao Count(Distinct <Field>) do SQL. Apesar da sintaxe compatível, as agregações em EQL possuem capacidades avançadas, baseadas na modelagem multidimensional utilizada por DataWarehouses. Estas capacidades estão fora do escopo desta documentação, mas é importante destacar a diferença de comportamento em relação à resolução de agregados. No SQL, quando uma agregação é feita, esta se aplica ao conjunto de tabelas relacionado na cláusula “From”. Desta maneira, se torna difícil a confecção de consultas que busquem informações relacionadas indiretamente como na pseudo-consulta abaixo: Obtenha a quantidade pedida e o estoque atual dos produtos da categoria “Calça” A única forma de responder esta consulta seria fazer uma consulta das quantidades pedida das calças e outra das quantidades de estoque das mesmas. Também poderia ser utilizada a capacidade de “subselect” do SQL, porém com perda significativa de desempenho, já que “subselects” relacionados não podem ser executados de uma só vez: Select p.Descricao, Sum(pe.Quantidade) as qtPedida, (select Sum(Saldo) from Estoques e Where e.Produto=p.Produto) as qtEstoque From Produtos p Inner Join Pedidos PE on p.Produto=PE.Produto Inner Join Categorias c on p.Categoria=c.Categoria Where c.Nome = “Calça” Na EQL, a mesma consulta pode ser expressa da seguinte forma: Select Produto.Descricao, Sum(Pedido.Quantidade) as qtPedida, Sum(Estoque.SaldoAtual) as qtEstoque Where Produto.Categoria.Nome = “Calça” A diferença nos mecanismos, é que o EQL “entende” que Pedido e Estoque são “fatos” e que Produto é uma “dimensão”, ou seja produto classifica registros de pedido e estoque. Quando a consulta é executada, são geradas consultas independentes ao banco de dados que depois são alinhadas, trazendo o resultado correto. Na verdade, as expressões agregadas EQL possuem várias outras capacidades que são mais utilizadas em ferramentas de BI (Business Intelligence) e estão fora do escopo da explanação da API de desenvolvimento. 2.2.4 Restrições Para a aplicação de restrições, deve-se utilizar a cláusula Where após a cláusula Select, de forma similar ao SQL. As expressões de comparação padrão =, <, >, >=, <= e <> podem ser utilizadas, além do operadores IN LIST() / NOT IN LIST() para coleções. Um exemplo de uso deste tipo de restrição segue abaixo, onde são listados os produtos que possuem cores inativas: Select Produto.Descricao where Produto.Produto in List(Produto.Cores.Ativo= ‘I’) A EQL se diferencia do SQL no que trata a restrições sobre agregações, pois não é necessária a separação entre WHERE e HAVING. Uma restrição agregada pode ser aplicada na própria cláusula WHERE desde que não esteja combinada com uma restrição lógica OU com um atributo: Permitido: Select Produto.Descricao where Sum(Venda.Total)>1000 Select Produto.Descricao where (Sum(Venda.Total)>1000) or (Sum(Estoque.SaldoAtual)<20) Não permitido: Select Produto.Descricao where (Sum(Venda.Total)>1000) or (Produto.Codigo= “000”) 2.2.5 Parâmetros É possível a passagem de parâmetros para a EQL sem o uso de literais na linha de comando. Para isto, é utilizada a função Prompt: Prompt(nome, tamanho,tipo de dados:[String,Integer,Float,Boolean]) Quando um parâmetro é utilizado, este fica acessível na coleção Params do DataSet, podendo ser preenchido sem a posterior alteração do comando EQL. 2.2.6 Exemplos Lista o Logradouro do primeiro Endereço de cada Filial: ‘Note que a classe é wtsmddataset o = CreateObject(“wtsclientx.wtsmddataset”) ‘A Transaction é um comando EQL o.Transaction = “select filial.nome as n,”_ & “list(filial.enderecos.logradouro as l) as e” ‘Indicação do servidor o.Host = “localhost” ‘Busca informações o.Refresh ‘Lê cada registro retornado While not o.Eof ‘Imprime o nome da filial Debug.Print “Filial:” & o.Fields.ByName(“n”).Value ‘Note que o campo “e” possui outro dataset para cada registro Set d = o.Fields.ByName(“e”).ValueAsDataSet While not d.Eof Debug.Print “Endereço:” & d.Fields.ByName(“l”).Value d.Next Wend o.Next Wend 2.2.7 Sintaxe BNF simplificada da EQL SelectClause ::= “Select” {SelectMember} Where <Condition>; EntityMember ::= Entity, {“.”,<Entity>}, “.”, Attribute; SelectMember ::= EntityMember, [“as”, Alias]; Constant ::= String, Number; Value ::= EntityMember | Constant; ComparisonOp ::= “>”, “<”, “>=”, “<=”, “<>”, “in”; Condition ::= EntityMember, ComparisonOp, Value; 3 API OData/REST 3.1 Configuração 3.2 Utilização A forma pela qual as chamadas HTTP serão feitas para o uso desta API varia muito de linguagem para linguagem, assim as instruções a seguir serão baseadas apenas no formato das chamadas, sem se preocupar com a forma que elas serão feitas pelas linguagens e frameworks de programação. 3.2.1 Operações de leitura As operações de leitura podem ser feitas por meio de chamadas a métodos do Millennium ou a operações sobre entidades. Cada entidade do Millennium possui operações padrão para CRUD, ou seja, para as operações básicas de inclusão, alteração, consulta e exclusão. Estas operações são tratadas de forma transparente pela API OData/REST para que sejam mapeadas em verbos HTTP. Os verbos HTTP podem ser POST (inclusão), PUT (alteração total), MERGE (alteração parcial), GET (leitura) ou DELETE (exclusão). Para ler uma entidade, basta fazer uma chamada http GET: GET https://<host>/api/millenium/<entidade>(<chave>)?$format=json|xml A <entidade> é a entidade a ser lida e a <chave> neste caso será seu identificador. O formato de retorno pode ser controlado pela opção $format que pode ser JSON ou XML. O formato JSON é mais compacto e simples, retornando cada campo da entidade como um par com atributo e valor. Os atributos podem ser valores, objetos ou arrays. A leitura da entidade cliente identificada pelo número 10 em json, por exemplo, seria feita da seguinte forma: GET https://<host>/api/millenium/clientes(10)?$format=json {“d”:[{ “cliente”:10, “cod_cliente”: “000010”, “data_cadastro”: “Date(452345343453)”, “geradores”: [{“nome”: “CLIENTE DE TESTE”, “enderecos”:[{“endereco”:0, “cod_endereco”: “01”, “logradouro”: “RUA DE TESTE”}, {“endereco”:1, “cod_endereco”: “02”, “logradouro”: “RUA DE TESTE 2”}] }] }]} Nas respostas JSON sempre há um array principal, chamado “d” com uma lista de registros filhos, mesmo quando só há um registro, como neste caso. Cada item do array principal é um objeto contendo os atributos do registro principal no formato especificado pela convenção JSON. Esta especificação dita algumas normas que são seguidas pela API: Todos os atributos string usam códigos de escape apropriados para retorno de caracteres reservados como quebras de linha, por exemplo. A codificação utilizada é a UTF8, garantindo também que todos os textos acentuados funcionem corretamente. O formato de data Os campos de data são codificados utilizando-se o número de milissegundos a partir do unix epoch (1/1/1970) envolvidos pela função Date(). Por ser um padrão, qualquer biblioteca JSON será capaz de decodificar os resultados e transformá-los em objetos nativos de sua linguagem, portanto não é recomendável tentar tratar os resultados manualmente. Junto à distribuição da API existem exemplos em diversas linguagens de como fazer as chamadas e a decodificação do resultado. 4 API WtsClientX 4.1 Configuração A API para acesso ao Servidor de Aplicações Millennium é implementada pela biblioteca wtsclientx.dll. Por ser baseada na tecnologia COM (Component Object Model), esta deve ser registrada com o programa RegSvr32.exe fornecida com o Windows™. É necessário também que o catálogo de transações que será invocado no servidor esteja acessível. Para isto, deve-se manter o arquivo *.wts no diretório da aplicação ou em qualquer diretório acessível pela variável de ambiente PATH do Windows™. 4.2 Interfaces 4.2.1 IwtsDataSet Classe: IwtsDataSet Esta interface representa uma transação existente no Editor de Transações. Possibilita o preenchimento dos parâmetros necessários, assim como a invocação e leitura dos resultados da transação. Nome Descrição function Eof: WordBool Determina se o dataset está após seu último registro procedure Next Avança um registro no cursor interno do dataset procedure Refresh Método que invoca o servidor enviando os parâmetros contidos na propriedade Params e retornando o resultado que pode ser lido por meio da propriedade Fields. procedure SaveToFile(const FileName: WideString) Salva o conteúdo do dataset e seus parâmetros em um arquivo procedure LoadFromFile(const FileName: WideString) Recupera o conteúdo do dataset e seus parâmetros de um arquivo procedure ApplyDefaults Aplica os defaults especificados nos parâmetros da transação nos parâmetros do dataset property Transaction: WideString Lê e grava o nome da transação que será invocada no servidor de aplicações quando o método Refresh for chamado property Fields: IwtsCollection Lista de campos disponíveis da transação property Params: IwtsCollection Lista de parâmetros disponíveis da transação property RecordCount: Integer Número de registros disponíveis para leitura 4.2.2 IwtsCollection Interface: IwtsCollection Esta interface representa a lista de campos (Fields) e parâmetros (Params) da transação. Cada item da lista pode ser acessado por índice ou nome. Nome Descrição function Count: Integer Determina o número de elementos da lista procedure SaveToFile(const FileName: WideString) Salva os valores dos itens da lista em um arquivo procedure LoadFromFile(const FileName: WideString) Recupera os valores dos itens da lista de um arquivo property ByName[const Name: WideString]: IwtsCollectionItem Acessa os itens da lista pelo nome property Items[Index: Integer]: IwtsCollectionItem Acessa os itens da lista por seu índice property Ordered: WordBool Determina se o conteúdo da lista será ordenado segundo a declaração na transação ou na ordem utilizada para a interface com o usuário 4.2.3 IwtsCollectionItem Interface: IwtsCollectionItem Representa um item dos campos ou parâmetros da transação. Sua propriedade principal é a “value”, que possui o valor atual do item. No caso de um “Field”, este valor muda, conforme o dataset é movimentado. Nome Descrição property Value: OleVariant Valor atual deste item (pode ser Params[] ou Fields[]) property ValueAsJPEG: OleVariant Lê o conteúdo do campo como um array de bytes codificado como uma imagem JPEG property ValueAsDataSet: IwtsSimpleDataSet Lê o conteúdo do campo como um DataSet aninhado Metadatados property Name: WideString Nome do item property Size: Integer Tamanho máximo do valor do item (aplicável principalmente a campos “string”) property Format: WideString Formato do valor do item. Pode ter os valores: A: Texto B: Booleano N: Numérico M: Monetário D: Data H: Data e Hora T: Tempo I: Imagem R: Recordset property Visible: WordBool Determina se o item deve ser visível para o usuário property NotNull: WordBool Determina se o item é obrigatório property FieldLabel: WideString Determina o texto que será utilizado para rotular o item para o usuário property Lookup: WideString Nome da transação utilizada para busca de lista de valores utilizados para preenchimento do campo property LookupKeyField: WideString Campo chave da lista utilizado para preenchimento do valor do item property LookupDisplayCode: WideString Campo que deverá ser utilizado como chave na apresentação da lista ao usuário property LookupDisplay: WideString Campo que deverá ser utilizado como decrição na apresentação da lista ao usuário property Style: TInputStyle Estilo que deve ser utilizado para apresentação do item ao usuário: stDefault: O estilo é determinado pelo tipo do campo; stPopupRowset: Sub-dataset que é apresentado como uma tela popup; stInplaceRowset: Sub-dataset que é apresentado inline com os outros itens; stGridRowset: Sub-dataset que é apresentado inline com os outros itens em forma de tabela; stCheckList: Lista de itens apresentados como checkbox de múltipla seleção stFilteredLookup: Lista de valores filtrados property IsCustomLookup: WordBool Determina se o item possui um lookup customizado property Ambient: WordBool Determina se o valor do item será preenchido automaticamente baseado nos valores dos campos da transação 4.3 Utilização O paradigma de acesso a dados utilizado pela API WtsClientX segue o padrão de programação DataSet, onde as informações são expostas como Tabelas, Campos e Registros. Existem duas classes principais que possuem interfaces idênticas: wtsDataSet e wtsMDDataSet. Ambas possuem exatamente as mesmas propriedades e métodos, contudo a propriedade Transaction de data uma recebe comandos distintos. No caso no wtsDataSet, esta propriedade recebe o nome de um método, ou seja um procedimento pré-definido que será invocado no servidor de aplicações. Já na classe wtsMDDataSet, esta propriedade recebe um comando EQL (Entity Query Language) que também será executado no servidor de aplicações. As duas formas de comandos serão explicadas posteriormente neste documento. Depois de informado o comando por meio da propriedade Transaction, deve indicar o servidor que será invocado. Isto se faz por meio da propriedade Host. O próximo passo é preencher algum parâmetro, se for o caso, na propriedade Params[]. Se for o caso, pode-se preencher estes parâmetros com os “defaults” do sistema por meio do método ApplyDefaults(). Para invocar o servidor, utiliza-se o método Refresh() e para ler seu resultado a propriedade Fields[]. Existem também as propriedades EOF para testar se o registro corrente é o posterior ao final e o método Next() para movimentar-se adiante. O acesso aos campos pode ser feito por posição [Fields[0].Value (Delphi, C#), Fields(0).Value (VB)] ou por nome [Fields.ByName[‘Nome’].Value (Delphi, C#), Fields.ByName(“Nome”).Value (VB)] que é o mais recomendável. É possível também tratar DataSets aninhados em campos por meio do método ValueAsDataSet da propriedade Fields[]. No capítulo 5, constam as referências completas das classes da biblioteca.