Перейти к основному содержанию
`Ctrl`+`K`

# Языки разработки

Основными языками разработки являются

- scala
  Для программирования автономной и интерактивной бизнес логики.

- jexl
  Для программирования динамических скриптов.

- PL/pgSQL
  Для обработки данных в БД

## Scala

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

- Основы языка

- Гайд по коллекциям
  Может быть полезен, так как данные библиотеки очень удобны при манипуляциях со строками, к примеру операции сортировки и фильтрации

- Руководство scala: Типы коллекций

- Выбор последовательности

Прикладные разработчики в основном используют процедурную парадигму, что позволяет избежать сложного порога вхождения со стороны разработчиков `java`

При этом `scala` увеличивает производительность программистов за счет:

- Наличие удобной библиотеки коллекций

- Инлайн классов
  Позволяет использовать специализированную арифметику при работе с null типами, при этом не перегружая сборщик мусора

- Интерполяции строк
  Облегчает работу с sql запросами

- Трейтов
  Позволяют реализовать наследование отображений

- Прозрачного механизма инъекций зависимости

      //При этом автоматически пробрасывается контекст сессии базы данных.
      //Использование фабрик позволяет прозрачно перекрывать контроллеры бизнес логики
      //в итоговом решении при необходимости
      SomeApi().load(id)

- Фабрик контроллеров
  За счет того что обращения к любому контроллеру идет не статично, а через фабрику(`SomeApi()`), возможно динамическое аспектирование(к примеру запись макросов) и проектное расширение кода (подмена базового контроллера его проектным наследником)

### Обработка исключений

При обработке исключений нельзя перехватывать системные сообщения. Системные исключения всегда должны быть проброшены на верх. Это в том числе позволяет при необходимости прерывать сессию.

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

#### Объявление исключений

Прикладные исключения объявляются только как наследники от класса AppException или от потомков класса AppException.

При объявлении класса исключения нужно создать класс и фабрику.

    class ApiException(params:AnyRef) extends AppException(params)
    object ApiException extends ExceptionFactory(new ApiException(_))

#### Выбрасывания исключения

Для выбрасывания исключения используется конструкция

    throw AppException(args)

Внимание

Все прикладные исключения должны создаваться только от фабрики.

#### Перехват исключений

Перехватываться могут только прикладные исключения, если необходима принудительная очистка ресурсов в любом случае используйте секцию `finally`

Пример:

    try App{
      println("start")
      throw AppException("Ups")
    }catch {
      case e:AppException =>
        e.raise("Ups2")
    }finally{
      println("end")
    }

Внимание

- Используйте `try app`
  Это позволит гарантировано получать `AppException`

- При необходимости продолжить выбрасывания сообщения используйте конструкцию `e.raise`

- При необходимости выбросить новый тип сообщения используйте конструкцию
  `e.raise(ApiException,"Ups2")`

Данная практика позволит избегать потери стека.

#### Конвертация java исключений в прикладные исключения

Все прикладные исключения являются наследниками от `AppException`.

При необходимости типизированной обработки java исключений в прикладном коде, необходимо выполнять конвертацию java исключений в прикладные исключения, что делается в функции:

    ru.bitec.app.gtk.exception.App#apply

## Jexl скрипты

Для выполнения динамического кода, существует jexl расширение.

Возможно выполнить jexl скрипт из:

- Ssh консоли

      login user/password@db
      jexl
      doSomeThing()
      /

- Api

      JexlScript().eval(“doSomeThing”)

- Бизнес приложения, если у пользователя есть привелегии администратора
  Используйте пункт меню
  `Сервис > Инструменты > Выполнить jexl скрипт`

### Обработка исключений в jexl

Jexl в ядре не поддерживает исключения, механизм исключений реализован через аннотации

пример:

    @begin{
      logInfo("ok")
    }@exception function(e){
      rollback()
    } end

Для бросания исключения можно воспользоваться командой `raise`:

    raise(“Текст исключения”);

### Добавленные методы

- `lpad`
  Дополнение строки символами слева

- `rpad`
  Дополнение строки символами справа

- `flush()`
  Выполняет session.flush

- `commit()`
  Выполняет session.commit

- `rollback()`
  Выполняет session.rollback

- `raise`
  Бросает исключение AppException с указанным текстом

