
A terceira e última parte de uma série de artigos sobre a linguagem lsFusion (links para a
primeira e a
segunda partes)
Ele se concentrará no modelo físico: tudo o que não está conectado à funcionalidade do sistema, mas está associado ao seu desenvolvimento e otimização de desempenho, quando houver muitos dados.
Este artigo, como os anteriores, não é muito adequado para leitura divertida, mas, ao contrário dos outros, haverá mais detalhes técnicos e tópicos "quentes" (como digitação ou metaprogramação), além disso, este artigo fornecerá parte das respostas à pergunta, como é tudo trabalha dentro.
Neste artigo, ficaremos sem uma foto (não há pilha como essa), mas faremos um índice, conforme solicitado nos artigos anteriores:
Identificação do item
Se o projeto consiste em vários arquivos pequenos, geralmente não surgem problemas com a nomeação de elementos. Todos os nomes estão à vista e são fáceis o suficiente para garantir que eles não se sobreponham. Se o projeto, pelo contrário, consiste em muitos módulos desenvolvidos por um grande número de pessoas diferentes, e abstrações nesses módulos de um domínio de domínio, conflitos de nome se tornam muito mais prováveis. O LsFusion possui dois mecanismos para resolver esses problemas:
- Namespaces - separação de um nome em completo e abreviado, e a capacidade de usar apenas um nome abreviado ao acessar um elemento
- Digitação explícita (para ser mais preciso, sobrecarga de função) - a capacidade de nomear propriedades (e ações) da mesma maneira e, ao acessá-las, dependendo das classes de argumentos, determina automaticamente qual propriedade a chamada deve ser
Namespaces
Qualquer projeto complexo geralmente consiste em um grande número de elementos que devem ser nomeados. E, se os domínios do domínio se cruzarem, muitas vezes haverá a necessidade de usar o mesmo nome em contextos diferentes. Por exemplo, temos o nome de uma classe ou formulário Fatura (fatura) e queremos usar esse nome em vários blocos de funções, por exemplo: Compra (Compra), Venda (Venda), Retorno de compra (PurchaseReturn), Retorno de venda (SaleReturn). É claro que as classes / formulários podem ser chamados de PurchaseInvoice, SaleInvoice e assim por diante. Mas, primeiro, esses nomes em si mesmos serão muito volumosos. Em segundo lugar, em um bloco funcional, as chamadas costumam ir aos elementos do mesmo bloco funcional, o que significa que, ao desenvolver, por exemplo, o bloco funcional de Compra a partir de uma constante repetição da palavra Compra, ele simplesmente ondula seus olhos. Para impedir que isso aconteça, a plataforma tem um conceito como um espaço para nome. Funciona da seguinte maneira:
- todos os elementos da plataforma são criados em algum espaço para nome
- se no processo de criação de um elemento, outros elementos forem referenciados, os elementos criados no mesmo espaço para nome terão precedência
Os espaços para nome na versão atual do idioma são definidos para o módulo inteiro imediatamente no cabeçalho do módulo. Por padrão, se nenhum espaço para nome for especificado, ele será criado implicitamente com um nome igual ao nome do módulo. Se você precisar acessar um elemento de um espaço para nome não prioritário, poderá fazer isso especificando o nome completo do elemento (por exemplo, Sale.Invoice).
Digitação explícita
Os espaços para nome são importantes, mas não a única maneira de tornar o código mais curto e mais legível. Além deles, ao procurar propriedades (e ações), também é possível levar em consideração as classes de argumentos passadas a eles na entrada. Então, por exemplo:
Aqui, é claro, a questão pode surgir: o que acontecerá se o espaço para nome da propriedade desejada não for uma prioridade, mas for mais adequado para as classes? De fato, o algoritmo de busca geral é bastante complicado (sua descrição completa está
aqui ) e existem muitos casos "ambíguos"; portanto, em caso de incerteza, é recomendável especificar espaços de nomes / classes da propriedade desejada explicitamente ou verificar duas vezes o IDE (usando Ir para a declaração - CTRL + B) que a propriedade encontrada é exatamente o que se quis dizer.
Além disso, vale a pena notar que a digitação explícita em lsFusion geralmente não é necessária. As classes de parâmetro podem ser omitidas e, se a plataforma tiver informações suficientes para encontrar a propriedade desejada, ela o fará. Por outro lado, em projetos realmente complexos, ainda é recomendável definir classes de parâmetros explicitamente, não apenas do ponto de vista da brevidade do código, mas também do ponto de vista de vários recursos adicionais, como: diagnóstico precoce de erros, autocompletar inteligente a partir do IDE e assim por diante. Tivemos uma vasta experiência trabalhando tanto com digitação implícita (nos primeiros 5 anos) quanto com explícita (tempo restante), e devo dizer que os tempos de digitação implícita agora são lembrados com um calafrio (embora possa apenas “não sabíamos como cozinhar”).
Modularidade
A modularidade é uma das propriedades mais importantes do sistema, permitindo garantir sua extensibilidade, reutilização de código e interação efetiva da equipe de desenvolvimento.
LsFusion fornece modularidade com os dois mecanismos a seguir:
- Extensões - a capacidade de expandir (alterar) os elementos do sistema depois que eles são criados.
- Módulos - a capacidade de agrupar algumas funcionalidades para posterior reutilização.
Extensões
O lsFusion suporta a capacidade de estender classes e formas, bem como propriedades e ações, através do mecanismo de polimorfismo descrito no primeiro artigo.
Além disso, observamos que quase todos os outros designs de plataforma (por exemplo, navegador, design de formulário) são extensíveis por definição, portanto, não há uma lógica de extensão separada para eles.
Módulos
Um módulo é uma parte funcionalmente completa de um projeto. Na versão atual do lsFusion, um módulo é um arquivo separado que consiste no cabeçalho e no corpo de um módulo. O cabeçalho do módulo, por sua vez, consiste em: o nome do módulo, bem como, se necessário, uma lista de módulos usados e o espaço para nome do nome deste módulo. O corpo do módulo consiste em declarações e / ou extensões de elementos do sistema: propriedades, ações, restrições, formulários, metacódigos e assim por diante.
Normalmente, os módulos usam elementos de outros módulos para declarar seus próprios / expandir elementos existentes. Por conseguinte, se o módulo B utiliza elementos do módulo A, é necessário indicar no módulo B que depende de A.
Com base em suas dependências, todos os módulos do projeto são organizados em uma determinada ordem na qual eles são inicializados (essa ordem desempenha um papel importante ao usar o mecanismo de extensão mencionado anteriormente). É garantido que, se o módulo B depender do módulo A, a inicialização do módulo A ocorrerá antes da inicialização do módulo B. Não são permitidas dependências cíclicas entre os módulos no projeto.
Dependências entre módulos são transitivas. Ou seja, se o módulo C depende do módulo B e o módulo B depende do módulo A, considera-se que o módulo C também depende do módulo A.
Qualquer módulo sempre depende automaticamente do sistema do módulo do sistema, independentemente de ser indicado explicitamente ou não.
Metaprogramação
Metaprogramação é um tipo de programação associada à gravação de código de programa, que, como resultado, gera outro código de programa. LsFusion usa os chamados metacódigos para metaprogramação.
O metacódigo consiste em:
- nome do metacódigo
- parâmetros de metacódigo
- corpo de um metacódigo - um bloco de código que consiste em declarações e / ou extensões de elementos do sistema (propriedades, ações, eventos, outros metacódigos, etc.)
Portanto, antes de iniciar o processamento principal do código, a plataforma o prepara - substitui todos os usos de metacódigos pelos corpos desses metacódigos. Nesse caso, todos os parâmetros de metacode usados em literais de identificadores / string são substituídos pelos argumentos passados para esse metacode:
Anúncio:
Uso:
Código resultante:
Além de simplesmente substituir os parâmetros de metacódigo, a plataforma também permite combinar esses parâmetros com identificadores / literais de strings existentes (ou entre si), por exemplo:
Anúncio:
Uso:
Código resultante:
Os metacódigos são muito semelhantes às macros em C, mas, diferentemente do último, eles não funcionam no nível do texto (não podem, por exemplo, passar palavras-chave no parâmetro), mas apenas no nível dos identificadores / literais de string (essa restrição, em particular, permite analisando o corpo do metacódigo no IDE).
No lsFusion, os metacódigos resolvem problemas semelhantes aos genéricos em Java (passando classes como parâmetros) e lambda no FP (passando funções como parâmetros), no entanto, eles não fazem isso muito bem. Mas, por outro lado, eles fazem isso em um caso muito mais geral (ou seja, por exemplo, com a possibilidade de combinar identificadores, usar em qualquer construção sintática - formas, projetos, navegador etc.)
Observe que a "implantação" de metacódigos é suportada não apenas na própria plataforma, mas também no IDE. Portanto, no IDE, existe um modo especial, meta meta, que gera o código resultante diretamente nas fontes e, assim, permite que esse código gerado participe da pesquisa de usos, preenchimento automático etc. Nesse caso, se o corpo do metacódigo for alterado, o IDE atualizará automaticamente todos os usos desse metacódigo.

