Третья и заключительная часть серии статей о языке lsFusion (ссылки на первую и вторую части)

В ней речь пойдет о физической модели: всем том, что не связано с функционалом системы, а связано с ее разработкой и оптимизацией производительности, когда данных становится слишком много.

Эта статья, как и предыдущие, не очень подходит для развлекательного чтения, но, в отличие от остальных, тут будет больше технических деталей и «горячих» тем (вроде типизации или метапрограммирования), плюс эта статья даст часть ответов на вопрос, как это все работает внутри.

В этой статье обойдемся без картинки (стека тут как такого нет), но зато сделаем оглавление, о чем просили в предыдущих статьях:


Идентификация элементов


Если проект состоит из нескольких небольших файлов, то проблем с именованием элементов обычно не возникает. Все имена на виду, и достаточно легко сделать так, чтобы они не пересекались. Если же проект, наоборот, состоит из множества модулей, разрабатываемых большим количеством различных людей, и абстракции в этих модулях из одной доменной области, конфликты имен становятся куда более вероятными. Для решения этих проблем в lsFusion есть два механизма:

  • Пространства имен — разделение имени на полное и короткое, и возможность использования при обращении к элементу только короткого имени
  • Явная типизация (если быть более точным, function overloading) — возможность называть свойства (и действия) одинаково, а затем при обращении к ним, в зависимости от классов аргументов, автоматически определять к какому именно свойству идет обращение

Пространства имен


Любой сложный проект обычно состоит из большого количества элементов, которые необходимо именовать. И, если доменные области пересекаются, очень часто возникает необходимость использовать одно и то же имя в различных контекстах. Например, у нас есть имя класса или формы Invoice (накладная), и мы хотим использовать это имя в различных функциональных блоках, например: Закупка (Purchase), Продажа (Sale), Возврат закупки (PurchaseReturn), Возврат продажи (SaleReturn). Понятно, что можно называть классы / формы PurchaseInvoice, SaleInvoice и так далее. Но, во-первых, такие имена сами по себе будут слишком громоздкими. А во-вторых, в одном функциональном блоке обращения, как правило, идут к элементам этого же функционального блока, а значит, при разработке, к примеру, функционального блока Закупки (Purchase) от постоянного повторения слова Purchase будет просто рябить в глазах. Чтобы этого не происходило, в платформе существует такое понятие как пространство имен. Работает это следующим образом:

  • каждый элемент в платформе создается в некотором пространстве имен
  • если в процессе создания элемента идет обращение к другим элементам, элементы созданные в этом же пространстве имен имеют приоритет
MODULE PurchaseInvoice;
NAMESPACE Purchase;
CLASS Invoice 'Накладная (закупка)';
MODULE SaleInvoice;
NAMESPACE Sale;
CLASS Invoice 'Накладная (продажа)';
MODULE PurchaseShipment;
REQUIRE PurchaseInvoice, SaleInvoice;
NAMESPACE Purchase;
// В качестве Invoice будет использован именно Purchase.Invoice, а не Sale.invoice
// так как namespace этого модуля Purchase и элементы с namespace Purchase приоритетнее
shipment(Invoice invoice) = AGGR ShipmentInvoce WHERE createShipment(invoice);
Пространства имен в текущей версии языка задаются для всего модуля сразу в заголовке модуля. По умолчанию, если пространство имен не задано, оно создается неявно с именем равным имени модуля. Если необходимо обратиться к элементу из не приоритетного пространства имен, это можно сделать, указав полное имя элемента (например Sale.Invoice).

Явная типизация


Пространства имен являются важным, но не единственным способом сделать код короче и читабельнее. Помимо них при поиске свойств (и действий) также существует возможность учитывать классы аргументов, передаваемых им на вход. Так, например:
sum = DATA NUMERIC[10,2] (OrderDetail);
sum = GROUP SUM sum(OrderDetail od) BY order(od);
// выберет свойство во второй строке, так как оно принимает на вход класс Order
// в то время как свойство в первой строке принимает на вход класс OrderDetail
CONSTRAINT sum(Order o) < 0 MESSAGE 'Сумма заказа должна быть положительной';
Тут, конечно, может возникнуть вопрос: а что будет, если пространство имен искомого свойства не приоритетное, но оно лучше подходит по классам? На самом деле, общий алгоритм поиска достаточно сложный (полное его описание тут) и таких «неоднозначных» случаев достаточно много, поэтому в случае неуверенности рекомендуется или задавать пространства имен / классы искомого свойства явно, или перепроверять в IDE (при помощи Go to Declaration — CTRL+B), что найденное свойство именно то, которое имелось ввиду.

Также, стоит отметить, что явная типизация в lsFusion в общем случае не обязательна. Классы параметров можно не указывать, и если платформе хватит информации, чтобы найти нужное свойство, она сделает это. С другой стороны, в реально сложных проектах классы параметров все же рекомендуется задавать явно, не только с точки зрения краткости кода, но и с точки зрения различных дополнительных возможностей, таких как: ранняя диагностика ошибок, умное автодополнение со стороны IDE и так далее. У нас был большой опыт работы как с неявной типизацией (первые 5 лет), так и с явной (оставшееся время), и надо сказать, что времена неявной типизации сейчас вспоминают с содроганием (хотя может просто «мы не умели ее готовить»).

Модульность


Модульность является одним из самых важных свойств системы, позволяющим обеспечить ее расширяемость, повторное использование кода, а также эффективное взаимодействие команды разработчиков.

В lsFusion модульность обеспечивается следующими двумя механизмами:

  • Расширения — возможность расширять (изменять) элементы системы после их создания.
  • Модули — возможность группировать некоторый функционал вместе для его дальнейшего повторного использования.

Расширения


lsFusion поддерживает возможность расширения классов и форм, а также свойств и действий через механизм полиморфизма, описанный в первой статье.
sum = DATA NUMERIC[10,2] (OrderDetail);
sum = GROUP SUM sum(OrderDetail od) BY order(od);
// выберет свойство во второй строке, так как оно принимает на вход класс Order
// в то время как свойство в первой строке принимает на вход класс OrderDetail
CONSTRAINT sum(Order o) < 0 MESSAGE 'Сумма заказа должна быть положительной';
CLASS ABSTRACT Shape;
CLASS Box : Shape;

CLASS Quadrilateral;
EXTEND CLASS Box : Quadrilateral; // Добавляем наследование