- `nvl`
  Возвращает первый параметр, если он не null, иначе второй параметр. Поддерживает nullable-типы

- `isNull`
  Проверка на null указанного параметра

- `isNotNull`
  Проверка, что параметр не null

- `pgArrayToNLongList`
  Преобразует массив Long в список NLong

Примечание

Для просмотра списка поддерживаемых методов смотрите:\

`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`

- `toTimeStampStr`
  Конвертирует дату к формату `dd.MM.yyyy HH:mm:ss` и затем формирует строку для подставки в SQL запрос

- `+`,`-`
  Используются для изменения даты, операторы добавляют или отнимают от указанной даты количество дней. Например `sysDate() + 1` вернет дату на один день больше текущей

### Работа с sql

Для работы с sql используется команды

- `sql(sqlText:String)`
  Для запросов на чтение

- `tsql(sqlText:String)`
  Для запросов на запись

Основные функции:

- `execute`
  Выполняет sql комманду

- `asList`
  Выполняет запрос, и возвращает список строк

- `asSingle`
  Возвращает запись

- `foreach`
  Принимает на вход функцию для потоковой обработки запроса

Пример

    var l= sql("select 1 d").asList()
    for(r:l){
      logInfo(r.d)
    }

Если вы используете интерполяцию в sql запросе, то для дат нужно обязательно указывать маску, как при конвертации в строку, так и в самом запросе:

    override protected def selectStatement: String = {
      val dvDate = NDate.now()
      s"select to_timestamp('${dvDate.toString("dd.MM.yyyy HH:mm:ss")}', 'DD.MM.YYYY HH24:MI:SS') as dDateTime"
    }

Для удобства можно использовать метод `toTimeStampStr`, то есть предыдущий запрос будет выглядеть так:

    override protected def selectStatement: String = {
      val dvDate = NDate.now()
      s"select ${dvDate.toTimeStampStr} as dDateTime"
    }

### Работа в контексте выборки

Контекст выборки (JexlSelScript) – расширяет контекст бизнес-логики возможностью работы с выборками и пользовательским интерфейсом. Используется в тех случаях, когда необходимо получить данные из полей для пользовательского ввода. Пример методов, доступных из jexl-скрипта контекста выборки:

- `varExists`
  Проверка наличия переменной в выборке(если не найдено, пойдет поиск в мастер-выборках)

- `selfVarExists`
  Проверка наличия переменной только в текущей выборке

- `setVar`
  Установить значение переменной(если не найдено, пойдет поиск в мастер-выборках)

- `setSelfVar`
  Установить значение переменной только в текущей выборке

- `getVar`
  Получить значение переменной текущей выборки(если не найдено, пойдет поиск в мастер-выборках)

- `getSelfVar`
  Получить значение переменной только из текущей выборки

- `addVar`
  Добавить переменную в выборку с установкой значения

- `newForm`
  Создание форм

Пример:

      //Получаем значение из атрибута "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

### Написание библиотек

Можно получить библиотеку с помощью `lib('название либы')`

Примечание

Посмотреть на существующие или написать свою можно в «Настройка системы» -> «Настройки и сервисы» -> «Сервисы JEXL» -> «Функции JEXL» Класс `Btk_JexlFunctionLib`

Пример либы «test»:

      // Магическое число 10
      let value = () => 10;
      /*
      * Складывает числа
      * @param x: Первое число
      * @param y: Второе число
      * @return результат
      */
      let func = (x, y) => x + y;

Пример вызова:

      let testLib = lib(`test`);
      let ten = testLib.value();
      let res = testLib.func(2, 2);

## PL/pgSQL

Язык обработки бизнес логики на стороне базы данных. Применяется в случаи если выполнение логики на сервере приложений не рационально в следствие высокой нагрузки на сеть или диск. Типичными примерами является:

- Выполнение агрегаций

- Построения аналитических запросов

- При фильтрации и сортировки больших данных

## Требования к масштабируемости

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

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

Типовыми вариантами сохранения масштабируемости методов являются:

- Работа через объектный кэш с возможностью его предварительной загрузки
  При этом кэш должен загружаться batch запросами, что значительно уменьшает нагрузку на базу

- Массовая обработка объектов
  При этом обработка разбивается на порции, каждая порция данных вытаскивается и сохраняется batch запросами

## Общие принципы наименования

