# Языки разработки Основными языками разработки являются - [scala](https://www.scala-lang.org/) \ Для программирования автономной и интерактивной бизнес логики. - [jexl](https://commons.apache.org/proper/commons-jexl/reference/syntax.html) \ Для программирования динамических скриптов. - [PL/pgSQL](https://postgrespro.ru/docs/postgresql/10/plpgsql) \ Для обработки данных в БД ## Scala Язык разработки используемый для программирования бизнес логики сервера приложения. - [Основы языка](https://docs.scala-lang.org/tour/tour-of-scala.html) - [Гайд по коллекциям](https://docs.scala-lang.org/overviews/collections/introduction.html) \ Может быть полезен, так как данные библиотеки очень удобны при манипуляциях со строками, к примеру операции сортировки и фильтрации - [Руководство scala: Типы коллекций](https://docs.scala-lang.org/ru/scala3/book/collections-classes.html) - [Выбор последовательности](https://docs.scala-lang.org/ru/scala3/book/collections-classes.html#%D0%B2%D1%8B%D0%B1%D0%BE%D1%80-%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8) Прикладные разработчики в основном используют процедурную парадигму, что позволяет избежать сложного порога вхождения со стороны разработчиков `java` При этом `scala` увеличивает производительность программистов за счет: - Наличие удобной библиотеки коллекций - Инлайн классов \ Позволяет использовать специализированную арифметику при работе с null типами, при этом не перегружая сборщик мусора - Интерполяции строк \ Облегчает работу с sql запросами - Трейтов \ Позволяют реализовать наследование отображений - Прозрачного механизма инъекций зависимости ```scala //При этом автоматически пробрасывается контекст сессии базы данных. //Использование фабрик позволяет прозрачно перекрывать контроллеры бизнес логики //в итоговом решении при необходимости SomeApi().load(id) ``` - Фабрик контроллеров \ За счет того что обращения к любому контроллеру идет не статично а через фабрику(`SomeApi()`) возможно динамическое аспектирование(к примеру запись макросов) и проектное расширение кода(подмена базового контроллера его проектным наследником) ### Обработка исключений При обработки исключений нельзя перехватывать системные сообщения. Системные исключения всегда должны быть проброшены на верх. Это в том числе позволяет при необходимости прерывать сессию. Для упрощения корректной обработки исключений введены вспомогательные функции и классы. #### Объявление исключений Прикладные исключения объявляются только как наследники от класса AppException или от потомков класса AppException. При объявлении класса исключения нужно создать класс, и фабрику. ```scala class ApiException(params:AnyRef) extends AppException(params) object ApiException extends ExceptionFactory(new ApiException(_)) ``` #### Выбрасывания исключения Для выбрасывания исключения используется конструкция ```scala throw AppException(args) ``` ```{attention} Все прикладные исключения должны создаваться только от фабрики. ``` #### Перехват исключений Перехватываться могут только прикладные исключения, если необходима принудительная очистка ресурсов в любом случае используйте секцию `finally` Пример: ```scala try App{ println("start") throw AppException("Ups") }catch { case e:AppException => e.raise("Ups2") }finally{ println("end") } ``` ```{attention} - Используйте `try app` \ Это позволит гарантировано получать `AppException` - При необходимости продолжить выбрасывания сообщения используйте конструкцию `e.raise` - При необходимости выбросить новый тип сообщения используйте конструкцию `e.raise(ApiException,"Ups2")` Данная практика позволит избегать потери стека. ``` #### Конвертация java исключений в прикладные исключения Все прикладные исключения являются наследниками от `AppException`. При необходимости типизированной обработки java исключений в прикладном коде, необходимо выполнять конвертацию java исключений в прикладные исключения, что делается в функции: ```scala ru.bitec.app.gtk.exception.App#apply ``` ## Jexl скрипты Для выполнения динамического кода, существует [jexl](http://commons.apache.org/proper/commons-jexl/reference/syntax.html) расширение. Возможно выполнить jexl скрипт из: - Ssh консоли ```bash login user/password@db jexl doSomeThing() / ``` - Api ```text JexlScript().eval(“doSomeThing”) ``` - Бизнес приложения, если у пользователя есть привелегии администратора \ Используйте пункт меню `Сервис > Инструменты > Выполнить jexl скрипт` ### Обработка исключений в jexl Jexl в ядре не поддерживает исключения, механизм исключений реализован через аннотации пример: ```text @begin{ logInfo("ok") }@exception function(e){ rollback() } end ``` Для бросания исключения можно воспользоваться командой `raise`: ```text raise(“Текст исключения”); ``` ### Добавленные методы - `lpad` \ Дополнение строки символами слева - `rpad` \ Дополнение строки символами справа - `flush()` \ Выполняет session.flush - `commit()` \ Выполняет session. commit - `rollback()` \ Выполняет session. rollback - `raise` \ Бросает исключение AppException с указанным текстом - `nvl` \ Возвращает первый параметр, если он не null, иначе второй параметр. Поддерживает nullable-типы - `isNull` \ Проверка на null указанного параметра - `isNotNull` \ Проверка, что параметр не null - `foreachRop` \ Обход записей, возвращенных объектным запросом, например `byParent` - `pgArrayToNLongList` \ Преобразует массив Long в список NLong ```{note} Для просмотра списка поддерживаемых методов смотрите:\ `ru.bitec.app.gtk.jexl.session.JexlScriptContextExtension#ScopeBuilder` ``` ### Работа с датами Методы работы с датами: - `sysDate()` \ Текущая дата - `truncDate` \ Дата на начало дня от переданной - `truncYear` \ Дата на начало года от переданной - `toDate` \ Переводит строку в дату по указанному формату. Если формат не указан, то используется стандартный. Стандартный формат `dd.MM.yyyy` и `dd.MM.yyyy HH:mm:ss` - `toString`\ Переводит дату в строку по указанному формату. Если формат не указан, то используется стандартный. Стандартный формат `dd.MM.yyyy` и `dd.MM.yyyy HH:mm:ss` - `+`,`-` \ Используются для изменения даты, операторы добавляют или отнимают от указанной даты количество дней. Например `sysDate() + 1` вернет дату на один день больше текущей ### Работа с sql Для работы с sql используется команды - `sql(sqlText:String)` \ Для запросов на чтение - `tsql(sqlText:String)` \ Для запросов на запись Основные функции: - `execute` \ Выполняет sql комманду - `asList` \ Выполняет запрос, и возвращает список строк - `asSingle` \ Возвращает запись - `foreach` \ Принимает на вход функцию для потоковой обработки запроса Пример ```text var l= sql("select 1 d").asList() for(r:l){ logInfo(r.d) } ``` ### Работа с массивами объектов навигации (Rop) Для обхода записей, возвращенных функцией byParent используется метод toJRops Основные функции: - `asList` \ Возвращает список строк - `asSingle` \ Возвращает запись - `foreach` \ Принимает на вход функцию для потоковой обработки запроса Пример ```text var l= toJRops(Btk_SomeEntityApi.byParent(rop)).asList() for(r:l){ logInfo(r.id); logInfo(r.sSystemName) } ``` ### Работа в контексте выборки Контекст выборки (JexlSelScript) – расширяет контекст бизнес-логики возможностью работы с выборками и пользовательским интерфейсом. Используется в тех случаях, когда необходимо получить данные из полей для пользовательского ввода. Пример методов, доступных из jexl-скрипта контекста выборки: - `varExists` \ Проверка наличия переменной в выборке(если не найдено, пойдет поиск в мастер-выборках) - `selfVarExists` \ Проверка наличия переменной только в текущей выборке - `setVar` \ Установить значение переменной(если не найдено, пойдет поиск в мастер-выборках) - `setSelfVar` \ Установить значение переменной только в текущей выборке - `getVar` \ Получить значение переменной текущей выборки(если не найдено, пойдет поиск в мастер-выборках) - `getSelfVar` \ Получить значение переменной только из текущей выборки - `addVar` \ Добавить переменную в выборку с установкой значения - `newForm` \ Создание форм Пример: ```scala //Получаем значение из атрибута "id" выборки, приводим его к типу NString и пишем в переменную idTree var idTree = getVar("id").asNString() //Получаем атрибут DGLOBALENDDATE из мастер-выборки var dEndDate = getVar("super$DGLOBALENDDATE").asNDate() //Вызываем метод из пакета с передачей параметров //Обратите внимание, для обращения к Api или пакетам используются их короткие имена(без скобок) var fltCond = Act_UniversalReportPkg.getUniFilterCondByIdTree(idTree, dEndDate) //Вызывается ещё один метод, возвращающий объект scala-класса immutable.Map[NString, Any] var filters = Act_UniversalReportPkg.getFilterValues(idTree) /*Определение Map-ы внутри jexl-скрипта. Отметим, что Map внутри jexl и объект scala-класса Map (неважно mutable или immutable) - это разные объекты. Наиболее важным фактом является то, что передать scala-объект Map, полученный в предыдущей строке, в пакетный метод, принимающий scala-объект Map, в jexl-скрипте напрямую нельзя, именно поэтому приходится получать значения из переменной filters, перезаписывать их в jexl-Map param и потом передавать param в scala-метод. var param = {"flt_idDepOwner" : filters['flt_idDepOwner'], "flt_idAcc" : filters['flt_idAcc'], "flt_dFrom" : filters['flt_dFrom'], "flt_dTo" : filters['flt_dTo'], "flt_idAdjustMethod" : filters['flt_idAdjustMethod'], "flt_idAccCor" : filters['flt_idAccCor'], "uniFilterCondition_dz": fltCond} */ //Открываем новую форму с переданными параметрами Act_TransAvi.defList().newForm().params(param).open() ``` ### Контекстная справка В выборке выполнения jexl-скрипта есть возможность открытия выборки контекстной помощи, при нажатии на операцию «Помощь». В этой выборке в левой части поле ввода команд помощи, а права отображает справку. Если методы справки вызывать из ssh-консоли, то результат будет напечатан в консоль. #### Команды - `help` \ Справка по глобальным методам; - `listObj` \ Список доступных Api и Pkg - `describe` \ Описание Api иPkg ## PL/pgSQL Язык обработки бизнес логики на стороне базы данных. Применяется в случаи если выполнение логики на сервере приложений не рационально в следствие высокой нагрузки на сеть или диск. Типичными примерами является: - Выполнение агрегаций - Построения аналитических запросов - При фильтрации и сортировки больших данных ## Требования к масштабируемости При проектировании архитектуры приложения необходимо стремиться, чтобы горизонтальный рост системы не приводил к нелинейному увеличению потребностей в ресурсах. Типичным узким местом в такой ситуации является увеличение запросов в базу данных при увеличении данных, когда код написан таким образом что при обработки бизнес логики на каждую строчку данных идет запрос в базу данных. Типовыми вариантами сохранения масштабируемости методов являются: - Работа через объектный кэш с возможностью его предварительной загрузки \ При этом кэш должен загружаться [batch](https://www.eclipse.org/eclipselink/documentation/2.4/jpa/extensions/q_batch.htm) запросами, что значительно уменьшает нагрузку на базу - Массовая обработка объектов \ При этом обработка разбивается на порции, каждая порция данных вытаскивается и сохраняется batch запросами ## Общие принципы наименования Global3-FrameWork является регистро-зависимым. Это означает что имена сущностей, свойств, методов и т.д. нужно указывать строго так, как они объявлены. ```{warning} Btk_Class и BTK_Class это разные сущности с точки зрения фреймворка ``` При написании кода очень важно соблюдать общие правила, это позволяет улучшить взаимодействие между людьми и повысить эффективность. Общие принципы по работе с кодом: - Помните! Код чаще читается, чем пишется, поэтому не экономьте на понятности и чистоте кода ради скорости набора. - Не используйте подчеркивание для отделения слов внутри идентификаторов. Подчеркивание используется только после имени модуля. - Старайтесь не использовать сокращения лишний раз, помните о тех, кто читает код. - Старайтесь делать имена идентификаторов как можно короче (но не в ущерб читабельности). Главное, чтобы смысл идентификатора был понятен в используемом контексте. - Когда придумываете название для нового наименования, старайтесь не использовать имена, потенциально или явно конфликтующие со стандартными идентификаторами. - Предпочтительно использовать имена, которые ясно и четко описывают предназначение и/или смысл сущности. - Старайтесь использовать имена с простым написанием. Их легче читать и набирать. Избегайте (в разумных пределах) использования слов с двойными буквами, сложным чередованием согласных. Прежде, чем остановиться в выборе имени, убедитесь, что оно легко пишется и однозначно воспринимается на слух. Если оно с трудом читается, и вы ошибаетесь при его наборе, возможно, стоит выбрать другое. ### Конвенция для разработки на scala Стандартный код именуется в соответствии с [руководством по языку](http://docs.scala-lang.org/style/) ## Сокращения В основном сокращения использовать либо в горячих методах, либо в объектах базы данных, которые имеют ограничения. Старайтесь не использовать аббревиатуры или неполные слова в идентификаторах, если только они не являются общепринятыми. Например, пишите getClass, а не getCls. (Исключением являются базовые понятия модуля, на которые имеет смысл придумать аббревиатуры, т.к. изначально сложно предсказать каким количеством коллекций они могут обладать, что может привести к вынужденным сокращениям в дальнейшем) Не используйте акронимы, если они не общеприняты в области информационных технологий. Широко распространенные акронимы используйте для замены длинных фраз. Например, UI вместо User Interface или Olap вместо On-line Analytical Processing. Если имеется идентификатор длиной менее трех букв, являющийся сокращением, то его записывают заглавными буквами. Имена длиннее двух букв записывайте в стиле Паскаль Xml, xmlDocument. Если, все-таки, сокращение необходимо из-за ограничений в длине наименования или по другим причинам, старайтесь сокращать наименее информативные части в имени и наиболее часто используемые (легко запомнить). К примеру, в таблице настроек есть поле idEnergyVolWithLostParamType -Тип показателя расхода электроэнергии с потерями. Что из этого можно сократить: Energy - так как мы находимся в модуле энерго-учета, это поле обладает не большой информативностью и его можно сократить до En, по крайней мере, в контексте энерго-учета, легко вспомнить, что это электроэнергия. VolWithLost — это основной смысл настройки, и его сокращать нельзя ни в коем случае ParamType - тип параметра, так как в таблице настроек лежат в основном аналитики параметра учета, то на них можно придумать аббревиатуры. Название легко будет восстановить, помня о возможных аналитиках параметра учета. В итоге, получаем сокращение idEnVolWithLostPT, с которым намного проще работать, чем с idEnergyVwltParamType ## Scala типы для работы с данными Для удобной обработки данных язык скала расширен null типами. Null типы, добавляют null логику к стандартным java типам, а так же расширяют их функциональность. Задачи null типов: 1. Определить стандартные операции 2. Исключить `Null pointer exception` 3. Сделать арифметику более компактной Для большей наглядности достаточно взглянуть на методы типов, к примеру для `NNumber`: ```scala def round(value: NLong): NNumber = { if (this.isNull.isTrue || value.isNull.isTrue) this else NNumber(this.get.setScale(value.get.intValue(), RoundingMode.HALF_UP)) } ``` Альтернативой null типам в scala является класс `Option`, однако в нем отсутствуют специализированные методы под каждый тип. Пример использования: ```scala //Null типы val a1 = 1.nn val b1 = None.nn assert( (a1+b1).isNull ) //Option val a2 = Some(1) val b2: Option[Int] = None assert( (for(a<-a2;b<-b2) yield a+b).isEmpty ) ``` Любая колонка в таблице обрабатывается соответствующим ей null типом. При этом, происходит перегрузка стандартных операторов языка, создавая, таким образом, dsl расширение. Для создания констант используются соответствующие фабрики: ### NLong Используется для работы с идентификаторами. Фабрика: nl ### NNumber Используется для работы с числами Фабрика: nn ### NGid Используется для работы с глобальными идентификаторами Фабрика: ng ### NDate Используется для работы с датами. Фабрика: nd ### NString Используется для работы со строками. Фабрика: ns ### NBigDecimal Используется для работы с номерами. Внимание, обязательно используйте BigDecimal для выполнения расчетов. Использование чисел с двоичной арифметикой, может приводить к ошибкам. Фабрика: nn ### NDuration Используется для арифметики дат, к примеру: ```scala dvNewDate = dvDate + (10.hours+5.mins) ``` Фабрики: seconds, minutes, hours, second, minute, hour ## Наименование переменных для nullable типов и атрибутов Необходимо очень четко прослеживать, где работа идет с dsl, а где со стандартным типом. Наиболее яркий пример возможной, это операция сравнения. ```scala If ( (idvSome1 === idvSome2 ) &&(nvNumber > 0.nn) { } ``` В данном примере, если не использовать null логику, операция nvNumber `>0.nn` выдаст исключение, при nvNumber равном null. Поэтому, использование null логики требует ввести эквивалент операции сравнения. Так как, невозможно перегрузить оператор `==`, если он возвращает другой тип или используется [значимый класс](http://docs.scala-lang.org/overviews/core/value-classes.html). Создан стандартный оператор `===`. ```{attention} Очень легко перепутать `===` и `==` поэтому использование расширенных стандартов наименования для null типов обязательно. ``` Правила наименование переменной: ```text [Variable][a][t][Scope][Name][Suffix] ``` где: - `[Variable]` \ Определяет тип - `n` - Число - `s` - Строка - `j` - Строка в формате json - `d` - Дата - `r`,`x` - Запись - `u`,`cur` - Курсор - `l`,`blob` - Бинарные данные (bytea, blob) - `с` - Символьные данные (text, clob) - `b` - Булево значение - `id`- Идентификатор - `gid` - Глобальный идентификатор - `[a]` \ Определяет является ли переменная последовательностью - `[t]` \ Определяет пользовательский тип - `[Scope]` \ Область действия - `v` - Переменная процедуры - `g` - Переменная пакета (Как различать константы) - `p` - параметр процедуры - `[Name]` \ Имя переменной - `[Suffix]` \ Суффикс - `_dz` - Системные атрибуты - `_z` - Проектные атрибуты ### Примеры задания параметров - `dStart` - дата начала в таблице - `dvStart` - дата начала переменной процедуры - `dgMaxDate` - максимальная дата переменной пакета которую нельзя менять. - `tvdaDate` - тип коллекции дат объявленный в процедуре - `davDate` - коллекция дат объявленная в процедуре - `uvStudents` - курсор объявленный в процедуре. ## Наименования Scala-пакетов Всегда в нижнем регистре. Правильно: - `ru.bitec.deepspace` - `ru.bitec.deep_space` Не правильно: - `ru.bitec.deepSpace` ## Правила наименования сущностей ### Наименование классов ```text [Имя модуля]_[Аббревиатура логического пакета][Системное имясущности] ``` Так как, имя класса привязано к таблице, необходимо согласовывать наименование классов, с наименованием таблицы, что накладывает ограничение на имена. ### Наименование пакетов По аналогии с классами: ```text [Имя модуля]_[Аббревиатура логического пакета][Наименование сущности][pkg] ``` ### Наименование процедур и функций Используйте глаголы или комбинацию глагола и существительных и прилагательных для имен методов. Старайтесь избегать неоднозначных глаголов: Не правильно: - `сheckItem` - проверить элемент \ Абсолютно не сообщает, что произойдет в результате отработки данного метода, толи будет выдано исключения, толи возращен результат, Правильно: - `validateItem` - гарантировать корректность элемента \ в случае несоответствия генерирует исключение - `isItemValid` - возвращает истину если элемент корректен - `itemErrCode` - Возвращает код ошибки - `deleteItem` - удалить элемент - `createOrder` - создать заказ ### Наименование параметра Из имени и типа параметра должны быть понятны его назначение и смысл. Не усложняйте методы параметрами, которые, возможно, будут использоваться в будущих версиях. Если в будущем понадобится новый параметр, можно использовать перегрузку методов и значения по умолчанию. ### Наименование временных таблиц ```text [Имя модуля]_[Аббревиатура логического пакета][Системное имя][gtt] ``` ### Наименование выборок ```text [Имя модуля]_[Аббревиатура логического пакета][Системное имя] ``` Данный способ наименование позволяет, более легко выполнять рефакторинг, при перемещении выборки между пакетами. ### Наименование отображений ```text [Системное имя]_[группа зависимости] ``` Где: - `[группа зависимости]` \ Опциональный параметр в случаи если данные выборки имеют внешнии зависимости Примеры зависимостей: - idDoc \ Выборка отображает детализацию по документу - TurnOver \ Детализация к оборотной ведомости Не следует называть группу зависимости в несоответствии с тем, что реально происходит, так как это вводит в заблуждения, к примеру, название idDoc говорит о том, что можно использовать эту детализацию в любых выборках для детализации документа, однако, если запрос заточен под конкретную выборку мастера, это невозможно. ### Формат комментария в систему контроля версий ```text format ::= (attention | label)? text attention ::= add | rem | fix label ::= (feat| test| doc | err ..)* text ::= Краткий текст комментария. dp (при наличии) dp ::= #id - Документ поддержки ``` ## Стандартные сокращения ### Odm (Object domain model) Доменная модель сущности. Используется для описания метаданных классов фреймворка: Обозначение, наименование, тип класса, атрибутный состав, ссылочность и т.д. ### Orm (Object-Relational Mapper) Объектно-реляционное представление сущности. Сопоставляет объектную доменную модель и реляционную базу данных. Используется в EclipseLink ### Pojo Java объект на сервере приложения. Хранит значения атрибутов объекта класса (строки таблицы БД) в оперативной памяти. ### Dpi Доменная автономная бизнес логика (создается кода генератором). Обеспечивает для сущности базовую обвязку методами фреймворка. ### Dvi Доменная интерактивная бизнес логика (создается кода генератором). Обеспечивает для сущности базовые методы работы с пользовательским интерфейсом. ### Api Прикладная автономная бизнес-логика по классу. Содержит методы прикладной обработки данных для конкретной сущности. ### Pkg Автономная бизнес логика. Содержит методы прикладной обработки данных. В отличии от api не привязана к какой либо конкретной сущности. ### Avi Прикладная интерактивная бизнес логика. Содержит методы работы с пользовательским интерфейсом. ### Dvm (Domain view markup) Доменная разметка выборки (создается автоматически). Шаблон базового представления сущности в системе. ### Avm (Application view markup) Прикладная разметка выборки. Описывает базовое представление сущности в системе. ### Ata (Application table) Прикладная таблица. Определение таблицы на языке Scala. Что позволяет использовать подсказчик кода, и статическую проверку при написании объектных запросов. ### Aro (Application row) Прикладная строка. Определяет правила взаимодействия с сущностью (pojo) в EclipseLink. Предоставляет адаптированный для фреймворка способ взаимодействия с объектами EclipseLink. ### Rop (Row provider) Провайдер строки. Используется для гарантированного доступа к строке (Aro) через кэш. При переходе к редактору rop проверяет наличие строки в кэше, и, если ее нет загружает строку из базы.