Tarantool CE/EE Documentation portal logo
Помощь
Обновлена 15 сентября 2026 г. в 08:55

Прикладные роли

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

Роли можно разделить на следующие группы:

  • Встроенные роли Tarantool. Например, роль config.storage позволяет использовать набор реплик Tarantool в качестве хранилища конфигурации.
  • Роли, предоставляемые сторонними Lua-модулями. Например, модуль CRUD предоставляет роли roles.crud-storage и roles.crud-router, которые включают CRUD-операции в шардированном кластере.
  • Пользовательские роли, разрабатываемые как часть кластерного приложения. Например, можно создать пользовательскую роль для определения хранимой процедуры или реализации вспомогательного сервиса, такого как почтовый нотификатор или репликатор.

В этом разделе описывается разработка пользовательских ролей. Подробнее о включении и настройке ролей см. configuration_application_roles.

Предоставление конфигурации роли

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

В примере ниже показано, как включить и настроить роль greeter, реализация которой приведена в следующем разделе:

instance001:

Конфигурация роли, заданная в roles_cfg, доступна при валидации и применении этой конфигурации.

В состав Tarantool входит встроенный модуль experimental.config.utils.schema, предоставляющий инструменты для управления пользовательскими конфигурациями приложений (app.cfg) и ролей (roles_cfg). В примерах ниже показано его базовое использование.

Поскольку роль является Lua-модулем, имя роли передается в require() для получения модуля. При разработке приложения файл с кодом роли можно поместить рядом с файлом конфигурации кластера.

Создание пользовательской роли

Обзор

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

Создание пользовательской роли включает следующие шаги:

  1. (Необязательно) Определить схему конфигурации роли.
  2. Определить функцию, которая валидирует конфигурацию роли.
  3. Определить функцию, которая применяет провалидированную конфигурацию.
  4. Определить функцию, которая останавливает роль.
  5. (Необязательно) Определить роли, от которых зависит данная пользовательская роль.
  6. (Необязательно) Определить функцию обратного вызова on_event.

В результате модуль роли должен возвращать объект с соответствующими функциями и полями:

return {    validate = function() -- ... -- end,    apply = function() -- ... -- end,    stop = function() -- ... -- end,    dependencies = { -- ... -- },    on_event = function(config, key, value)        local log = require('log')        log.info('roles_cfg.my_role.foo: ' .. config.foo)        log.info('on_event is triggered by ' .. key)        log.info('is_ro: ' .. value.is_ro)    end,}

В примерах из этой статьи показано, как это сделать.

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

return {    validate = function() -- ... -- end,    apply = function() -- ... -- end,    stop = function() -- ... -- end,}

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

Определение схемы конфигурации роли

Встроенный модуль experimental.config.utils.schema предоставляет класс config-utils-schema_object. Объект этого класса определяет пользовательскую схему конфигурации роли или приложения.

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

local greeter_schema = schema.new('greeter', schema.record({    greeting = schema.scalar({        type = 'string',        allowed_values = { 'Hi', 'Hello' }    })}))

Если модуль не используется, пропустите этот шаг. В этом случае для обращения к значениям конфигурации роли используйте аргумент cfg функций validate() и apply(), например, cfg.greeting.

Валидация конфигурации роли

Для валидации конфигурации роли необходимо определить функцию validate().

В примере ниже функция validate() схемы конфигурации роли используется для валидации значения greeting:

local function validate(cfg)    greeter_schema:validate(cfg)end

Если конфигурация недействительна, validate() сообщает о неисправимой ошибке, выбрасывая объект ошибки.

Применение конфигурации роли

Для применения провалидированной конфигурации определите функцию apply(). Как и функция validate(), apply() предоставляет доступ к конфигурации роли через аргумент cfg.

В примере ниже функция apply() использует модуль log для записи значения из конфигурации роли в лог:

local function apply(cfg)    log.info("%s from the 'greeter' role!", greeter_schema:get(cfg, 'greeting'))end

Остановка роли

Для остановки роли используйте функцию stop().

В примере ниже функция stop() использует модуль log, чтобы указать, что роль остановлена:

local function stop()    log.info("The 'greeter' role is stopped")end

После определения всех функций роли необходимо вернуть объект с соответствующими функциями:

return {

Зависимости ролей

Для определения зависимостей роли используйте поле dependencies. В этом примере роль byeer имеет роль greeter в качестве зависимости:

-- byeer.lua --local log = require('log').new("byeer")return {    dependencies = { 'greeter' },    validate = function() end,    apply = function() log.info("Bye from the 'byeer' role!") end,    stop = function() end,}

Роль не может быть запущена без своих зависимостей. Это означает, что все зависимости роли должны быть указаны в конфигурационном параметре roles:

instance001:  roles: [ greeter, byeer ]

Полный пример доступен здесь: application_role_cfg.

Функция обратного вызова on_event

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

Функция обратного вызова on_event принимает 3 аргумента при вызове:

  • config — содержит конфигурацию роли;

  • key — отражает событие-триггер и принимает следующие значения:

    • config.apply — если функция обратного вызова вызвана при обновлении конфигурации;
    • box.status — если функция вызвана системным событием box.status.
  • value — содержит информацию о статусе экземпляра, как в системном событии-триггере box.status. Если функция обратного вызова вызвана при обновлении конфигурации, value содержит информацию о последнем системном событии box.status.

Пример функции обратного вызова on_event приведен в разделе Создание спейсов ниже.

Добавление кода инициализации

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

local function init()    -- ... --endinit()

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

См. также: Особенности создания спейсов.

Особенности создания спейсов

Для создания спейса в роли необходимо убедиться, что целевой экземпляр находится в режиме чтения-записи (значение box.info.ro равно false). Проверить состояние экземпляра можно, подписавшись на событие box.status с помощью box.watch():

box.watch('box.status', function()    -- creating a space    -- ...end)

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

Пример такого определения приведен ниже:

return {    validate = function() end,    apply = function() end,    stop = function() end,    on_event = function(config, key, value)        -- Can only create spaces on RW.        if value.is_ro then            return        end        -- Assume the role config is a table.        if type(config) ~= 'table' then            error('Config must be a table')        end        local space_name = config.space_name or 'default'        box.schema.space.create(space_name, {            if_not_exists = true,        })    end}

Жизненный цикл ролей

Жизненный цикл роли включает описанные ниже этапы.

  1. Загрузка ролей

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

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

  2. Остановка ролей

    Этот этап действует при перезагрузке конфигурации, когда роль удаляется из конфигурации для данного экземпляра. Обратите внимание, что все вызовы stop() выполняются до любых вызовов validate() или apply(). Это означает, что сначала останавливаются старые роли и только затем запускаются новые.

  3. Валидация конфигураций ролей

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

  4. Применение конфигураций ролей

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

Все функции роли сообщают о неисправимой ошибке, выбрасывая объект ошибки. Если на любом этапе возникает ошибка, применение конфигурации прекращается. Если при запуске или остановке роли возникает ошибка, последующие роли не останавливаются и не запускаются. Ошибка перехватывается и отображается в config:info() в разделе alerts.

Выполнение функций для зависимых ролей

Для ролей, зависящих друг от друга, функции validate(), apply() и stop() выполняются с учетом зависимостей. Предположим, есть три независимые и две зависимые роли:

role1role2role3    └─── role4             └─── role5
  • role1, role2 и role5 — независимые роли.

  • role3 зависит от role4, role4 зависит от role5.

Роли включены в конфигурации следующим образом:

roles: [ role1, role2, role3, role4, role5 ]

В этом случае validate() и apply() для этих ролей выполняются в следующем порядке:

role1 -> role2 -> role5 -> role4 -> role3

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

roles: [ role1 ]

После перезагрузки конфигурации функции stop() для удаленных ролей выполняются в следующем порядке:

role3 -> role4 -> role5 -> role2

Пример: роль без конфигурации

В примере ниже показано, как включить пользовательскую роль greeter для instance001:

instance001:  roles: [ greeter ]

Реализация этой роли выглядит следующим образом:

-- greeter.lua --return {    validate = function() end,    apply = function() require('log').info("Hi from the 'greeter' role!") end,    stop = function() end,}

Пример на GitHub: application_role

Пример: роль с конфигурацией

В примере ниже показано, как включить пользовательскую роль greeter для instance001 и задать конфигурацию для этой роли:

instance001:  roles: [ greeter ]  roles_cfg:    greeter:      greeting: 'Hi'

Реализация этой роли выглядит следующим образом:

local greeter_schema = schema.new('greeter', schema.record({    greeting = schema.scalar({        type = 'string',        allowed_values = { 'Hi', 'Hello' }    })}))
local function validate(cfg)    greeter_schema:validate(cfg)end
local function apply(cfg)    log.info("%s from the 'greeter' role!", greeter_schema:get(cfg, 'greeting'))end
local function stop()    log.info("The 'greeter' role is stopped")end
return {

Пример на GitHub: application_role_cfg

Пример: HTTP API

В примере ниже показано, как включить и настроить пользовательскую роль http-api:

instance001:  roles: [ http-api ]  roles_cfg:    http-api:      host: '127.0.0.1'      port: 8080

Реализация этой роли выглядит следующим образом:

-- http-api.lua --local httpdlocal json = require('json')local schema = require('experimental.config.utils.schema')local function validate_host(host, w)    local host_pattern = "^(%d+)%.(%d+)%.(%d+)%.(%d+)$"    if not host:match(host_pattern) then        w.error("'host' should be a string containing a valid IP address, got %q", host)    endendlocal function validate_port(port, w)    if port <= 1 or port >= 65535 then        w.error("'port' should be between 1 and 65535, got %d", port)    endendlocal listen_address_schema = schema.new('listen_address', schema.record({    host = schema.scalar({        type = 'string',        validate = validate_host,        default = '127.0.0.1',    }),    port = schema.scalar({        type = 'integer',        validate = validate_port,        default = 8080,    }),}))local function validate(cfg)    listen_address_schema:validate(cfg)endlocal function apply(cfg)    if httpd then        httpd:stop()    end    local cfg_with_defaults = listen_address_schema:apply_default(cfg)    local host = listen_address_schema:get(cfg_with_defaults, 'host')    local port = listen_address_schema:get(cfg_with_defaults, 'port')    httpd = require('http.server').new(host, port)    local response_headers = { ['content-type'] = 'application/json' }    httpd:route({ path = '/band/:id', method = 'GET' }, function(req)        local id = req:stash('id')        local band_tuple = box.space.bands:get(tonumber(id))        if not band_tuple then            return { status = 404, body = 'Band not found' }        else            local band = { id = band_tuple['id'],                           band_name = band_tuple['band_name'],                           year = band_tuple['year'] }            return { status = 200, headers = response_headers, body = json.encode(band) }        end    end)    httpd:route({ path = '/band', method = 'GET' }, function(req)        local limit = req:query_param('limit')        if not limit then            limit = 5        end        local band_tuples = box.space.bands:select({}, { limit = tonumber(limit) })        local bands = {}        for _, tuple in pairs(band_tuples) do            local band = { id = tuple['id'],                           band_name = tuple['band_name'],                           year = tuple['year'] }            table.insert(bands, band)        end        return { status = 200, headers = response_headers, body = json.encode(bands) }    end)    httpd:start()endlocal function stop()    httpd:stop()endlocal function init()    require('data'):add_sample_data()endinit()return {    validate = validate,    apply = apply,    stop = stop,}

Пример на GitHub: application_role_http_api

Справочник по API

Элементы

validate(cfg)

Валидация конфигурации роли.

apply(cfg)

Применение конфигурации роли.

stop(cfg)

Остановка роли.

dependencies

validate([cfg])

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

validate() должна выбрасывать ошибку, если валидация не пройдена.

Параметры:

  • cfg — конфигурация роли, которую нужно провалидировать. Этот параметр предоставляет доступ к параметрам конфигурации, определенным в roles_cfg.<role_name>. Для получения значений параметров конфигурации, находящихся вне roles_cfg.<role_name>, используйте config:get().

См. также: Валидация конфигурации роли.

apply([cfg])

Применение конфигурации роли. apply() вызывается после выполнения validate() для всех включенных ролей. Как и функция validate(), apply() вызывается при запуске экземпляра или при перезагрузке конфигурации для экземпляра с этой ролью.

apply() должна выбрасывать ошибку, если указанную конфигурацию невозможно применить.

  • cfg — конфигурация роли, которую нужно применить. Этот параметр предоставляет доступ к параметрам конфигурации, определенным в roles_cfg Для получения значений параметров конфигурации, находящихся вне roles_cfg.<role_name>, используйте config:get().

См. также: Применение конфигурации роли.

stop()

Остановка роли. Эта функция вызывается при перезагрузке конфигурации, если роль удалена из roles для данного экземпляра.

См. также: Остановка роли.

dependencies

(Необязательно) Определение зависимостей роли.

Тип возвращаемого значения

table

См. также: Зависимости ролей