Global3-FrameWork является регистро-зависимым. Это означает что имена сущностей, свойств, методов и т.д. нужно указывать строго так, как они объявлены.

Предупреждение

Btk_Class и BTK_Class это разные сущности с точки зрения фреймворка

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

Общие принципы по работе с кодом:

- Помните! Код чаще читается, чем пишется, поэтому не экономьте на понятности и чистоте кода ради скорости набора.

- Не используйте подчеркивание для отделения слов внутри идентификаторов. Подчеркивание используется только после имени модуля.

- Старайтесь не использовать сокращения лишний раз, помните о тех, кто читает код.

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

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

- Предпочтительно использовать имена, которые ясно и четко описывают предназначение и/или смысл сущности.

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

### Конвенция для разработки на scala

Стандартный код именуется в соответствии с руководством по языку

## Сокращения

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

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

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

Широко распространенные акронимы используйте для замены длинных фраз. Например, UI вместо User Interface или Olap вместо On-line Analytical Processing.

Если имеется идентификатор длиной менее трех букв, являющийся сокращением, то его записывают заглавными буквами. Имена длиннее двух букв записывайте в стиле Паскаль Xml, xmlDocument.

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

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

VolWithLost — это основной смысл настройки, и его сокращать нельзя ни в коем случае

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

В итоге, получаем сокращение idEnVolWithLostPT, с которым намного проще работать, чем с idEnergyVwltParamType

## Scala типы для работы с данными

Для удобной обработки данных язык Scala расширен null типами. Null типы, добавляют null логику к стандартным java типам, а так же расширяют их функциональность.

Задачи null типов:

1. Определить стандартные операции

2. Исключить `Null pointer exception`

3. Сделать арифметику более компактной

Для большей наглядности достаточно взглянуть на методы типов, к примеру для `NNumber`:

    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`, однако в нем отсутствуют специализированные методы под каждый тип.

Пример использования:

    //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

Используется для арифметики дат, к примеру:

    dvNewDate = dvDate + (10.hours+5.mins)

Фабрики: seconds, minutes, hours, second, minute, hour

## Наименование переменных для nullable типов и атрибутов

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

    If ( (idvSome1 === idvSome2 ) &&(nvNumber > 0.nn) {

    }

В данном примере, если не использовать null логику, операция nvNumber `>0.nn` выдаст исключение, при nvNumber равном null. Поэтому, использование null логики требует ввести эквивалент операции сравнения.

Так как, невозможно перегрузить оператор `==`, если он возвращает другой тип или используется значимый класс. Создан стандартный оператор `===`.

Внимание

Очень легко перепутать `===` и `==`, поэтому использование расширенных стандартов наименования для null типов обязательно.

Правила наименования переменной:

    [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`

## Правила наименования сущностей

### Наименование классов

    [Имя модуля]_[Аббревиатура логического пакета][Системное имясущности]

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

### Наименование пакетов

По аналогии с классами:

    [Имя модуля]_[Аббревиатура логического пакета][Наименование сущности][pkg]

### Наименование процедур и функций

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

Старайтесь избегать неоднозначных глаголов:

Не правильно:

- `сheckItem` - проверить элемент
  Абсолютно не сообщает, что произойдет в результате отработки данного метода: будет выдано исключения или возращен результат.

Правильно:

- `validateItem` - гарантирует корректность элемента, в случае несоответствия - генерирует исключение

- `isItemValid` - возвращает истину если элемент корректен

- `itemErrCode` - Возвращает код ошибки

- `deleteItem` - удалить элемент

- `createOrder` - создать заказ

### Наименование параметра

Из имени и типа параметра должны быть понятны его назначение и смысл.

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

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

### Наименование временных таблиц

    [Имя модуля]_[Аббревиатура логического пакета][Системное имя][gtt]

### Наименование выборок

    [Имя модуля]_[Аббревиатура логического пакета][Системное имя]

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

### Наименование отображений

    [Системное имя]_[группа зависимости]

Где:

- `[группа зависимости]`
  Опциональный параметр, если данные выборки имеют внешние зависимости

Примеры зависимостей:

- idDoc
  Выборка отображает детализацию по документу

- TurnOver
  Детализация к оборотной ведомости

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

### Формат комментария в систему контроля версий

    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 проверяет наличие строки в кэше и если ее нет, загружает строку из базы.