Практические рекомендации по Interactor

Один основной интерфейс на тип процесса

Перед созданием интерфейса найдите существующие @ProcessInteractorReference с нужным typeIds. Два независимых интерфейса для одного типа процесса быстро расходятся по параметрам, сообщениям и правилам кеширования.

Основной интерфейс собирайте из небольших аспектов:

  • With* — связанные сущности или отдельная возможность: WithSingleCustomer, WithProcessType;

  • Has* — обязательная или характерная группа параметров: HasSingleAddress;

  • CanBe* — допустимый переход состояния: CanBeClosed.

interface HasInstallationAddress : ProcessInteractor {
    @BindAddressParam(PROCESS_PARAMETER_ADDRESS_ID)
    @NotNull(message = "В процессе #objectId не указан адрес")
    fun getAddress(): ParameterAddressValue

    @BindAddressParam(PROCESS_PARAMETER_ADDRESS_ID)
    fun getAddressOrNull(): ParameterAddressValue?

    @BindAddressParam(PROCESS_PARAMETER_ADDRESS_ID)
    @DropCache
    fun setAddress(value: ParameterAddressValue?)
}

Разделяйте обязательное и необязательное чтение

Если отсутствие значения допустимо не всегда, предоставьте два явно названных метода: nullable-метод без валидации и обязательный метод с бизнес-сообщением.

@BindProcessLinkedCustomers
fun getCustomerOrNull(): Customer?

@BindProcessLinkedCustomers
@NotNull(message = "К процессу #objectId не привязан контрагент")
fun getCustomer(): Customer

Для одиночной связи добавляйте @Size(max = 1), иначе обработчик вернёт первый элемент и скроет некорректную множественную привязку.

Кешируйте только в пределах короткой операции

@EnableCache хранит значения в памяти proxy и не знает об изменениях вне него. Не сохраняйте proxy надолго и не рассматривайте его как актуальную модель сущности.

На интерфейсе с кешированием помечайте @DropCache все setters и операции add, remove, clear, смену статуса и любые косвенные изменения. Сброс происходит до изменения и очищает весь кеш экземпляра.

Если операция может завершиться ошибкой после сброса, кеш всё равно останется пустым — это безопаснее, чем сохранить устаревшие значения.

Используйте сигнатуру как часть контракта

Обработчик выбирает действие не только по аннотации, но и по типам и имени метода. Придерживайтесь глаголов get, set, add/plus, remove/delete, clear, а специальные проверки называйте по правилам конкретного обработчика (can…​, isClosed…​, new…​).

Не добавляйте две @Bind*-аннотации на один метод: фабрика выбирает первое совпадение из своей таблицы, и результат станет зависеть от порядка регистрации.

Не скрывайте внешние вызовы

BindProcessTmcMoves и BindTmcUserLeftovers обращаются в Verdandi, а параметрические и процессные обработчики могут обращаться к БД через сервисы. Название бизнес-метода должно показывать, что данные загружаются, а вызывающий код должен учитывать ошибки интеграции и стоимость повторных вызовов.

Предпочитайте актуальные обработчики

Для конфигурации типа процесса используйте BindConfig; BindProcessTypeConfig устарел. Не используйте BindConfig для GroupInteractor, пока в GroupInteractionDispatcherFactory не будет исправлена привязка к обработчику конфигурации группы.

Не обходите ServerContext

В production-коде обычно достаточно текущего ServerContext, который берёт Interactor.create. В фоновых сценариях и тестах передавайте нужный контекст явно. Новый обработчик должен получать сервисы из этого контекста и соблюдать принятые границы DAO/сервисов.