Link Search Menu Expand Document

Акторы с состоянием

Введение

Данный раздел документации предназначен для описания принципов работы с акторами, у которых есть состояние (stateful actors).

Описание

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

Принципы и рекомендации

Обертка для конструктора

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

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

Пример

На практике описанное выше выглядит следующим образом. Конструктор имеет следующий вид:

public GetDocumentsExecutor(final GetDocumentsConfig wrapper) throws InitializationException {
    String connectionOptionsName = wrapper.getConnectionOptionsName();
    String connectionPoolName = wrapper.getConnectionPoolName();
    String collectionName = wrapper.getCollectionName();
    String searchTaskKey = wrapper.getSearchTaskKey();
    // и так далее
}

Обертка, соответственно, имеет следующий вид:

public interface GetDocumentsConfig {
    String getConnectionOptionsName() throws ReadValueException;
    String getConnectionPoolName() throws ReadValueException;
    String getCollectionName() throws ReadValueException;
    String getSearchTaskKey() throws ReadValueException;
}

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

IObject configurationObject = (IObject) args[0];

GetDocumentsConfig config = new GetDocumentsConfig() {

    @Override
    String getConnectionOptionsName() throws ReadValueException {
        return config.getValue(connectionOptionsFN);
    }

    @Override
    String getConnectionPoolName() throws ReadValueException {
        return config.getValue(connectionPoolFN);
    }

    @Override
    String getCollectionName() throws ReadValueException {
        return config.getValue(collectionNameFN);
    }

    @Override
    String getSearchTaskKey() throws ReadValueException {
        return config.getValue(searchTaskKeyFN);
    }
};

return new GetDocumentsExecutor(config);

Замечание: для упрощения примера тут не продемонстрировано формирование IFieldName, регистрация в IOC и обработка исключений.

Передача аргументов

Может показаться, что хорошим местом для передачи аргументов будет секция objects, где и происходит объявление актора. Но по факту это приводит к тому, что параметры актора при переиспользовании модуля в других проектах могут оказаться неизменяемыми. Для этих целей рекомендуется выносить все параметры актора в properties, а в секции objects оставить только путь до properties.

Пример

Рассмотрим актор для хранения информации о текущем подключении к БД. Первоначально его объявление в секции objects имело следующий вид:

{
    "name": "connection-options-storage",
    "kind": "actor",
    "dependency": "options storage",
    "url": "jdbc://localhost:5432/project_db",
    "username": "user",
    "password": "strong-password",
    "maxConnections": 20
}

Если по каким-то причинам нам пришлось перезагрузить сервер, или же модуль с этим актором переиспользуется на другом проекте, то единственная возможность поменять параметры - залезть в исходный код модуля и поправить их. Поэтому здесь мы заменяем параметры на путь до properties-файла.

{
    "name": "connection-options-storage",
    "kind": "actor",
    "dependency": "options storage",
    "properties": "properties/connectionOptionsStorage.properties"
}

И в файле connectionOptionsStorage.properties параметры лежат в следующем виде:

url = jdbc://localhost:5432/project_db
username = user
password = strong-password
maxConnections = 20

Управляющие хэндлеры

Когда сервер SmartActors запущен, единственная причина, по которой он должен перезагружаться - пропало электричество и сервер был перезагружен. Все остальное время он должен работать непрерывно. Это накладывает свои ограничения на акторы с состоянием, т.к. у администратора всегда должна быть возможность повлиять на состояние актора. Для этих целей разработчик актора с состоянием обязательно должен добавить управляющий (контрольный) хэндлер, который может повлиять на состояние.

Пример

Рассмотрим актор для отправки почты. У него в памяти должна лежать следующая информация - адрес почтового сервера, его порт и по какому протоколу идет отправка. Эта информация на старте сервера считывается из properties-файла. У актора есть один хэдлер - send(), который, собственно, и занимается отправкой почты. Выглядит это все следующим образом:

private String host;
private String port;
private String protocol;

public EmailSender(final EmailSenderConfig config) {
    // заполнение внутреннего состояние
}

public void send(final SendEmailMessage message) {
    // отправка почты через почтовый сервер, информация о котором лежит в памяти
}

Предположим, что почтовый сервер переехал на другой адрес. Мы заменили информацию о нем в properties-файле, но сервер сейчас занимается обработкой данных, которую прерывать никак нельзя. В таком случае единственный способ заменить информацию о почтовом сервере - вызвать управляющий хэндлер updateSettings().

private String host;
private String port;
private String protocol;

public EmailSender(final EmailSenderConfig config) {
    // заполнение внутреннего состояние
}

public void send(final SendEmailMessage message) {
    // отправка почты через почтовый сервер, информация о котором лежит в памяти
}

public void updateSettings(final UpdateSettingsMessage message) {
    this.host = wrapper.getHost();
    this.port = wrapper.getPort();
    this.protocol = wrapper.getProtocol();
}

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

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