CLASS ShapeType {
    point 'Точка',
    segment 'Отрезок'

 
EXTEND CLASS ShapeType { // Добавляем статический объект
    circle 'Окружность'
}

CLASS ItemGroup;
name = DATA ISTRING[100] (ItemGroup);

itemGroup = DATA ItemGroup (Item);

EXTEND FORM items
    PROPERTIES(i) NEWSESSION DELETE // добавляем на форму кнопку удаления
    
    OBJECTS g = ItemGroup BEFORE i // добавляем на форму объект группы товаров перед товаром
    PROPERTIES(g) READONLY name
    FILTERS itemGroup(i) == g // если бы объект был добавлен после объекта с товарами, то фильтрация шла бы по группе товаров, а не по товарам
;
Также, отметим, что практически все остальные конструкции платформы (например, навигатор, дизайн формы) расширяемы по определению, поэтому для них не существует отдельной логики расширений.

Модули


Модуль — это некоторая функционально законченная часть проекта. В текущей версии lsFusion модуль — это отдельный файл, состоящий из заголовка и тела модуля. Заголовок модуля, в свою очередь, состоит из: имени модуля, а также, при необходимости, списка используемых модулей и имени пространства имен этого модуля. Тело модуля состоит из объявлений и / или расширений элементов системы: свойств, действий, ограничений, форм, метакодов и так далее.

Обычно модули для объявления своих / расширения существующих элементов используют элементы из других модулей. Соответственно, если модуль B использует элементы из модуля A, то в модуле B необходимо указать, что он зависит от A.

На основании своих зависимостей все модули в проекте выстраиваются в некотором порядке, в котором происходит их инициализация (этот порядок играет важную роль при использовании вышеупомянутого механизма расширений). Гарантируется, что если модуль B зависит от модуля A, то инициализация модуля A произойдет раньше, чем инициализация модуля B. Циклические зависимости между модулями в проекте не допускаются.

Зависимости между модулями являются транзитивными. То есть, если модуль C зависит от модуля B, а модуль B зависит от модуля A, то считается, что и модуль С также зависит от модуля A.

Любой модуль всегда автоматически зависит от системного модуля System, вне зависимости от того, указано это явно или нет.
MODULE EmployeeExample;         // Задаем имя модуля

REQUIRE Authentication, Utils;         // Перечисляем модули, от которых зависит модуль Employee
NAMESPACE Employee;             // Задаем пространство имен
 
CLASS Employee 'Сотрудник';    // Создаем класс
CLASS Position 'Должность'// Создаем еще один класс
 
employeePosition(employee) = DATA Position (Employee); // Создаем свойство

Метапрограммирование


Метапрограммирование — это вид программирования, связанный с написанием программного кода, который в качестве результата порождает другой программный код. В lsFusion для метапрограммирования используются так называемые метакоды.

Метакод состоит из:

  • имени метакода
  • параметров метакода
  • тела метакода — блока кода, состоящего из объявлений и / или расширений элементов системы ( свойств, действий, событий, других метакодов и т.д.)

Соответственно, перед тем как начать основную обработку кода, платформа выполняет его предобработку — заменяет все использования метакодов на тела этих метакодов. При этом все параметры метакода, использованные в идентификаторах / строковых литералах, заменяются на переданные этому метакоду аргументы:

Объявление:
META addActions(formName)
    EXTEND FORM formName
        PROPERTIES() showMessage, closeForm
    ;
END
Использование:
@addActions(documentForm);
@addActions(orderForm);
Результирующий код:
EXTEND FORM documentForm
    PROPERTIES() showMessage, closeForm
;  
EXTEND FORM orderForm
    PROPERTIES() showMessage, closeForm
;
Кроме просто подстановки параметров метакода, платформа также позволяет объединять эти параметры с существующими идентификаторами / строковыми литералами (или друг с другом), например:

Объявление:
META objectProperties(object, caption)
    object##Name 'Имя '##caption = DATA BPSTRING[100](object);
    object##Type 'Тип '##caption = DATA Type (object);
    object##Value 'Стоимость '##caption = DATA INTEGER (object);
END
Использование:
@objectProperties(document, 'документа');
Результирующий код:
DocumentName 'Имя документа' = DATA BPSTRING[100](Document);
DocumentType 'Тип документа' = DATA Type (Document);
DocumentValue 'Стоимость документа' = DATA INTEGER (Document);
Метакоды очень похожи на макросы в C, но, в отличии от последних, работают не на текстовом уровне (в них нельзя, к примеру, передавать параметром ключевые слова), а только на уровне идентификаторов / строковых литералов (это ограничение, в частности, позволяет парсить тело метакода в IDE).

В lsFusion метакоды решают задачи, схожие с generics в Java (передача классов в качестве параметров) и lambda в ФП (передачи функций в качестве параметров), правда, делают это не очень красиво. Но, с другой стороны, они это делают в существенно более общем случае (то есть, например, с возможностью объединения идентификаторов, использования в любых синтаксических конструкциях — формах, дизайнах, навигаторе и т.п.)

Отметим, что «разворачивание» метакодов поддерживается не только в самой платформе, но и в IDE. Так, в IDE есть специальный режим Enable meta, который генерирует результирующий код прямо в исходниках и тем самым позволяет участвовать этому сгенерированному коду в поиске использований, автодополнении и т.п. При этом, если тело метакода изменяется, IDE автоматически обновляет все использования этого метакода.



Также метакоды можно использовать не только для автоматической, но и для ручной кодогенерации (в качестве шаблонов). Для этого достаточно вместо одной @ написать @@ — и сразу после того, как строка использования метакода будет полностью введена (вплоть до точки с запятой), IDE заменит это использование метакода на сгенерированный по этому метакоду код:



Интеграция


Интеграция включает в себя все то, что связано с взаимодействием системы lsFusion с другими системами. С точки зрения направления этого взаимодействия интеграцию можно разделить на:

  • Обращение к lsFusion системе из другой системы.
  • Обращение из lsFusion системы к другой системе.

С точки зрения физической модели интеграцию можно разделить на:

  • Взаимодействие с системами, выполняющимися в «той же среде», что и lsFusion система (то есть, в виртуальной Java машине (JVM) lsFusion-сервера и/или использующими тот же SQL-сервер, что и lsFusion система).
  • Взаимодействие с удаленными системами по сетевым протоколам.

Соответственно, первые системы будем называть внутренними, вторые — внешними.

Таким образом в платформе существует четыре различных вида интеграции:

  • Обращение к внешней системе
  • Обращение из внешней системы
  • Обращение к внутренней системе
  • Обращение из внутренней системы

Обращение к внешней системе


Обращение к внешним системам в lsFusion в большинстве случаев реализуется при помощи специального оператора EXTERNAL. Этот оператор выполняет заданный код на языке / в парадигме заданной внешней системы. Кроме того, этот оператор позволяет передавать объекты примитивных типов в качестве параметров такого обращения, а также записывать результаты обращения в заданные свойства (без параметров).

На данный момент в платформе поддерживаются следующие типы взаимодействий / внешних систем:

HTTP — выполнение http-запроса Web-сервера.

Для этого типа взаимодействия необходимо задать строку запроса (URL), которая одновременно определяет как адрес сервера, так и непосредственно запрос, который необходимо выполнить. Параметры могут передаваться как в строке запроса (для обращения к параметру используется спецсимвол $ и номер этого параметра, начиная с 1), так и в его теле (BODY). Предполагается, что в BODY передаются все параметры, не использованные в строке запроса. Если в BODY больше одного параметра, тип контента BODY при передаче устанавливается равным multipart/mixed, а параметры передаются как составные части этого BODY.

При обработке параметров файловых классов (FILE, PDFFILE и т.п.) в BODY, тип контента параметра определяется в зависимости от расширения файла (в соответствии со следующей таблицей). Если расширение файла отсутствует в этой таблице, тип контента устанавливается равным application/<расширение файла>.

При необходимости, при помощи специальной опции (HEADERS) можно задать заголовки выполняемого запроса. Для этого нужно указать свойство с ровно одним параметром строкового класса, в котором будет храниться название заголовка, и значением строкового класса, в котором будет храниться значение этого заголовка.

Результат http-запроса обрабатывается аналогично его параметрам, только в обратную сторону: к примеру, если тип контента результата или присутствует в следующей таблице, или равен application/*, то считается, что полученный результат — это файл и должен записываться в свойство со значением FILE. Заголовки результата http-запроса обрабатываются по аналогии с заголовками самого этого запроса (с той лишь разницей, что опция называется HEADERSTO, а не HEADERS).
EXTERNAL HTTP GET 'https://www.cs.cmu.edu/~chuck/lennapg/len_std.jpg' TO exportFile; 
open(exportFile()); 

LOCAL headers = STRING (STRING);
headers('Authentication : Bearer') <- 'd43ks43ds343dd233'';
EXTERNAL HTTP 'http://tryonline.lsfusion.org/exec?action=getExamples' 
    HEADERS headers 
    HEADERSTO headers 
    PARAMS JSONFILE('\{"mode"=1\}'
    TO exportFile;

IMPORT FROM exportFile() FIELDS () STRING caption, STRING code DO
    MESSAGE 'Example : ' + caption + ', code : ' + code;
FOR v = headers(STRING s) DO
    MESSAGE 'Result Header is : Key - ' + s + ', Value - ' + v;
SQL — выполнение команды SQL-сервера.

Для этого типа взаимодействия задается строка подключения и SQL-команда(ы), которую необходимо выполнить. Параметры могут передаваться как в строке подключения, так и в SQL-команде. Для обращения к параметру используется спецсимвол $ и номер этого параметра (начиная с 1).

Параметры файловых классов (FILE, PDFFILE и т.п.) можно использовать только в SQL-команде. При этом, если какой-либо из параметров при выполнении является файлом формата TABLE (TABLEFILE или FILE с расширением table), то такой параметр считается таблицей и в этом случае:

  • перед выполнением SQL-команды значение каждого такого параметра загружается на сервер во временную таблицу
  • при подстановке параметров подставляется не само значение параметра, а имя созданной временной таблицы

Результатами выполнения являются: для DML-запросов — числа, равные количеству обработанных записей, для SELECT-запросов — файлы формата TABLE (FILE с расширением table), содержащие результаты этих запросов. При этом порядок этих результатов совпадает с порядком выполнения соответствующих запросов в SQL-команде.
externalSQL ()  { 
    EXPORT TABLE FROM bc=barcode(Article a) WHERE name(a) LIKE '%Мясо%'// получаем все штрих-коды товаров с именем мясо
    EXTERNAL SQL 'jdbc:mysql://$1/test?user=root&password=' 
        EXEC 'select price AS pc, articles.barcode AS brc from $2 x JOIN articles ON x.bc=articles.barcode'
        PARAMS 'localhost',exportFile() 
        TO exportFile; // читаем цены для считанных штрих-кодов
    
    // для всех товаров с полученными штрих-кодами записываем цены
    LOCAL price = INTEGER (INTEGER);
    LOCAL barcode = STRING[30] (INTEGER);
    IMPORT FROM exportFile() TO price=pc,barcode=brc;
    FOR barcode(Article a) = barcode(INTEGER i) DO 
        price(a) <- price(i);
}
LSF — вызов действия другого lsFusion-сервера.

Для этого типа взаимодействия задается строка подключения к lsFusion-серверу (или его веб-серверу, при наличии такового), действие, которые необходимо выполнить, а также список свойств (без параметров), в значения которых будут записаны результаты обращения. Передаваемые параметры должны по количеству и по классам совпадать с параметрами выполняемого действия.

Способ задания действия в этом типе взаимодействия полностью соответствует способу задания действия при обращении из внешней системы (про этот тип обращения в следующем разделе).
externalLSF()  { 
    EXTERNAL LSF 'http://localhost:7651' EXEC 'System.testAction[]'
}
По умолчанию этот тип взаимодействия реализуется по протоколу HTTP с использованием соответствующих интерфейсов обращений к / из внешней системы.

В том случае, если нужно обратиться к системе по протоколу, отличному от вышеперечисленных, это всегда можно сделать, создав действие на Java и реализовав это обращение там (но об этом чуть позже в разделе «Обращение к внутренним системам»)

Обращение из внешней системы


Платформа предоставляет возможность внешним системам обращаться к разработанной на lsFusion системе с использованием сетевого протокола HTTP. Интерфейсом такого взаимодействия является вызов некоторого действия с заданными параметрами и, при необходимости, возврат значений некоторых свойств (без параметров) в качестве результатов. Предполагается, что все объекты параметров и результатов являются объектами примитивных типов.

Вызываемое действие может задаваться одним из трех способов:

  • /exec?action=<имя действия> — задается имя вызываемого действия.
  • /eval?script=<код> — задается код на языке lsFusion. Предполагается, что в этом коде присутствует объявление действия с именем run, именно это действие и будет вызвано. Если параметр script не задан, то предполагается, что код передается первым параметром BODY.
  • /eval/action?script=<код действия> — задается код действия на языке lsFusion. Для обращения к параметрам можно использовать спецсимвол $ и номер параметра (начиная с 1).

Во втором и третьем случае, если параметр script не задан, то предполагается, что код передается первым параметром BODY.

Обработка параметров и результатов симметрична обращению к внешним системам по протоколу HTTP (с той лишь разницей, что параметры при этом обрабатываются как результаты, и, наоборот, результаты обрабатываются как параметры), поэтому особо повторяться не будем.

Например, если у нас есть действие:
importOrder(INTEGER no, DATE date, FILE detail) {
   NEW o = FOrder {
       no(o) <- no;
       date(o) <- date;
       LOCAL detailId = INTEGER (INTEGER);
       LOCAL detailQuantity = INTEGER (INTEGER);
       IMPORT FROM detail TO detailId, detailQuantity;
       FOR imported(INTEGER i) DO {
           NEW od = FOrderDetail {
               id(od) <- detailId(i);
               quantity(od) <- detailQuantity(i);
               price(od) <- 5;
               order(od) <- o;
           }
       }
       APPLY;
       EXPORT JSON FROM price = price(FOrderDetail od), id = id(od) WHERE order(od) = o;
       EXPORT FROM orderPrice(o), exportFile();
   }
}
То к нему можно обратиться при помощи POST-запроса у которого:
  • URL — хттп://адрес_сервера/exec?action=importOrder&p=123&p=2019-01-01
  • BODY — json-файл со строками запроса

Пример обращения на 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)
 
##############################################################
 
print(sum)
for item in data:
    print('{0:3}: price {1}'.format(int(item['id']), int(item['price'])))
 
##############################################################
# 205
#   4: price 5
#  18: price 5
#   3: price 5
#   1: price 5
#  10: price 5
#  15: price 5

Обращение к внутренней системе


Существует два типа внутреннего взаимодействия:

Java-взаимодействие

Этот тип взаимодействия позволяет вызвать код на языке Java внутри JVM lsFusion-сервера. Для этого необходимо:

  • обеспечить, чтобы скомпилированный Java-класс был доступен в classpath сервера приложений. Также необходимо, чтобы этот класс наследовал lsfusion.server.physics.dev.integration.internal.to.InternalAction.
    Пример 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) {
            }
        }
    }
    
  • зарегистрировать действие при помощи специального оператора внутреннего вызова (INTERNAL)
    calculateGCD 'Рассчитать НОД' INTERNAL 'CalculateGCD' (INTEGERINTEGER);
  • зарегистрированное действие, как и любое другое, можно вызывать при помощи оператора вызова. Выполняться при этом будет метод executeInternal(lsfusion.server.logics.action.controller.context.ExecutionContext context) заданного Java-класса.
    // на форме
    FORM gcd 'НОД'
        OBJECTS (a = INTEGER, b = INTEGERPANEL
        PROPERTIES 'A' = VALUE(a), 'B' = VALUE(b)
        
        PROPERTIES gcd(), calculateGCD(a, b)
    ;

    // в другом действии
    run() {
        calculateGCD(100200);
    }
SQL-взаимодействие

Этот тип взаимодействия позволяет обращаться к объектам / синтаксическим конструкциям SQL-сервера, используемого разрабатываемой lsFusion-системой. Для реализации такого типа взаимодействия в платформе используется специальный оператор — FORMULA. Этот оператор позволяет создавать свойство, вычисляющее некоторую формулу на языке SQL. Формула задается в виде строки, внутри которой для обращения к параметру используется спецсимвол $ и номер этого параметра (начиная с 1). Соответственно, количество параметров у полученного свойства будет равно максимальному из номеров использованных параметров.
round(number, digits) = FORMULA 'round(CAST(($1) as numeric),$2)';  // свойство с двумя параметрами: округляемым числом и количеством знаков после запятой
jumpWorkdays = FORMULA NULL DATE PG 'jumpWorkdays($1, $2, $3)'MS 'dbo.jumpWorkdays($1, $2, $3)'// свойство с двумя различными реализациями для разных диалектов SQL
Использовать этот оператор рекомендуется только в случаях, когда задачу невозможно решить при помощи других операторов, а также, если гарантированно известно, какие конкретно SQL сервера могут быть использованы, или используемые синтаксические конструкции соответствуют одному из последних стандартов SQL.

Обращение из внутренней системы


Тут все симметрично обращению к внутренней системе. Есть два типа взаимодействия:

Java-взаимодействие

В рамках такого типа взаимодействия внутренняя система может обращаться непосредственно к Java-элементам lsFusion-системы (как к обычным Java объектам). Таким образом, можно выполнять все те же операции как и с использованием сетевых протоколов, но при этом избежать существенного оверхеда такого взаимодействия (например, на сериализацию параметров / десериализацию результата и т.п). Кроме того, такой способ общения гораздо удобнее и эффективнее, если взаимодействие очень тесное (то есть в процессе выполнения одной операции требуется постоянное обращение в обе стороны — от lsFusion системы к другой системе и обратно) и / или требует доступа к специфическим узлам платформы.

Для того, чтобы обращаться к 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
}

Изменения, в свою очередь, бывают следующих типов:
DATA PROPERTY oldNS.oldName[class1,...,classN] -> newNS.newName[class1,...,classN]
CLASS oldNS.oldName -> newNS.newName
OBJECT oldNS.oldClassName.oldName -> newNS.newClassName.newName

TABLE oldNS.oldName -> newNS.newName
PROPERTY oldNS.oldName[class1,...,classN] -> newNS.newName[class1,...,classN]
FORM PROPERTY oldNS.oldFormName.oldName(object1,...,objectN) -> newNS.newFormName.newName(object1,...,objectN) 
NAVIGATOR oldNS.oldName -> newNS.newName
Для миграции пользовательских данных актуальны только первые три типа изменений (изменения первичных свойств, классов, статических объектов). Оставшиеся четыре типа изменений нужны:

  • для миграции метаданных (политики безопасности, настроек таблиц и т.п)
  • для оптимизации миграции пользовательских данных (чтобы не перерасчитывать агрегации и не переносить данные между таблицами лишний раз).

Соответственно, если миграция метаданных не нужна или данных не очень много, такие изменения в миграционном скрипте можно не указывать.

Пример миграции
V0.3.1 {
    DATA PROPERTY Item.gender[Item.Article] -> Item.dataGender[Item.Article] // изменение имени DATA свойства
    PROPERTY System.SIDProperty[Reflection.Property] -> Reflection.dbNameProperty[Reflection.Property] // одновременный перенос в другое пространство имен и изменение имени свойства
    FORM PROPERTY Item.itemForm.name(i) -> Item.itemForm.itemName(i)
}
  
V0.4 {
    FORM PROPERTY Document.documentForm.name(i) -> Document.itemForm.itemName(i)
    FORM PROPERTY Item.itemForm.itemName(i) -> Item.itemForm.iname // добавление явного имени для свойства на форме: iname = itemName(i)
    CLASS Date.DateInterval -> Date.Interval
    OBJECT Geo.Direction.North -> Geo.Direction.north
    TABLE User.oldTable -> User.newTable
}


Стоит отметить, что обычно большинство работ по генерации миграционных скриптов выполняется при помощи IDE. Так, при переименовании большинства элементов, можно указать специальную галочку Change migration file (включена по умолчанию), и IDE сгенерирует все нужные скрипты автоматически.

Интернационализация


На практике иногда возникает ситуация, когда необходимо иметь возможность использовать одно приложение на разных языках. Эта задача обычно сводится к локализации всех строковых данных, которые видит пользователь, а именно: текстовых сообщений, заголовков свойств, действий, форм и т.д. Все эти данные в lsFusion задаются при помощи строковых литералов (строк в одиночных кавычках, например 'abc'), соответственно, их локализация осуществляется следующим образом:
  • в строке вместо текста, который необходимо локализовать, указывается идентификатор строковых данных, заключенный в фигурные скобки (например, '{button.cancel}').
  • при передаче этой строки клиенту на сервере осуществляется поиск всех встречаемых в строке идентификаторов, затем осуществляется поиск каждого из них во всех ResourceBundle файлах проекта в нужной локали (то есть локали клиента), и при нахождении нужного варианта идентификатор в скобках заменяется на соответствующий текст.
script '{scheduler.script.scheduled.task.detail}' = DATA TEXT (ScheduledTaskDetail);
CONSTRAINT script(ScheduledTaskDetail d) AND action(d) MESSAGE '{scheduler.constraint.script.and.action}';
FORM scheduledTask '{scheduler.form.scheduled.task}';
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

Вообще, тема материализаций достаточно обстоятельно разбиралась в недавней статье про баланс записи и чтения в базах данных, поэтому останавливаться на ней подробно здесь, на мой взгляд, особого смысла не имеет.

Индексы


Построение индекса по свойству позволяет хранить в базе все значения такого свойства в упорядоченном виде. Соответственно, индекс обновляется при каждом изменении значения индексированного свойства. Благодаря индексу, если, например, идет фильтрация по индексированному свойству, можно очень быстро найти нужные объекты, а не просматривать все существующие объекты в системе.

Индексировать можно только материализованные свойства (из раздела выше).

Индекс также может быть построен сразу по нескольким свойствам (это эффективно, если, к примеру, фильтрация идет сразу по этим нескольким свойствам). Кроме того, в такой составной индекс можно включать параметры свойств. Если указанные свойства хранятся в разных таблицах, то при попытке построения индекса будет выдана соответствующая ошибка.
INDEX customer(Order o);

date = DATA DATE (Order);
INDEX date(Order o), o;

INDEX name(Sku s), price(s, DATE d), d;

Таблицы


Для хранения и вычисления значений свойств платформа lsFusion использует реляционную базу данных. Все первичные свойства, а также все агрегированные свойства, которые помечены как материализованные, хранятся в полях таблиц базы данных. Для каждой таблицы существует набор ключевых полей с именами key0, key1, ..., keyN, в которых хранятся значения объектов (например, для пользовательских классов — идентификаторы этих объектов). Во всех остальных полях хранятся значения свойств таким образом, что в соответствующем поле каждого ряда находится значение свойства для объектов из ключевых полей.

При создании таблицы необходимо указать список классов объектов, которые будут ключами в этой таблице.
TABLE book (Book);

in = DATA BOOLEAN (Sku, Stock);
TABLE skuStock (Sku, Stock); // в ней будет храниться свойство in

price = DATA NUMERIC[10,2] (Sku, DATE);
TABLE skuDate (Sku, DATE); // в ней будет храниться свойство Sku

TABLE sku (Sku);
Для каждого свойства можно указать, в какой таблице оно должно храниться. При этом количество ключей таблицы должно совпадать с количеством параметров свойства, а классы параметров должны подходить к классам ключей этой таблицы. Если для свойства таблица, в которой оно должно храниться, не задана явно, свойство автоматически будет помещено в «ближайшую» существующую в системе таблицу (то есть, количество ключей которой совпадает с количеством параметров свойства, и классы ключей которой ближе всего подходят к классам параметров).

Имена таблиц и полей, в которых хранятся свойства, в СУБД формируются в соответствии с заданной политикой именования. На текущий момент в платформе поддерживается три стандартных политики именования.
Политика Имя таблицы Имя поля
Полное с сигнатурой (по умолчанию) ПространствоИмен_ИмяТаблицы ПространствоИмен_ИмяСвойства_ИмяКласса1
_ИмяКласса2..._ИмяКлассаN
Полное без сигнатуры ПространствоИмен_ИмяТаблицы ПространствоИмен_ИмяСвойства
Краткое ИмяТаблицы ИмяСвойства
При необходимости, для каждого свойства разработчик может явно указать имя поля, в котором будет храниться это свойство. Кроме того, существует возможность создать свою политику именования полей свойств, если вышеперечисленные по каким-то причинам не подходят.

При выборе политики именования важно иметь в виду, что использование слишком краткой политики именования свойств, если количество материализованных свойств достаточно большое, может значительно усложнить именование этих свойств (так, чтобы оно было уникальным), или, соответственно, приводить к слишком частой необходимости явно именовать поля, в которых будут храниться эти свойства.

Заключение


Как говорилось в одном известном мультике: «мы строили, строили и наконец построили». Может, конечно, немного поверхностно, но разобраться с основными возможностями языка / платформы lsFusion по этим трем статьям, я думаю, можно. Пришла пора переходить к самой интересной части — сравнению с другими технологиями.

Как показал опыт и формат хабра, делать это эффективнее играя не на своем, а на чужом поле. То есть идти не от возможностей, а от проблем и, соответственно, рассказывать не о своих преимуществах, а о недостатках альтернативных технологий, причем в их же терминологии. Такой подход обычно гораздо лучше воспринимается на консервативных рынках, а именно таким рынком, на мой взгляд, и является рынок разработки информационных систем, во всяком случае, на постсоветском пространстве.

Так что совсем скоро появится еще несколько статей в стиле: «Почему не ...?», и, я уверен, они будут значительно более интересными, чем эти весьма занудные tutorial'ы.

Комментарии (96)


  1. user_man
    25.07.2019 13:59

    >> Такой подход обычно гораздо лучше воспринимается на консервативных рынках, а именно таким рынком, на мой взгляд, и является рынок разработки информационных систем

    Интересно, а зачем вашей фирме популяризация вашего языка? Надеетесь создать много доступных разработчиков? Мне кажется, что это очень дорого стоит, вряд ли потянете. Но тогда зачем ещё?


    1. NitroJunkie Автор
      25.07.2019 14:27

      Интересно, а зачем вашей фирме популяризация вашего языка? Надеетесь создать много доступных разработчиков? Мне кажется, что это очень дорого стоит, вряд ли потянете. Но тогда зачем ещё?

      Во-первых — маркетинг, нам самим будет проще заходить на новые / существующие рынки с готовыми / custom-made бизнес-решениями, когда есть комьюнити платформы, на которой построены эти решения. Это снимает опасения вендор-локов, дает много дополнительных лидов и т.п.

      Во-вторых — когда-нибудь, возможно, сделаем ultimate edition, облачную версию, ну и коммерческую поддержку (в том числе старых версий) или что-то типа того.

      Мне кажется, что это очень дорого стоит, вряд ли потянете

      Все относительно. Понятно, когда вы выводите на рынок еще один клон продукта, вам нужен огромный бюджет на маркетинг. Но если вы выводите что-то принципиально новое, да и еще на рынке (скажем ERP-платформ и продвинутых СУБД) где все стоит очень дорого, а вы предлагаете open-source, то в конечном итоге все может быть не настолько дорого. Но жизнь покажет.


      1. user_man
        26.07.2019 12:15

        >> возможно, сделаем ultimate edition, облачную версию, ну и…

        Версию чего? Язык, вроде, облачным не бывает. Платформы? Но это для разработчиков — рынок мал. А вот если готового приложения, тогда понятно, но вы ведь про язык, ну и что-то про платформу, но не про продукт типа ERP.

        >> несколько статей в стиле: «Почему не ...?»

        Да, именно так привлекают внимание хомячков. Теоретически да системно излагать полезно для грамотных людей, но их обычно немного, и к тому же они уже давно определились с выбором платформы, а переучиваться долго и нет мотивации. А вот молодёжь, которой всё равно что учить (ибо ничего ещё не знают), может заинтересоваться и дать гораздо большее количество.

        Вообще, грамотные специалисты привыкли излагать грамотно, но так они слона никогда не продадут, вот в чём проблема. Нужно «по быстрому», бодренько, с анекдотами, что бы пипл хавал с удовольствием, что бы был драйв, фан и прочее у.г. В итоге имеем массовость для дебилов, а потом удивляемся отсутствию качества, плачемся об отсутствии профессионализма и т.д. и т.п. Но по другому нельзя, если нужна массовость. Хотя когда-то профессиональную массовость умели получать при помощи усилий государства по обучению граждан…


        1. NitroJunkie Автор
          26.07.2019 12:32

          Версию чего? Язык, вроде, облачным не бывает. Платформы? Но это для разработчиков — рынок мал. А вот если готового приложения, тогда понятно, но вы ведь про язык, ну и что-то про платформу, но не про продукт типа ERP.


          Рынок разработки как минимум включает в себя рынок СУБД и ERP (причем что касается ERP там основная стоимость в платформах, а не в решениях, посмотрите например политику лицензирования 1С). А это на секунду Oracle + Microsoft + SAP (а у них и 10% не факт что на всех есть). Не говоря уже о «мелочевке» типа 1С, MongoDB и т.п. Вообще объем этого рынка где-то триллионов 5 долларов.

          Да, именно так привлекают внимание хомячков. Теоретически да системно излагать полезно для грамотных людей, но их обычно немного, и к тому же они уже давно определились с выбором платформы, а переучиваться долго и нет мотивации. А вот молодёжь, которой всё равно что учить (ибо ничего ещё не знают), может заинтересоваться и дать гораздо большее количество.

          Вообще, грамотные специалисты привыкли излагать грамотно, но так они слона никогда не продадут, вот в чём проблема. Нужно «по быстрому», бодренько, с анекдотами, что бы пипл хавал с удовольствием, что бы был драйв, фан и прочее у.г. В итоге имеем массовость для дебилов, а потом удивляемся отсутствию качества, плачемся об отсутствии профессионализма и т.д. и т.п. Но по другому нельзя, если нужна массовость. Хотя когда-то профессиональную массовость умели получать при помощи усилий государства по обучению граждан…

          Не понял вашу мысль. Я как раз старался максимально системно все изложить. Во всяком случае в одном стиле, на одном уровне абстрагирования. При этом у очень многих популярных продуктов (которыми и вы наверняка пользуетесь) документация это какой то поток сознания, где все систематизировать и составлять общую картину нужно самому перечитывая документацию по 100 раз.

          Про выбор платформы тоже не понял. IT вообще меняется достаточно с быстрой скоростью, MongoDB за 10 лет стала многомиллиардной компанией, массовый переход в веб начался лет 10 назад. Да че уж там SQL массово начал применяться лет 20 назад максимум. И это я еще про всякие React'ы молчу. Хотя да, до сих пор есть кучу систем на COBOL написанных, только что это меняет.


          1. lair
            26.07.2019 12:40

            Рынок разработки как минимум включает в себя рынок СУБД и ERP

            То есть вы одним продуктом одновременно целитесь на два совершенно разных рынка?


            1. NitroJunkie Автор
              26.07.2019 12:45

              Вы первую статью (а точнее ее заключение) я так понимаю не читали :)

              Но вообщем да. Понятно, что про рынок СУБД не очень очевидно, но я сейчас допишу статью Почему не SQL, и там будет лучше понятно почему мы целимся и в рынок СУБД (если совсем вкратце то lsFusion решает и кучу задач СУБД, которые они не решают / решают в частных случаях или которые решают только Oracle MSSQL но не решают Postgres / MySQL)

              PS: Картинка в первой статье была выбрана не случайно ;-)


              1. lair
                26.07.2019 13:00

                Но вообщем да.

                Весьма амбициозная заявка, учитывая, что вашей системе для работы нужна РСУБД.


                Картинка в первой статье была выбрана не случайн

                Я надеюсь, вы помните, чем история закончилась.


                1. NitroJunkie Автор
                  26.07.2019 13:57

                  Весьма амбициозная заявка, учитывая, что вашей системе для работы нужна РСУБД

                  А для работы программ нужен компьютер. Какая по большому счету конечному пользователю СУБД разница что там внутри: РСУБД, файлы или еще что-то.
                  Я надеюсь, вы помните, чем история закончилась.

                  Ну так это же художественное произведение. А в жизни все заканчивается далеко не всегда так как в фильмах / книжках. :)


                  1. lair
                    26.07.2019 13:59

                    Какая по большому счету конечному пользователю СУБД

                    Конечному пользователю — никакой. Но рынок СУБД ориентирован не на конечных пользователей, а на разработчиков, а вот им разница очень большая.


                    Ну так это же художественное произведение.

                    One ring to rule them all — тоже.


          1. user_man
            26.07.2019 12:57

            >> Рынок разработки как минимум включает в себя рынок СУБД и ERP

            То есть вы говорите о потенциально возможной расширенной версии платформы. Понятно.

            >> причем что касается ERP там основная стоимость в платформах, а не в решениях

            Тут не очевидно. Стоимость внедрения и эксплуатации (с написанием кастом-решений) по сравнению со стоимостью лицензий той же 1С явно не выглядит бледной тенью.

            >> Не понял вашу мысль

            Я поддержал ваше намерение сделать текст для хомячков, а потом повздыхал по поводу последствий таких способов собирать толпу. Но это не относится ни к вам, ни к вашей фирме. Это общая проблема для всего мира.


            1. NitroJunkie Автор
              26.07.2019 13:50

              Тут не очевидно. Стоимость внедрения и эксплуатации (с написанием кастом-решений) по сравнению со стоимостью лицензий той же 1С явно не выглядит бледной тенью.

              Так в капитализацию SAP, Oracle и Microsoft не включена капитализация компаний разрабатывающих на их технологиях.
              Я поддержал ваше намерение сделать текст для хомячков, а потом повздыхал по поводу последствий таких способов собирать толпу. Но это не относится ни к вам, ни к вашей фирме. Это общая проблема для всего мира.

              Ну как говорил Эйнштейн: «Всё следует упрощать до тех пор, пока это возможно, но не более того».
              Хотя здесь все же основная мысль была перейти на чужую терминологию, так как ее уже знают.


  1. NitroJunkie Автор
    25.07.2019 14:27

    del


  1. michael_vostrikov
    26.07.2019 21:16

    CLASS ItemGroup;
    name = DATA ISTRING[100] (ItemGroup);

    Скажите, почему выбрана именно такая запись? У вас же язык со своей грамматикой, можно было вот так сделать:


    CLASS ItemGroup {
        name: DATA ISTRING[100];
    }

    Даже в вашем примере вы обозначаете комментариями начало и конец структуры, это говорит о том, что выразительность языка недостаточная.


    //#game
    CLASS Game 'Игра';
    
    date 'Дата' = DATA DATE (Game);
    hostTeam = DATA Team (Game);
    guestTeam = DATA Team (Game);
    hostTeamName 'Хозяева' (Game game) = name(hostTeam(game));
    guestTeamName 'Гости' (Game game) = name(guestTeam(game));
    //#game end

    Так гораздо лучше выглядит, меньше информационного шума:


    CLASS Game 'Игра' {
        date 'Дата': DATA DATE;
        hostTeam: DATA Team;
        guestTeam: DATA Team;
        hostTeamName 'Хозяева': name(this.hostTeam);
        guestTeamName 'Гости': name(this.guestTeam);
    }


    1. DAleby
      26.07.2019 21:58

      Даже в вашем примере вы обозначаете комментариями начало и конец структуры, это говорит о том, что выразительность языка недостаточная.

      Эти комментарии — это разметка для скрипта, который формирует html'ки для confluence, который мы используем для документации. Это учебный пример из документации, поэтому он так размечен, выразительность языка тут не при чем.


    1. DAleby
      26.07.2019 22:53

      Скажите, почему выбрана именно такая запись?

      В lsfusion классы не являются центральной сущностью, вокруг которой строится остальная логика, в lsfusion главными и первичными яляются свойства и действия, а классы выступают в роли ограничений, фильтров. Да, с них удобно начинать объяснение, но это следствие того, что так большинству людей привычней.

      Такой синтаксис также является следствием отсутсвия у нас инкапсуляции в том значении этого термина, где речь идет об объединении данных и методов работы с ними. То есть у нас свойства и действия не привязаны к какому-то одному главному классу. Если искать близкий аналог, то наши свойства — это что-то вроде мультиметодов, что дает возможность, например, subtype полиморфизма по нескольким параметрам. Реализуется это в lsfusion с помощью абстрактных (ABSTRACT) свойств и действий.

      Также это дает нам возможность «расширять классы» новыми свойствами и действиями в разных модулях, обеспечивая модульность и позволяя нам компоновать необходимую функциональность с помощью выбора нужного множества модулей. Да, такого можно было добиться и открытыми классами c инкапсуляцией, как, например, в ruby, но наш подход — это следствие того, что свойства первичны, а классы вторичны. То есть свойство, зависящее, например, от товара и склада при нашем подходе не «принадлежит» ни товару, ни складу.


      1. lair
        27.07.2019 01:23

        Любопытно, как расходятся взгляды разных представителей lsFusion на то, какие в нем используются термины (и даже я бы сказал, «что в нем главное», но хорошей цитаты не могу найти).


        1. DAleby
          27.07.2019 01:56

          У нас есть более-менее сложившаяся терминология, которая используется в документации. Но здесь в комментариях я, например, иногда отхожу от этой терминологии, потому что говорить с людьми на незнакомом им языке не совсем разумно. В моем комментарии выше из терминов lsfusion используются: свойство, действие, класс, абстрактное свойство/действие, модуль. Вроде бы все, остальное — просто слова.


          1. lair
            27.07.2019 11:48

            Функциональная СУБД


            Поле в таблице с N ключами будет представлено как функция от N параметров.

            Собственно, термин "свойство" в этой статье не упоминается ни разу, как и в следующей статье на ту же тему.


            Я и говорю — любопытно.


            1. CrushBy
              27.07.2019 11:56

              Да, у меня там было два варианта, либо использовать нашу терминологию, либо использовать более известную обычному человеку «функция».

              Формально ту статью я делал без прямой привязке к платформе, а как концепцию. Поэтому старался не загружать новой терминологией.


              1. lair
                27.07.2019 11:59

                Из этого можно сделать не менее любопытный вывод: lsFusion не является функциональной СУБД. Ну так, по формальным признакам.


      1. michael_vostrikov
        27.07.2019 07:01
        +1

        Эти комментарии — это разметка для скрипта, который формирует html'ки для confluence

        Тем более, это наоборот подтверждает то, о чем я говорю. Машина не может прочитать формализованный и структурированный язык программирования, приходится подсказки давать. Не только для человека выразительность недостаточная, но и даже для машины.


        В lsfusion классы не являются центральной сущностью, вокруг которой строится остальная логика
        Такой синтаксис также является следствием отсутствия у нас инкапсуляции в том значении этого термина, где речь идет об объединении данных и методов работы с ними.

        Я не предлагаю делать их центральной сущностью. Я говорю о том, что оба варианта абсолютно эквивалентны, но во втором меньше информационного шума и копипасты.


        Да, с них удобно начинать объяснение, но это следствие того, что так большинству людей привычней.

        Так языки программирования для людей же делаются.


        То есть у нас свойства и действия не привязаны к какому-то одному главному классу.

        Ну так и плохо, что не привязано, потому что в предметной области оно привязано. Вы даже специальный язык разметки исходников сделали, чтобы отражать это в документации.


        То есть свойство, зависящее, например, от товара и склада при нашем подходе не «принадлежит» ни товару, ни складу.

        Правильно, оно принадлежит сущности "Глобальное пространство имен". И когда их там становится много, среди них начинают выделяться группы со схожим назначением. Оно не принадлежит ни товару, ни складу, оно принадлежит третьему классу.


        1. NitroJunkie Автор
          27.07.2019 10:14

          Вы даже специальный язык разметки исходников сделали, чтобы отражать это в документации.

          Язык разметки там для того чтобы в документацию часть файла вставлять, там их можно было part1, part2, ..., partN называть.

          Ну так и плохо, что не привязано, потому что в предметной области оно привязано

          Там в фразе на которую вы отвечали, основная часть — слово главному. То есть если у вас скажем currentBalance(Stock stock, Article article) — тут какой класс главный Stock или Article. Куда его класть в Stock или Article?

          Плюс надо понимать что при очень высокой модульности (а это очень важно в сложных ИС) обычно свойства так вместе не группируются как в этих примерах и в этих случаях инкапсуляция наоборот будет создавать информационный шум.

          Но то, что для многих простых случаев инкапсуляция выразительнее — факт. И мы ее добавим (там на самом деле это не сложно, хотя описание алгоритма поиска свойств конечно еще больше усложнится), но и старый синтаксис естественно тоже останется (это своего рода static'ы в Java, C#), выше описал для чего. Просто в данном случае то что вы предлагаете это лучшее — враг хорошего.


          1. michael_vostrikov
            27.07.2019 11:09
            +1

            Язык разметки там для того чтобы в документацию часть файла вставлять, там их можно было part1, part2, ..., partN называть.

            Так, и с какой же целью вы вставляете в конкретный раздел документации именно эту часть, а не рандомные строки? Может потому что строки в этой части как-то связаны?


            Там в фразе на которую вы отвечали, основная часть — слово главному.

            Да, и в предметной области ровно то же самое.


            То есть если у вас скажем currentBalance(Stock stock, Article article) — тут какой класс главный Stock или Article. Куда его класть в Stock или Article?

            В третий класс. Потому что сейчас он тоже находится в третьем классе, только там еще и куча других методов, не связанных с балансом.


            и в этих случаях инкапсуляция наоборот будет создавать информационный шум.

            Я говорю не про инкапсуляцию, а про разный синтаксис для описания одной и той же ситуации.


            1. NitroJunkie Автор
              27.07.2019 11:33

              В третий класс. Потому что сейчас он тоже находится в третьем классе, только там еще и куча других методов, не связанных с балансом.

              Это в каком таком третьем классе? Как он называется? Когда объекты этого класса создаются, удаляются? Он может наследоваться? ну и т.п.
              Сейчас у нас никакого третьего класса нет, ни физически, ни логически.
              Я говорю не про инкапсуляцию, а про разный синтаксис для описания одной и той же ситуации.

              Не совсем понял эту мысль. Сейчас как раз у нас один синтаксис для описания свойств. Добавив инкапсуляцию будет два. Оставив чисто инкапсуляционный синтаксис, можно получить что некоторые модули будут пестрить EXTEND Team. При этом static синтаксис все равно придется оставить, иначе непонятно что например делать со свойствами от примитивов. Скажем f = DATE (INTEGER, INTEGER); Их куда? Делать EXTEND INTEGER?


              1. lair
                27.07.2019 11:45

                При этом static синтаксис все равно придется оставить, иначе непонятно что например делать со свойствами от примитивов. Скажем f = DATE (INTEGER, INTEGER);

                А что такое "свойство от примитива"? Вот в вашем конкретном примере в f какой прикладной смысл вкладывается?


                1. LeshaLS
                  27.07.2019 11:47

                  Например, свойство isDayOff = DATA BOOLEAN (DATE). Обозначает, является ли данный день выходным или нет.


                  1. lair
                    27.07.2019 11:51

                    В этом конкретном случае синтаксис должен быть таким же, как и для "свойства" любого другого типа (например, свойства isActive сущности "пользователь", означающего, является ли данный пользователь активным). Потому что консистентность.


                    1. LeshaLS
                      27.07.2019 11:54

                      Так он и есть такой же:
                      isDayOff = DATA BOOLEAN (DATE);
                      isActive = DATA BOOLEAN (User);
                      В чем разница?


                      1. lair
                        27.07.2019 11:56

                        А я и не говорю, что разница есть. Я отвечаю на вопрос "что делать со свойствами примитивов" — то же самое, что со "свойствами" других классов.


                        1. NitroJunkie Автор
                          27.07.2019 12:01

                          Так человек предложил сделать инкапсуляцию. ОК:

                          EXTEND CLASS User {
                          isActive = DATA BOOLEAN;
                          }

                          Что с isActive от DATE делать?


                          1. lair
                            27.07.2019 12:04

                            Так человек предложил сделать инкапсуляцию

                            Вообще-то, человек вам явно написал "я говорю не про инкапсуляцию".


                            EXTEND CLASS User {
                            isActive = DATA BOOLEAN;
                            }

                            … и тот же человек вам написал: "я ничего не говорил про extend".


                            Что с isActive от DATE делать?

                            То же самое: если вы настаиваете, что для записи добавленного свойства для пользователя нужно использовать EXTEND, то для записи добавленного свойства даты нужно использовать его же.


                            EXTEND DATE {
                            isDayOff = DATA BOOLEAN;
                            }


                            1. NitroJunkie Автор
                              27.07.2019 12:28

                              EXTEND слово нужно для определения является ли это объявление нового класса или просто использование существующего.

                              Согласен можно такой использовать синтаксис с DATE. Но тут вот какая штука, многие агитируют за инкапсуляцию, потому как привыкли к ней. А ирония в том, что в современных языках инкапсуляции для примитивов в основном нет, и эта вещь наоборот будет мозг взрывать.


                              1. lair
                                27.07.2019 13:30
                                +1

                                EXTEND слово нужно для определения является ли это объявление нового класса или просто использование существующего.

                                Ну так и здесь оно используется для того, чтобы отличить определение нового типа данных от использования существующего. Все логично.


                                Но тут вот какая штука, многие агитируют за инкапсуляцию, потому как привыкли к ней.

                                Пока что "за инкапсуляцию" здесь агитируете вы. А ваши собеседники повторяют, что они говорят не об инкапсуляции.


                                А ирония в том, что в современных языках инкапсуляции для примитивов в основном нет

                                Я не знаю, что вы имеете в виду, если честно. В C# инкапсуляция для "примитивов" есть и ничем не отличается от других типов.


                                1. NitroJunkie Автор
                                  27.07.2019 13:55

                                  Пока что «за инкапсуляцию» здесь агитируете вы. А ваши собеседники повторяют, что они говорят не об инкапсуляции.

                                  Я не знаю, что вы имеете в виду, если честно. В C# инкапсуляция для «примитивов» есть и ничем не отличается от других типов.

                                  Вы сейчас про инкапсуляцию в ООП, или про абстрактную инкапсуляцию, которая все обо всем?


                                  1. lair
                                    27.07.2019 13:55

                                    Я специально дал ссылку на ту инкапсуляцию, о которой я.


                                    1. NitroJunkie Автор
                                      27.07.2019 14:02

                                      Так там вся статья что есть несколько разных трактовок инкапсуляции.

                                      Инкапсуляция зачастую рассматривается как понятие, присущее исключительно объектно-ориентированному программированию (ООП), но в действительности обширно встречается и в других (см. подтипизация на записях и полиморфизм записей и вариантов)

                                      Лично я чисто про ООП'ую говорю (сокрытие не обязательно, это опять таки перпендикулярная штука).


                                      1. lair
                                        27.07.2019 14:04

                                        Там даже написано, какая конкретно инкапсуляция есть в C#. Не знаю уж, куда конкретнее.


                  1. michael_vostrikov
                    27.07.2019 12:14

                    isDayOff не может быть свойством DATE, так как требует кучу дополнительной информации — пользователя, причину, даже страну, в зависимости от целей. Взял пользователь выходной среди недели, ну вот мастер из ЖКХ только в этот день может прийти, как вы будете true возвращать? Правильный вариант это например класс ScheduleManager с методом isDayOff(Date, User).


                    1. DAleby
                      27.07.2019 13:08

                      Хорошо, а isLeapYear(Date) может быть?

                      И я хотел бы заметить, что мы обсуждаем lsfusion, отвечаем на вопросы, почему у нас синтаксис именно такой, а не иной. Когда мы используем слово «свойство», мы имеем ввиду термин из lsfusion. Если мы будем говорить на разных языках, то друг друга так и не услышим.


                      1. michael_vostrikov
                        27.07.2019 13:24
                        +1

                        Ок, isLeapYear подходящий пример, и теперь понятно, при чем тут EXTEND. Допустим у нас несколько таких свойств.


                        isLeapYear = DATA BOOLEAN (DATE);
                        some LONG CODE;
                        isEvenYear = DATA BOOLEAN (DATE);
                        some LONG CODE;
                        another LONG CODE;
                        has31Days = DATA BOOLEAN (DATE);
                        some LONG CODE;
                        ...

                        EXTEND Date {
                            isLeapYear: DATA BOOLEAN;
                            isEvenYear: DATA BOOLEAN;
                            has31Days: DATA BOOLEAN;
                        }
                        
                        some LONG CODE;
                        another LONG CODE;
                        ...

                        Второй вариант более структурирован, выглядит чище, и соответственно проще для понимания.


                        1. NitroJunkie Автор
                          27.07.2019 13:52

                          Повторюсь еще раз:
                          Это неплохой синтаксический сахар. Но за функциональную группировку должны по хорошему отвечать модули, это их прямая обязанность. Для всех остальных элементов — это просто побочный эффект.

                          И наличие общего по классу параметра, еще не означает одинаковую или даже сильно похожую логику свойств.


                          1. michael_vostrikov
                            27.07.2019 14:17
                            +1

                            Если не означает, не надо писать в один класс, если означает, надо. Как есть, так и делать. Все просто же.


                        1. DAleby
                          27.07.2019 13:58

                          Да в таком синтаксисе нужно будет делать как-то так. Только вот кажется, что будет слишком много EXTEND CLASS инструкций в модулях, использующих классы из других модулей.

                          Но опять же мы все-таки хотели иметь возможность не создавать классы для свойств, зависящих от нескольких равноправных сущностей. Можно, конечно, для этой цели использовать какой-нибудь вот такой синтаксис:
                          (Item i, Store s) {
                          property1;
                          ...
                          propertyN;
                          }


                          Не уверен, правда, насколько это будет выглядеть чище, когда дело дойдет до расширений. Нужно смотреть.


              1. michael_vostrikov
                27.07.2019 12:00
                +1

                Это в каком таком третьем классе? Как он называется? Когда объекты этого класса создаются, удаляются?

                "Глобальное пространство имен". Один объект, создается при старте, удаляется при завершении.


                можно получить что некоторые модули будут пестрить EXTEND Team

                Я ничего не говорил про extend, только про форму записи того, что у вас и так записано.


                иначе непонятно что например делать со свойствами от примитивов. Скажем f = DATE (INTEGER, INTEGER); Их куда? Делать EXTEND INTEGER?

                Не совсем понятно, что такое "свойство от примитивов" и как оно используется, но EXTEND тут ни при чем. Помещаем в отдельный класс с названием из бизнес-логики, который ни от чего не наследуется.


                class SomeClass {
                    f: DATE;
                }

                Я не предлагаю убирать текущий синтаксис, если он вам действительно нужен, только мне пока непонятно, зачем он может быть нужен.


                1. NitroJunkie Автор
                  27.07.2019 12:16

                  «Глобальное пространство имен». Один объект, создается при старте, удаляется при завершении.

                  Ну вы же понимаете что это дырявая абстракция и попытка подогнать все под ответ. В ERP этой абстракции будет принадлежать 70% свойств (так как в сложных системах свойств от одного параметра как правило большинство).
                  Не совсем понятно, что такое «свойство от примитивов» и как оно используется, но EXTEND тут ни при чем. Помещаем в отдельный класс с названием из бизнес-логики, который ни от чего не наследуется.

                  Так а как определить у этого свойства должен быть параметр this или нет?

                  То есть как в примере сверху когда есть свойство является день выходным или нет:

                  class DayOffs {
                  isDayoff = BOOLEAN (DATE);
                  }

                  Как определить это на самом деле это isDayoff от одного параметра или от двух, то есть:
                  isDayOff = BOOLEAN (DATE)
                  или
                  isDayOff = BOOLEAN (Dayoffs, DATE)

                  Тоже самое с currentBalance. Не совсем понял какой синтаксис вы предлагается для него. Сейчас это:

                  income = GROUP SUM quantity(IncomeDocumentDetail i) BY sku(i), stock(document(i));
                  outcome = GROUP SUM quantity(OutcomeDocumentDetail i) BY sku(i), stock(document(i));
                  currentBalance(Sku sku, Stock stock) = income(sku,stock) — outcome(sku, stock);

                  В вашем синтаксисе это будет выглядеть для Sku:
                  CLASS Sku {
                  income = GROUP SUM quantity(IncomeDocumentDetail i) IF sku(i) = this BY
                  stock(document(i));
                  outcome = GROUP SUM quantity(OutcomeDocumentDetail i) IF sku(i) = this, BY stock(document(i));
                  currentBalance(Stock st) = income(st) — outcome(st);
                  }
                  Хотя тоже самое можно записать и для Stock. Соответственно вопрос куда именно класть эти свойства в Stock или Sku? И не будет ли такая неоднозначность создавать дополнительную сложность? В ООП инкапсуляция нужна для реализации field'ов и полиморфизма. А в lsFusion этой потребности нет.


                  1. lair
                    27.07.2019 12:22

                    В ERP этой абстракции будет принадлежать 70% свойств (так как в сложных системах свойств от одного параметра как правило большинство).

                    Эээ, нет. В ERP функции больше чем от одного параметра (обычно) все-таки кладутся в семантические связанные структурные блоки, а не просто в глобальное пространство имен.


                    Так а как определить у этого свойства должен быть параметр this или нет?

                    У свойства всегда есть параметр this, потому что свойство — оно всегда свойство "чего-то". Ну это, конечно, если вы под this понимаете тот объект, к которому привязано свойство.


                    Соответственно вопрос куда именно класть эти свойства в Stock или Sku?

                    Вы не поверите, но кортежи ровно для этого и придумали.


                    (Sku, Stock) {
                      income = ...
                      outcode = ...
                      currentBalance = income - outcome
                    }

                    Это, правда, зиждется на предположении, что у вас все свойства одинаковы и равноправны, что мне кажется несколько… избыточным.


                    1. NitroJunkie Автор
                      27.07.2019 12:32

                      Вы не поверите, но кортежи ровно для этого и придумали.

                      Вы эти точки тогда расшифруйте. Как вы в них будете к Sku и Stock обращаться this1 и this2?

                      Не говоря, о том что ладно когда у свойств первый параметр совпадает, но когда все совпадают это гораздо более редкая ситуация. Соответственно непонятно чем это лучше когда параметры задаются прямо при объявлении свойства.


                      1. lair
                        27.07.2019 13:33

                        Как вы в них будете к Sku и Stock обращаться this1 и this2?

                        sku и stock. Автоименование в кортежах уже даже в C# умеют.


                        Не говоря, о том что ладно когда у свойств первый параметр совпадает, но когда все совпадают это гораздо более редкая ситуация.

                        А если не совпадают, то и группировать, возможно, не надо. Или надо, но иначе.


                        Соответственно непонятно чем это лучше когда параметры задаются прямо при объявлении свойства.

                        Тем, что связанные в проекте сущности оказываются связанными в языке. Это удобно.


                        Но вообще, конечно, если вы просто перестанете делать вид, что у вас все свойства одинаковые, станет намного проще все это обсуждать.


                        1. NitroJunkie Автор
                          27.07.2019 13:50

                          Но вообще, конечно, если вы просто перестанете делать вид, что у вас все свойства одинаковые, станет намного проще все это обсуждать.

                          Вот честно не понимаю, что вы имеете ввиду под фразой «все свойства одинаковые».

                          Ну и вообще то что вы предложили с кортежами, это группировка вместе свойств с одинаковыми параметрами. Ну ок неплохой синтаксический сахар, такой общий случай инкапсуляции. Но за функциональную группировку должны по хорошему отвечать модули, это их прямая обязанность. Для всех остальных элементов — это просто побочный эффект.


                          1. lair
                            27.07.2019 13:55

                            Вот честно не понимаю, что вы имеете ввиду под фразой «все свойства одинаковые».

                            Очень простую вещь: вы в ходе этого обсуждения требуете, чтобы концепция, которую вам показали на isDayOff = DATA BOOLEAN (DATE), была применима к currentBalance(Stock st) = income(st) — outcome(st). Я из этого делаю вывод, что для вас и то, и другое — это свойство, и они в lsFusion относятся к одной и той же категории, и от них ожидается одно и то же поведение. Ну то есть ровно то, что подразумевается, когда две вещи называют одним словом.


                            Ну и вообще то что вы предложили с кортежами, это группировка вместе свойств с одинаковыми параметрами.

                            Нет, это группировка вместе свойств, которые должны быть вместе по дизайну.


                            Но за функциональную группировку должны по хорошему отвечать модули, это их прямая обязанность.

                            Заводить по модулю на каждую бизнес-сущность? Можно, конечно, но избыточно же.


                            1. NitroJunkie Автор
                              27.07.2019 13:58

                              Заводить по модулю на каждую бизнес-сущность? Можно, конечно, но избыточно же.

                              Вы же понимаете что наличие параметров одинакового класса, еще не означает семантическую связь между свойствами? Корреляция возможно и есть, но далеко не всегда.

                              И размазывать логику функциональной группировки на две абстракции модули и классы / кортежи классов, это по вашему не избыточно?


                              1. lair
                                27.07.2019 14:00

                                Вы же понимаете что наличие параметров одинакового класса, еще не означает семантическую связь между функциями?

                                А я нигде не говорил, что надо складывать в одну группу все функции (стоп, какие функции? были же свойства только что) с параметрами одинакового класса.


                                И размазывать логику функциональной группировки на две абстракции модули и классы / кортежи классов, это по вашему не избыточно?

                                Нет, не избыточно, если оно решает разные задачи.


                                1. NitroJunkie Автор
                                  27.07.2019 14:04

                                  Нет, не избыточно, если оно решает разные задачи.

                                  Как разные? Одну же — задачу функциональной группировки.


                                  1. lair
                                    27.07.2019 14:05

                                    Я же говорю: если. Например, классы могут решать не только задачу функциональной группировки. Или, например, классы и модули могут решать задачи функциональной группировки разных объектов.


                                    1. NitroJunkie Автор
                                      27.07.2019 14:19
                                      -1

                                      А здесь уже бритва не помню кого должна работать. Что разные сущности не должны отвечать за одно и то же.


                                      1. lair
                                        27.07.2019 14:20

                                        Ну так они и не отвечают за одно и то же. Они отвечают за разное.


                                        1. NitroJunkie Автор
                                          27.07.2019 14:27

                                          Или, например, классы и модули могут решать задачи функциональной группировки разных объектов.

                                          То есть классы и модули отвечают за одно и то же (что не отменяют что у них еще есть и другие обязанности). Что в общем случае хреново с архитектурной точки зрения.


                                          1. lair
                                            27.07.2019 14:28

                                            То есть классы и модули отвечают за одно и то же

                                            Нет же. Там же написано: разных объектов. В смысле, классы группируют одни объекты, модули группируют другие объекты.


                  1. michael_vostrikov
                    27.07.2019 12:44

                    Ну вы же понимаете что это дырявая абстракция и попытка подогнать все под ответ.

                    Нет, это именно та причина, по которой в языках программирования вообще появились классы, модули, пространства имен, и другие способы группирования глобальных сущностей.


                    В ERP этой абстракции будет принадлежать 70% свойств

                    Если вы так напишите, то да. А логически нет. externalSQL(), externalLSF(), importOrder(), shipment() логически делятся на 2 группы, одна относится к инфраструктуре, другая к бизнес-логике.


                    Так а как определить у этого свойства должен быть параметр this или нет?

                    Свойства всегда принадлежат какому-то this. Параметры свойствам не нужны, this используется в коде методов.


                    Как определить это на самом деле это isDayoff от одного параметра или от двух, то есть:

                    Я другой код писал, у меня там это функция с 2 аргументами.


                    В вашем синтаксисе это будет выглядеть для Sku: Хотя тоже самое можно записать и для Stock. Соответственно вопрос куда именно класть эти свойства в Stock или Sku?

                    Я же уже сказал, если отнести к конкретному классу нельзя, это будет третий класс.


                    class SkuManager {
                        currentBalance(Stock stock, Sku sku) {
                            return this.income(stock, sku) - this.outcome(stock, sku);
                        }
                    }

                    Вообще, если balance это количество товара на складе, то можно в склад поместить, но точно не в Sku. Потому что склад контролирует товар, а не наоборот. А вернее заведующий складом, вот и третья сущность появилась.


                    В ООП инкапсуляция нужна для реализации field'ов и полиморфизма.

                    Я не говорю про инкапсуляцию или иные архитектурные понятия, я говорю исключительно про форму записи одних и тех же взаимосвязей. Это вы почему-то переводите разговор в область проектирования в классическом ООП.


                    1. NitroJunkie Автор
                      27.07.2019 13:47

                      Нет, это именно та причина, по которой в языках программирования вообще появились классы, модули, пространства имен, и другие способы группирования глобальных сущностей.


                      Классы, модули и пространства имен это строго говоря перпендикулярные понятия. И все они есть в lsFusion. И никакой класс SkuManager не нужен, потому что для этого есть модуль. Точнее SkuManager должен быть модулем.


                      1. michael_vostrikov
                        27.07.2019 14:04

                        Ну так и в пределах одного модуля могут быть функции, которые можно сгруппировать по их назначению. Зачем относить их неявно к одной глобальной сущности, если можно отнести явно к нескольким конкретным в соответствии с предметной областью?


                        1. NitroJunkie Автор
                          27.07.2019 14:07

                          Ну так и в пределах одного модуля могут быть функции, которые можно сгруппировать по их назначению. Зачем относить их неявно к одной глобальной сущности, если можно отнести явно к нескольким конкретным в соответствии с предметной областью?


                          Если у свойств одинаковый / семантически близкий функционал их лучше выделить в отдельный модуль. Благо lsFusion отлично подходит для работы с такими микромодулями. Ну или просто в одном модуле рядом положить. В некоторых случаях сгруппировать в одном классе может быть еще лучше, но это уже лучшее враг хорошего.


                          1. michael_vostrikov
                            27.07.2019 14:18
                            +1

                            Ну или просто в одном модуле рядом положить.

                            Вот это и есть основной признак того, что это отдельная сущность со своим поведением.


        1. DAleby
          27.07.2019 11:18

          Тем более, это наоборот подтверждает то, о чем я говорю. Машина не может прочитать формализованный и структурированный язык программирования, приходится подсказки давать. Не только для человека выразительность недостаточная, но и даже для машины.

          Эти комментарии указывают на блок кода, то есть с какой по какую строчку исходного файла надо вставить в confluence. Это просто нарезка большого файла на кусочки, никакого отношения к выразительности языка это не имеет.

          Оно не принадлежит ни товару, ни складу, оно принадлежит третьему классу.

          В классическом ООП с инкапсуляцией оно будет принадлежать третьему классу, да. А у нас оно «принадлежит» паре классов (товар, склад), в этом разница. В результате этого мы можем получить при желании множественную диспетчеризацию (это расширение функциональности виртуальных методов). В языках же с классическим ООП для этого конкретного случая придется использовать, например, Visitor, который может реализовать двойную диспетчеризацию (double dispatch). Для случая с тремя классами будет вообще все сложно.


          1. michael_vostrikov
            27.07.2019 11:46

            А у нас оно «принадлежит» паре классов (товар, склад)

            "Пара" это логически и есть третья сущность, только без явно выделенного класса.
            Кстати, где хранятся значения, в промежуточной таблице для many-to-many?


            В результате этого мы можем получить при желании множественную диспетчеризацию

            Для поддержки множественной диспетчеризации в языке необязательно делать методы, висящие в глобальном пространстве имен. Нет никакой разницы выбирать реализацию мультиметода из globalNamespace.multiMethod() или someVariable.multiMethod().


            1. lair
              27.07.2019 11:52

              "Пара" это логически и есть третья сущность, только без явно выделенного класса.

              … заметим в скобках, что тип "кортеж нескольких типов" в природе вполне себе существует, поэтому тем более не понятно, что вызывает вопросы.


            1. NitroJunkie Автор
              27.07.2019 12:19

              «Пара» это логически и есть третья сущность

              Любая сущность (а точнее объект) подразумевает операции создания / удаления. У пары / кортежа таких операций нет.

              Хотя конечно вопрос что под сущностью понимать. Так как любое понятие можно абстрагировать до полной бессмысленности.


              1. lair
                27.07.2019 12:24

                У пары / кортежа таких операций нет.

                Это, очевидно, неправда. Такие операции у пары/кортежа есть могут быть. Но вернемся на шаг назад.


                Любая сущность (а точнее объект) подразумевает операции создания / удаления.

                А у вас свойства могут быть только у сущностей ("а точнее объектов"), или нет?


              1. michael_vostrikov
                27.07.2019 13:06

                У пары / кортежа таких операций нет.

                Ну как это нет. Вот у меня есть Product product, я достал его по id из таблицы. Какое у него значение этого общего свойства? Никакое, пары в данный момент еще не существует. Потом я достал Stock stock, обратился к свойству, получил значение. Значит пара в этот момент существует. Значит она была создана между этими двумя моментами.


                Но это буквоедство уже. Суть в том, что если свойство не принадлежит одному объекту, значит принадлежит другому, и лучше если это будет явно задано в коде, чем если это надо будет определять путем его анализа. Вот как в примере с функциями externalSQL(), externalLSF(), importOrder(), shipment(). Тот, кто читает код, будет их группировать в уме "ага, это системные функции, это бизнес-логика". Значит язык опять же недостаточно выразительный, не позволяет задавать все нужные взаимосвязи.


        1. DAleby
          27.07.2019 13:36

          Я не предлагаю делать их центральной сущностью. Я говорю о том, что оба варианта абсолютно эквивалентны, но во втором меньше информационного шума и копипасты.

          А мне кажется предложение для всех комбинаций классов создавать новый класс — это как раз делать класс центральной сущностью, как в условной Java.

          Оно не принадлежит ни товару, ни складу, оно принадлежит третьему классу.

          Ну а мы пошли по другому пути: мы можем не создавать новый класс, когда нам нужно свойство от нескольких равноправных сущностей. Это позволяет не создавать объекты (товар, склад) (объекты же будут у этого класса?), потому что с этими объектами есть сложности, например, их когда-то нужно создавать и удалять.

          Например, пользователь создал новый объект класса товар, теперь хочет переместить его на склад, в этом случае должен быть создан объект (товар, склад), то есть по какому-то событию нам нужно добавлять этот объект. Теперь пользователь удалил товар, нам теперь надо (надо ведь?) удалить объект (товар, склад), то есть опять же должно быть какое-то событие, по которому будет происходить удаление этого объекта. А если у нас класс товар участвует в большом количестве таких объединяющих классов?


          1. lair
            27.07.2019 13:41

            А мне кажется предложение для всех комбинаций классов создавать новый класс

            Как ни странно, это совершенно не нужно для того, чтобы реализовать написанное выше предложение. Кортежи, они же типы-произведения — и все.


            Например, пользователь создал новый объект класса товар, теперь хочет переместить его на склад, в этом случае должен быть создан объект (товар, склад), то есть по какому-то событию нам нужно добавлять этот объект.

            Ну, вы же по какому-то "событию" создаете запись в БД, которая содержит количество этого товара на этом складе? А она и есть этот "объект". И с удалением то же самое.


            1. DAleby
              27.07.2019 14:38

              Как ни странно, это совершенно не нужно для того, чтобы реализовать написанное выше предложение
              Да, тут согласен. Но что я вообще хотел бы заметить: можно обсуждать какую-то языковую конструкцию в отрыве от остальной части языка и придумать красивое и элегантное решение, которое абсолютно не будет вписываться в уже имеющийся синтаксис. Редко бывает, что есть изменение в языке, не несущее минусов (хотя бы просто минус от добавления новой конструкции, которую нужно учить). Но это так, лирическое отступление.

              Ну, вы же по какому-то «событию» создаете запись в БД, которая содержит количество этого товара на этом складе? А она и есть этот «объект». И с удалением то же самое.

              Да, если свойство DATA или материализованное, то создаем, для остальных нет.
              Ну и я, наверное, даже не столько про физическую сторону вопроса говорил, сколько про логическую. Создается ведь куча дополнительных логических связностей между такими классами, разве нет? (я сейчас не про вариант с кортежами).


              1. lair
                27.07.2019 14:44

                Но что я вообще хотел бы заметить: можно обсуждать какую-то языковую конструкцию в отрыве от остальной части языка и придумать красивое и элегантное решение, которое абсолютно не будет вписываться в уже имеющийся синтаксис.

                Это, к сожалению, может означать, что и имеющийся синтаксис недостаточно выразителен (потому что именно с этого началось обсуждение).


                Это не значит, что платформа плоха, это просто значит, что язык — для кого-то, потому что выразительность вообще субъективна — недостаточно выразителен. Ну да, так бывает.


                Ну и я, наверное, даже не столько про физическую сторону вопроса говорил, сколько про логическую.

                А с логической точки зрения вам не надо создавать или удалять какие-то объекты для работы с ними.


                Создается ведь куча дополнительных логических связностей между такими классами, разве нет?

                Ну так это, возможно, и правильно: не зря же эти связи видны в параметрах?


          1. michael_vostrikov
            27.07.2019 14:06
            +1

            А мне кажется предложение для всех комбинаций классов создавать новый класс — это как раз делать класс центральной сущностью, как в условной Java.

            Я нигде такого не предлагал. Классы создаются в соответствии с предметной областью, а не в соответствии с комбинациями в коде. Ну и да, к синтаксису задания свойств одного класса это не имеет никакого отношения.


            Это позволяет не создавать объекты (товар, склад) (объекты же будут у этого класса?), потому что с этими объектами есть сложности, например, их когда-то нужно создавать и удалять.

            А вот здесь мы возвращаемся к вопросу, который я задал ранее. Где у вас хранятся значения для таких свойств? Или они могут быть только вычисляемыми? И конкретно по этому примеру, как у вас хранится факт, что товар находится на складе?


            1. lair
              27.07.2019 14:27

              И конкретно по этому примеру, как у вас хранится факт, что товар находится на складе?

              Или вот возьмем более интересный пример, из соседней статьи.


              CLASS Person;
              likes = DATA BOOLEAN (Person, Person);
              friends = DATA BOOLEAN (Person, Person);

              Как это хранится, и почему? И что надо сделать, если нам надо хранить не только факт дружбы двух персон, но и еще набор атрибутов отдельно для их дружбы (дата начала) и их "нравится" (видимость, в значении "кто может видеть").


            1. DAleby
              27.07.2019 15:23

              Ну и да, к синтаксису задания свойств одного класса это не имеет никакого отношения.

              Да, можно сделать отдельный синтаксис для частного случая, можно даже и не для частного сделать, как я предлагал в другом комментарии. Тут вопрос, насколько это будет оправдано. Мое личное мнение, которое может не совпадать с политикой компании :), что частный случай точно не нужно добавлять в язык, а перед добавлением общего случая нужно оценить, насколько этот дополнительный синтаксис вообще вписывается в логику, грамматику языка, насколько нужно будет изменять другие конструкции, не будет ли стилистического винегрета (его и так у нас хватает по разным причинам), что нужно сделать, чтобы поддержать это в плагине к intellij.

              Где у вас хранятся значения для таких свойств?
              В таблице с несколькими ключевыми полями, это есть в этой статье, которую мы обсуждаем.


              1. michael_vostrikov
                27.07.2019 15:46

                Нет, давайте конкретно. Вот вы говорите про общее свойство currentBalance у Sku и Stock. Допустим оно не вычисляется, а просто вводится пользователем. В базе будут 3 таблицы — sku, stock, sku_stock? Или как?


                1. DAleby
                  27.07.2019 15:54

                  Да, примерно так.


                  1. michael_vostrikov
                    27.07.2019 16:07
                    +1

                    И этот currentBalance будет храниться в sku_stock? Ну вот и третий класс, в который это свойство надо поместить.


                    1. DAleby
                      27.07.2019 17:26

                      Я сказал «примерно так». В другой ситуации, когда, например, Sku и Stock — это некие производные классы, физическая модель может быть не такой. И эту физическую модель мы можем менять в продакшене.


                  1. lair
                    27.07.2019 17:02

                    Вот эта третья таблица sku_stock и есть та самая сущность, про которую NitroJunkie утверждает, что ее нет.


              1. lair
                27.07.2019 17:01

                В таблице с несколькими ключевыми полями, это есть в этой статье, которую мы обсуждаем.

                А в приведенном мной примере?


                CLASS Person;
                likes = DATA BOOLEAN (Person, Person);
                friends = DATA BOOLEAN (Person, Person);


      1. user_man
        27.07.2019 14:00

        >> свойство, зависящее, например, от товара и склада при нашем подходе не «принадлежит» ни товару, ни складу

        А в чём проблема ввести дополнительную сущность? Ну и «принадлежать» только ей.


        1. NitroJunkie Автор
          27.07.2019 14:11

          Бритва Оккама

          Принцип «бритвы Оккама» состоит в следующем: если некое явление может быть объяснено двумя способами: например, первым — через привлечение сущностей (терминов, факторов, фактов и проч.) А, В и С, либо вторым — через сущности А, В, С и D, — и при этом оба способа дают одинаковый результат, то следует предпочесть первое объяснение. Сущность D в этом примере лишняя, и её привлечение избыточно.


          1. user_man
            27.07.2019 14:18
            +1

            Бритвой Оккама можно отсечь самое нужное, а потом мило улыбаясь, заявить — я ведь всё «по науке» обосновал!

            По другому — Оккам за вас ничего не доказывает.


            1. NitroJunkie Автор
              27.07.2019 14:23

              Подождите, вы сказали:

              А в чём проблема ввести дополнительную сущность? Ну и «принадлежать» только ей.

              Проблема в том что: «Не следует привлекать новые сущности без крайней на то необходимости». А тут не то что крайней, тут вообще необходимости нету.


              1. lair
                27.07.2019 14:24
                +1

                Неа. Это вы не видите необходимости, но это не значит, что ее нет. Потому что эта сущность (в общем смысле слова, не в ER-терминологии, в ER-терминологии это будет связь) есть.


                1. NitroJunkie Автор
                  27.07.2019 14:37

                  Ну то есть давайте абстрагируем сущность до того чтобы она перестала что-либо значить. Потому как если связь тоже сущность, тогда все что угодно — сущность и бритва Оккама теряет смысл. Гениально. Софистика в чистом виде.


                  1. lair
                    27.07.2019 14:40

                    Ну то есть давайте абстрагируем сущность до того чтобы она перестала что-либо значить.

                    Неа, я этого не предлагал. Например, мне кажется, что в контексте вашей системы очень легко выделить "сущность": это все, чему соответствует явная (а не материализованная) таблица в БД.


                    Софистика в чистом виде.

                    К сожалению, софистикой занимаетесь вы, отказываясь признавать что-то сущностью, но при этом не дав сначала формального определения, что сущностью считается.


                    1. NitroJunkie Автор
                      27.07.2019 14:59

                      Неа, я этого не предлагал. Например, мне кажется, что в контексте вашей системы очень легко выделить «сущность»: это все, чему соответствует явная (а не материализованная) таблица в БД.

                      У нас можно написать TABLE objects (Object). И объекты всех классов (как и свойства с одним параметром) лягут в одну таблицу. Ну и есть еще числа, даты, которые тоже по сути сущности. Поэтому говорить что сущность соответствует таблице не совсем правильно. Хотя какая то логика тут есть.
                      К сожалению, софистикой занимаетесь вы, отказываясь признавать что-то сущностью, но при этом не дав сначала формального определения, что сущностью считается.

                      Сущность — это объект. То есть ровно тоже самое что подразумевается под объектом в том же C#. И вы же понимаете, что никаких объектов «Товар на складе» в C# нет.


                      1. lair
                        27.07.2019 17:00

                        У нас можно написать TABLE objects (Object).

                        Это другое.


                        Поэтому говорить что сущность соответствует таблице не совсем правильно.

                        А я и не говорю, что сущность соответствует таблице. Я говорю, что сущность — это все, чему соответствует таблица.


                        Сущность — это объект. То есть ровно тоже самое что подразумевается под объектом в том же C#.

                        В этом смысле ваши классы — не сущности. Потому что у ваших классов нет ни состояния, ни поведения.


                        И вы же понимаете, что никаких объектов «Товар на складе» в C# нет.

                        Ровно наоборот же: в типовой реализации ORM эта сущность будет:


                        class ItemAvailability
                        {
                          Item Item;
                          Warehouse Warehouse;
                          decimal Qty;
                          Unit UOM; 
                        }


    1. NitroJunkie Автор
      26.07.2019 22:57

      Я собственно писал про это в первой статье, что инкапсуляцию можно добавить в язык, но это будет чисто синтаксическим сахаром. Самой платформе для реализации этот this не нужен, так как он не нужен SQL (которому фиолетово таблица от одного или от пяти ключей). И для того же полиформизма он тоже не нужен, полиморфизм в lsFusion в принципе по другому реализуется, не через виртуальные таблицы, где ключом ровно один параметр.

      Кстати в вашем примере и this. собственно тоже не нужен. Да есть проблема с тем что hostTeam может быть параметром, но можно просто считать что параметры приоритетнее, вроде как локальные переменные в современных языках приоритетнее field'ов.


      1. michael_vostrikov
        27.07.2019 07:02
        +1

        Самой платформе для реализации этот this не нужен, так как он не нужен SQL

        Самой платформе зато нужен Game game, это то же самое, что this. Не аналогия, а именно абсолютно то же самое, только обозначается другими буквами.


        Кстати в вашем примере и this. собственно тоже не нужен

        Естественно, я просто заменил всё один-к-одному.


      1. user_man
        27.07.2019 14:10
        +1

        >> этот this не нужен, так как он не нужен SQL (которому фиолетово таблица от одного или от пяти ключей)

        Ну вот, опять во всём всплывают уши SQL. То есть у вас неявная конвертация в SQL тянет за собой кучу последствий. Или по другому — скорее всего вы не имели никакой внятной картины будущей системы и ваяли «от души», то есть просто поглядывая, как ваше текущее желание ляжет на SQL. Ну и получили не стройное здание, а некую обвязку вокруг SQL, которую теперь всячески притягиваете за уши к чему-то стройному. Получилось не модель — реализация, а реализация — модель. С ног на голову. Поэтому везде вылазят уши SQL.


        1. NitroJunkie Автор
          27.07.2019 14:14

          SQL никогда не был первичен. Более того первые реализации вообще не под SQL были. Тут как раз работала бритва оккама — в которой не надо выбирать это товар лежит на складе или склад содержит товар.


  1. DAleby
    26.07.2019 22:33

    del