Além disso, os metacódigos podem ser usados não apenas para geração automática, mas também para geração manual de código (como modelos). Para fazer isso, basta escrever @@ em vez de um @ e imediatamente após a sequência de uso do metacódigo ter sido completamente inserida (até o ponto e vírgula), o IDE substituirá esse uso de metacode pelo código gerado por esse metacode:

Integração
A integração inclui tudo relacionado à interação do sistema lsFusion com outros sistemas. Do ponto de vista da direção dessa interação, a integração pode ser dividida em:
- Acessando o sistema lsFusion de outro sistema.
- Acesso do sistema lsFusion para outro sistema.
Do ponto de vista do modelo físico, a integração pode ser dividida em:
- Interação com sistemas em execução no “mesmo ambiente” que o sistema lsFusion (ou seja, na Java virtual machine (JVM) do servidor lsFusion e / ou usando o mesmo servidor SQL que o sistema lsFusion).
- Interação com sistemas remotos através de protocolos de rede.
Assim, os primeiros sistemas serão chamados internos, o segundo - externo.
Portanto, existem quatro tipos diferentes de integração na plataforma:
- Apelação para um sistema externo
- Apelação de um sistema externo
- Apelo ao sistema interno
- Apelação do sistema interno
Apelação para um sistema externo
O acesso a sistemas externos no lsFusion na maioria dos casos é implementado usando o operador EXTERNO especial. Este operador executa o código fornecido na linguagem / no paradigma do sistema externo especificado. Além disso, esse operador permite transferir objetos de tipos primitivos como parâmetros de uma chamada, além de gravar os resultados da chamada nas propriedades especificadas (sem parâmetros).
Atualmente, a plataforma suporta os seguintes tipos de interações / sistemas externos:
HTTP - Executa uma solicitação http de um servidor da web.Para esse tipo de interação, você deve especificar uma string de consulta (URL), que determina simultaneamente o endereço do servidor e a solicitação que precisa ser executada. Os parâmetros podem ser transferidos tanto na linha de consulta (para acessar o parâmetro, são utilizados o caractere especial $ e o número desse parâmetro, começando em 1), quanto em seu corpo (BODY). Supõe-se que todos os parâmetros não utilizados na string de consulta sejam passados para BODY. Se houver mais de um parâmetro no BODY, o tipo de conteúdo BODY durante a transmissão é definido como multipart / misto e os parâmetros são transferidos como componentes deste BODY.
Ao processar parâmetros de classes de arquivo (FILE, PDFFILE etc.) em BODY, o tipo de conteúdo do parâmetro é determinado dependendo da extensão do arquivo (de acordo com a
tabela a seguir). Se a extensão do arquivo não estiver nesta tabela, o tipo de conteúdo será definido como application / <file extension>.
Se necessário, usando a opção especial (HEADERS), você pode definir os cabeçalhos da solicitação executada. Para fazer isso, você precisa especificar uma propriedade com exatamente um parâmetro da classe de string na qual o título será armazenado e o valor da classe de string na qual o valor desse cabeçalho será armazenado.
O resultado da solicitação http é processado da mesma maneira que seus parâmetros, apenas na direção oposta: por exemplo, se o tipo de conteúdo do resultado estiver presente na
tabela a seguir ou for igual a application / *, será considerado que o resultado obtido é um arquivo e deve ser gravado em uma propriedade com o valor FILE . Os cabeçalhos do resultado da solicitação http são processados por analogia com os cabeçalhos dessa solicitação (com a única diferença de que a opção é chamada HEADERSTO, não HEADERS).
SQL - executando um comando do servidor SQL.Para esse tipo de interação, a cadeia de conexão e os comandos SQL a serem executados são especificados. Os parâmetros podem ser passados na cadeia de conexão e no comando SQL. Para acessar o parâmetro, o caractere especial $ e o número desse parâmetro são usados (a partir de 1).
Parâmetros de classes de arquivo (FILE, PDFFILE etc.) podem ser usados apenas no comando SQL. Além disso, se algum dos parâmetros durante a execução for um arquivo TABLE (TABLEFILE ou FILE com a extensão da tabela), esse parâmetro também será considerado uma tabela neste caso:
- Antes de executar o comando SQL, o valor de cada parâmetro é carregado no servidor em uma tabela temporária
- ao substituir parâmetros, não é o próprio valor do parâmetro que é substituído, mas o nome da tabela temporária criada
Os resultados da execução são: para consultas DML - números iguais ao número de registros processados, para consultas SELECT - arquivos no formato TABLE (FILE com a extensão da tabela) contendo os resultados dessas consultas. A ordem desses resultados coincide com a ordem de execução das consultas correspondentes no comando SQL.
LSF - uma chamada de ação de outro servidor lsFusion.Para esse tipo de interação, a cadeia de conexão com o servidor lsFusion (ou seu servidor da Web, se houver) é configurada, a ação a ser executada, bem como uma lista de propriedades (sem parâmetros), nos valores nos quais os resultados da chamada serão gravados. Os parâmetros a serem transferidos devem coincidir em número e classe com os parâmetros da ação que está sendo executada.
O método de definir a ação nesse tipo de interação é totalmente consistente com o método de definir a ação ao acessar de um sistema externo (sobre esse tipo de acesso na próxima seção).
Por padrão, esse tipo de interação é implementado usando o protocolo HTTP usando as interfaces apropriadas para acessar de / para um sistema externo.
No caso de você precisar acessar o sistema usando um protocolo diferente do acima, você sempre poderá fazer isso criando uma ação em Java e implementando essa chamada lá (mas mais sobre isso posteriormente na seção “Acessando sistemas internos”)
Apelação de um sistema externo
A plataforma permite que sistemas externos acessem o sistema desenvolvido no lsFusion usando o protocolo de rede HTTP. A interface dessa interação é chamar alguma ação com os parâmetros fornecidos e, se necessário, retornar os valores de algumas propriedades (sem parâmetros) como resultados. Supõe-se que todos os objetos de parâmetros e resultados sejam objetos de tipos primitivos.
A ação chamada pode ser definida de uma de três maneiras:
- / exec? action = <nome da ação> - define o nome da ação chamada.
- / eval? script = <code> - define o código em lsFusion. Supõe-se que neste código exista uma declaração de uma ação com o nome run, é essa ação que será chamada. Se o parâmetro de script não for especificado, presume-se que o código seja passado como o primeiro parâmetro BODY.
- / eval / action? script = <código de ação> - define o código de ação em lsFusion. Para acessar os parâmetros, você pode usar o caractere especial $ e o número do parâmetro (começando em 1).
No segundo e terceiro casos, se o parâmetro de script não for especificado, presume-se que o código seja passado pelo primeiro parâmetro BODY.
O processamento de parâmetros e resultados é simétrico ao acesso a sistemas externos usando o protocolo HTTP (com a única diferença que os parâmetros são processados como resultados e, pelo contrário, os resultados são processados como parâmetros), para que não nos repetamos muito.
Por exemplo, se tivermos uma ação:
Em seguida, você pode acessá-lo usando uma solicitação POST que:
- URL - http: // endereço_do_servidor / exec? Action = importOrder & p = 123 & p = 2019-01-01
- Arquivo json com seqüências de caracteres de consulta
Exemplo de chamada em Python import json import requests from requests_toolbelt.multipart import decoder lsfCode = ("run(INTEGER no, DATE date, FILE detail) {\n" " NEW o = FOrder {\n" " no(o) <- no;\n" " date(o) <- date;\n" " LOCAL detailId = INTEGER (INTEGER);\n" " LOCAL detailQuantity = INTEGER (INTEGER);\n" " IMPORT JSON FROM detail TO detailId, detailQuantity;\n" " FOR imported(INTEGER i) DO {\n" " NEW od = FOrderDetail {\n" " id(od) <- detailId(i);\n" " quantity(od) <- detailQuantity(i);\n" " price(od) <- 5;\n" " order(od) <- o;\n" " }\n" " }\n" " APPLY;\n" " EXPORT JSON FROM price = price(FOrderDetail od), id = id(od) WHERE order(od) == o;\n" " EXPORT FROM orderPrice(o), exportFile();\n" " }\n" "}") order_no = 354 order_date = '10.10.2017' order_details = [dict(id=1, quantity=10), dict(id=2, quantity=15), dict(id=5, quantity=4), dict(id=10, quantity=18), dict(id=11, quantity=1), dict(id=12, quantity=3)] order_json = json.dumps(order_details) url = 'http://localhost:7651/eval' payload = {'script': lsfCode, 'no': str(order_no), 'date': order_date, 'detail': ('order.json', order_json, 'text/json')} response = requests.post(url, files=payload) multipart_data = decoder.MultipartDecoder.from_response(response) sum_part, json_part = multipart_data.parts sum = int(sum_part.text) data = json.loads(json_part.text)
Apelo ao sistema interno
Existem dois tipos de interação interna:
Interoperabilidade JavaEsse tipo de interação permite chamar o código Java dentro do servidor JVM lsFusion. Para fazer isso, você deve:
- certifique-se de que a classe Java compilada esteja acessível no caminho de classe do servidor de aplicativos. Também é necessário que essa classe herda lsfusion.server.physics.dev.integration.internal.to.InternalAction.
Exemplo de classe Java import lsfusion.server.data.sql.exception.SQLHandledException; import lsfusion.server.language.ScriptingErrorLog; import lsfusion.server.language.ScriptingLogicsModule; import lsfusion.server.logics.action.controller.context.ExecutionContext; import lsfusion.server.logics.classes.ValueClass; import lsfusion.server.logics.property.classes.ClassPropertyInterface; import lsfusion.server.physics.dev.integration.internal.to.InternalAction; import java.math.BigInteger; import java.sql.SQLException; public class CalculateGCD extends InternalAction { public CalculateGCD(ScriptingLogicsModule LM, ValueClass... classes) { super(LM, classes); } @Override protected void executeInternal(ExecutionContext<ClassPropertyInterface> context) throws SQLException, SQLHandledException { BigInteger b1 = BigInteger.valueOf((Integer)getParam(0, context)); BigInteger b2 = BigInteger.valueOf((Integer)getParam(1, context)); BigInteger gcd = b1.gcd(b2); try { findProperty("gcd[]").change(gcd.intValue(), context); } catch (ScriptingErrorLog.SemanticErrorException ignored) { } } }
- registrar uma ação usando o operador de chamada interna especial (INTERNO)
- uma ação registrada, como qualquer outra, pode ser chamada usando o operador de chamada. Nesse caso, o método executeInternal (lsfusion.server.logics.action.controller.context.ExecutionContext context) da classe Java especificada será executado.
Interação com SQLEsse tipo de interação permite acessar as construções de objetos / sintaxe do servidor SQL usado pelo sistema lsFusion desenvolvido. Para implementar esse tipo de interação na plataforma, um operador especial é usado - FORMULA. Este operador permite criar uma propriedade que avalia alguma fórmula na linguagem SQL. A fórmula é definida na forma de uma cadeia dentro da qual o caractere especial $ e o número deste parâmetro são usados para acessar o parâmetro (começando em 1). Por conseguinte, o número de parâmetros da propriedade obtida será igual ao máximo dos números dos parâmetros utilizados.
É recomendável usar esse operador apenas nos casos em que a tarefa não pode ser resolvida com a ajuda de outros operadores, bem como se é garantido quais servidores SQL específicos podem ser usados ou as construções de sintaxe usadas estão em conformidade com um dos mais recentes padrões SQL.
Apelação do sistema interno
Tudo é simétrico ao apelo ao sistema interno. Existem dois tipos de interação:
Interoperabilidade JavaDentro da estrutura desse tipo de interação, o sistema interno pode acessar diretamente os elementos Java do sistema lsFusion (como objetos Java comuns). Assim, você pode executar as mesmas operações que o uso de protocolos de rede, mas ao mesmo tempo evitar sobrecarga significativa dessa interação (por exemplo, serialização de parâmetros / desserialização do resultado, etc.). Além disso, esse método de comunicação é muito mais conveniente e eficiente se a interação for muito próxima (ou seja, durante a execução de uma operação, é necessário contato constante em ambas as direções - do sistema lsFusion para outro sistema e vice-versa) e / ou requer acesso a nós específicos da plataforma.
, Java- lsFusion- , , Java . :
- lsFusion ( ), « » , « » ( lsfusion.server.physics.dev.integration.internal.to.InternalAction, , , ).
- , lsFusion , Spring bean', - , dependency injection ( bean businessLogics).
Java- import lsfusion.server.data.sql.exception.SQLHandledException; import lsfusion.server.data.value.DataObject; import lsfusion.server.language.ScriptingErrorLog; import lsfusion.server.language.ScriptingLogicsModule; import lsfusion.server.logics.action.controller.context.ExecutionContext; import lsfusion.server.logics.classes.ValueClass; import lsfusion.server.logics.property.classes.ClassPropertyInterface; import lsfusion.server.physics.dev.integration.internal.to.InternalAction; import java.math.BigInteger; import java.sql.SQLException; public class CalculateGCDObject extends InternalAction { public CalculateGCDObject(ScriptingLogicsModule LM, ValueClass... classes) { super(LM, classes); } @Override protected void executeInternal(ExecutionContext<ClassPropertyInterface> context) throws SQLException, SQLHandledException { try { DataObject calculation = (DataObject)getParamValue(0, context); BigInteger a = BigInteger.valueOf((Integer)findProperty("a").read(context, calculation)); BigInteger b = BigInteger.valueOf((Integer)findProperty("b").read(context, calculation)); BigInteger gcd = a.gcd(b); findProperty("gcd[Calculation]").change(gcd.intValue(), context, calculation); } catch (ScriptingErrorLog.SemanticErrorException ignored) { } } }
SQL-SQL- lsFusion- ( , , SQL-), , lsFusion-, SQL-. , , ( / ), (, , — , ..), . lsFusion- , , .
, ( ) OLAP-, .
. , , , - . , «» , - . , migration.script, classpath , , . :
, , . , , , . , . , . :
V< > { 1 ... N }
, , :
( , , ). :
, , .
, IDE. , , Change migration file ( ), IDE .
, . , , : , , , .. lsFusion ( , 'abc'), , :
ServerResourceBundle.properties:
scheduler.script.scheduled.task.detail=Script scheduler.constraint.script.and.action=In the scheduler task property and script cannot be selected at the same time scheduler.form.scheduled.task=Tasks
ServerResourceBundle_ru.properties
scheduler.script.scheduled.task.detail= scheduler.constraint.script.and.action= scheduler.form.scheduled.task=
, , - . , , , .
: . , — . ( - ), , , :
- . ( , ), , , .
- . , , .
- . / , . «» .
. , . , , ( ). , .
, , NULL
,
, , , .
. , . , , , , , .
( ).
( , , , ). , . , .
lsFusion . , , , . key0, key1, ..., keyN, (, — ). , .
, .
, . , . , , , «» ( , , ).
, , . .
, , . , , - .
, , , (, ), , , , .
Conclusão
: « , ». , , , / lsFusion , , . — .
, , . , , , , , . , , , , , .
: « ...?», , , , tutorial'.