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

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

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

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

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

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

  • foreachRop
    Обход записей, возвращенных объектным запросом, например byParent

  • 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

  • +,-
    Используются для изменения даты, операторы добавляют или отнимают от указанной даты количество дней. Например 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)
}

Работа с массивами объектов навигации (Rop)#

Для обхода записей, возвращенных функцией byParent используется метод toJRops Основные функции:

  • asList
    Возвращает список строк

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

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

Пример

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
    Создание форм

Пример:

  //Получаем значение из атрибута "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 запросами, что значительно уменьшает нагрузку на базу

  • Массовая обработка объектов
    При этом обработка разбивается на порции, каждая порция данных вытаскивается и сохраняется 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 типы для работы с данными#

Для удобной обработки данных язык скала расширен 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 - Строка

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