Определение модели

Экран Dynamic modelDynamic model settings предоставляет административный UI для редактирования динамической модели. Вы добавляете сущности, атрибуты, перечисления и другие элементы через визуальный редактор, а затем применяете изменения с помощью кнопки Apply, чтобы проверить их и опубликовать новую версию модели.

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

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

Использование редактора модели

Откройте экран Dynamic model settings. Редактор Visual представляет текущую модель как структуру связанных таблиц с сущностями, атрибутами, перечислениями, экранами. Вы можете добавлять и редактировать элементы с помощью диалоговых окон.

В любой момент вы можете переключиться на представление Code, чтобы просмотреть или отредактировать YAML-определение напрямую; оба представления синхронизированы.

Когда модель готова, нажмите Apply. Фреймворк сравнивает отредактированную модель с активной в данный момент и открывает промежуточный экран Dynamic model changes, который показывает, что будет применено, прежде чем что-либо будет зафиксировано. В разделе Model migrations перечисляются все отдельные изменения — такие как создание сущности, добавление или удаление атрибута либо добавление ограничения уникальности. Если затронуты какие-либо экраны, они перечисляются в разделе Changed views.

Просмотрите список и нажмите Apply changes для подтверждения. Модель проверяется и, если она корректна, изменения применяются и публикуются как новая версия модели, которая сразу становится активной. Закрытие экрана Dynamic model changes без применения отменяет изменения и оставляет активную модель без изменений.

Расширение статических сущностей

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

Пример ниже добавляет строковый атрибут taxId к Customer с длиной 20, флагом обязательности, уникальностью и правилом валидации:

- name: "Customer"
  attributes:
    - name: "taxId"
      javaClass: "java.lang.String"
      length: 20
      required: true
      unique: true
      validation:
        constraints:
          - annotation: "Pattern"
            parameters:
              regexp: "^[A-Z0-9-]+$"
            message:
              en: "Tax ID can contain uppercase letters, digits and dashes only"
      messages:
        en: "Tax ID"

Определение динамических сущностей

Динамическая сущность — это совершенно новая сущность, существующая только во время выполнения; у неё нет Java-класса или таблицы в вашем исходном коде. Чтобы создать её, добавьте в редакторе сущность, имя которой не совпадает ни с одной существующей сущностью, затем определите её атрибуты и, при необходимости, её экраны. См. Экраны и пункты меню, где описано создание экранов и пунктов меню для динамических сущностей.

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

- name: "LoyaltyLevel"
  resourceRoles:
    read:
      - "employee"
      - "manager"
    create:
      - "manager"
    update:
      - "manager"
    delete:
      - "manager"
  messages:
    en: "Loyalty level"
    de: "Treuestufe"
  validation:
    constraints:
      - name: "discountDescriptionRequired"
        target: "entity"
        type: "expression"
        evaluator: "spel"
        expression: "discount == null || description != null"
        attributes:
          - "discount"
          - "description"
        path: "description"
        groups:
          - "UiCrossFieldChecks"
          - "RestApiChecks"
        message:
          en: "Description is required when discount is specified"
  attributes:
    - name: "name"
      javaClass: "java.lang.String"
      instanceName: true
      messages:
        en: "Name"
    - name: "description"
      javaClass: "java.lang.String"
      lob: true
      messages:
        en: "Description"
    - name: "publicSummary"
      javaClass: "java.lang.String"
      calculated:
        evaluator: "spel"
        expression: "(name ?: '') + ': ' + (description ?: '')"
        dependsOn:
          - "name"
          - "description"
      messages:
        en: "Public summary"
    - name: "discount"
      javaClass: "java.math.BigDecimal"
      validation:
        constraints:
          - annotation: "DecimalMin"
            parameters:
              value: "0"
          - annotation: "DecimalMax"
            parameters:
              value: "100"
      messages:
        en: "Discount"
  views:
    - type: "list"
      viewId: "LoyaltyLevel.list"
      viewRoute: "loyalty-levels"
      viewTitle:
        en: "Loyalty levels"
        de: "Treuestufen"
      templateParams:
        includeProperties: ["name", "discount"]
        excludeProperties: ["publicSummary"]
      resourceRoles:
        - "employee"
        - "manager"
      menuItem:
        parentMenu: "application"
        insertBefore: "Customer.list"
        title:
          en: "Loyalty levels"
        resourceRoles:
          - "employee"
          - "manager"
      descriptor:
        template: "default"
    - type: "detail"
      viewId: "LoyaltyLevel.detail"
      viewTitle:
        en: "Loyalty level"
      descriptor:
        template: "default"

Атрибуты

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

Атрибуты типа данных

Атрибут типа данных хранит простое значение, такое как строка, число, дата или булево значение. Вы определяете его, выбирая его тип, который хранится как javaClass. Для строковых атрибутов и атрибутов-массивов байтов вы также можете задать length и пометить их как большие объекты с помощью lob. Атрибут taxId, показанный в разделе Расширение статических сущностей, является атрибутом типа данных.

Полный список поддерживаемых типов см. в секции поддерживаемые типы.

Ссылочные атрибуты

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

- name: "loyaltyLevel"
  entityName: "LoyaltyLevel"
  messages:
    en: "Loyalty level"

Атрибуты-коллекции

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

- name: "benefits"
  entityName: "Benefit"
  collection: true
  messages:
    en: "Benefits"

Атрибуты перечисления

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

- name: "grade"
  enumeration: "CustomerGrade" # short name, resolved via base package
  messages:
    en: "Grade"

Перечисления

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

enumerations:
  - name: "CustomerGrade"
    messages:
      en: "Customer grade"
    values:
      - name: "PLATINUM"
        id: "30"
        messages:
          en: "Platinum"
      - name: "GOLD"
        id: "20"
        messages:
          en: "Gold"
      - name: "BRONZE"
        id: "10"
        messages:
          en: "Bronze"

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

Вычисляемые атрибуты

Вычисляемый атрибут доступен только для чтения и вычисляется при обращении на основе выражения, а не хранится. Вы пишете выражение на Spring Expression Language (SpEL) и перечисляете атрибуты, от которых оно зависит, в dependsOn, чтобы они загружались вместе с ним.

- name: "publicSummary"
  javaClass: "java.lang.String"
  calculated:
    evaluator: "spel"
    expression: "(name ?: '') + ': ' + (description ?: '')"
    dependsOn:
      - "name"
      - "description"
  messages:
    en: "Public summary"

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

Валидация

Вы можете прикрепить к атрибуту ограничения валидации, чтобы ограничить значения, которые он принимает. Каждое ограничение имеет тип (такой как Pattern, Size или DecimalMax), необязательные параметры и необязательное сообщение.

validation:
  constraints:
    - annotation: "Pattern"
      parameters:
        regexp: "^[A-Z0-9-]+$"
      message:
        en: "Tax ID can contain uppercase letters, digits and dashes only"

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

Ограничения уникальности

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

unique: true

Чтобы потребовать уникальности комбинации нескольких атрибутов, определите составное ограничение уникальности для сущности:

uniqueConstraints:
  - name: "customerCountryTaxIdUnique"
    attributes:
      - "countryCode"
      - "taxId"
    message:
      en: "Tax ID must be unique within a country"

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

Безопасность

Вы можете ограничить доступ к динамическим сущностям и атрибутам с помощью ресурсных ролей. Разрешения являются аддитивными — указание роли предоставляет ей соответствующий доступ, а доступ, который не предоставлен, запрещается. Указанные роли должны уже существовать в приложении; в приведённых ниже примерах используются иллюстративные роли employee и manager.

На уровне сущности вы предоставляете доступ read, create, update и delete. Это поддерживается для динамических сущностей:

resourceRoles:
  read:
    - "employee"
    - "manager"
  create:
    - "manager"
  update:
    - "manager"
  delete:
    - "manager"

На уровне атрибута вы предоставляете доступ view и modify:

- name: "countryCode"
  javaClass: "java.lang.String"
  length: 2
  resourceRoles:
    view:
      - "employee"
      - "manager"
    modify:
      - "manager"
  messages:
    en: "Country code"

Подробнее о ресурсных ролях см. в разделе Ресурсные роли.