Рубрики
Без рубрики

Целуя государственную машину на прощание

Альтернатива моделям конечных автоматов для сложного поведения. Помечен как конечный автомат, событийный ресурс, кодирование, java.

Недавно я писал об упрощении приложения с источником событий.

Статья начинается с кода из выступления Якуба Филемона и Кенни Бастани. И это заканчивается построением модели событий в коде: как они применяются и при каких условиях.

Образец заявления касается управления кредитными картами. Ты можешь:

  • Назначить кредитный лимит . Но только один раз, в противном случае приложение выдает исключение IllegalStateException .
  • Вывести деньги . Но вы не можете вывести более 45 средств за определенный цикл. Или вы тоже получите исключение.
  • Вернуть деньги

Я поиграл с классом Кредитная карта . У меня было предчувствие, что что-то может быть не так с методом вывода . Поэтому я написал тест, который проверяет правильное поведение.

@Test(expected = IllegalStateException.class)
public void withdrawWithoutLimitAssignedThrowsIllegalStateException() {
    CreditCard card = new CreditCard(UUID.randomUUID());
    card.withdraw(BigDecimal.ZERO);
}

В ходе теста предпринимается попытка вывести нулевую сумму. Но ранее кредитный лимит не был установлен. Приложение должно отклонить это и выдать исключение IllegalStateException . Вместо этого приложение выдало исключение NullPointerException .

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

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

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

Государственная машина спешит на помощь

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

Поэтому я решил создать модель конечного автомата UML для примера приложения. Сначала я спросил себя: хочу ли я иметь дело с командами или событиями в государственной машине?

Команды – это то, что приложение должно делать в будущем. События связаны с чем-то, что произошло в прошлом.

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

Синтаксис перехода на диаграмме – команда[условие]/Обработчик команд() . Это означает: когда объект команды получен и условие выполнено, если оно присутствует, обработайте команду и перейдите в следующее состояние.

Модель фиксирует, что разрешено, а что нет. Например: выплата возможна только после вывода средств.

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

Вот почему в конечном автомате намного больше повторений, чем в исходном коде с операторами if . Способ уменьшить количество повторений – использовать суперсостояния и подсостояния :

Поведение, зависящее от состояния, легко определить в модели конечного автомата. Но независимое от состояния правило, такое как в любом состоянии (когда выполняется условие X), день приводит к нескольким переходам. Например, мне нужно было добавить запрос На Закрытие цикла в каждое суперсостояние.

Вам нужны люди с нужными навыками для создания моделей. И нелегко общаться о моделях с нетехническими заинтересованными сторонами. Это не то, как они обычно говорят о путешествиях пользователей.

Прощаясь

Похоже, пока есть два варианта.

В левом углу: оператор if . Легко начать с этого. Низкие накладные расходы. Лучше всего подходит для приложений, в которых нет сложных потоков поведения. Но легко потерять обзор, когда поведение усложняется.

В правом углу: модель исполняемого конечного автомата. Мощный. Доказанный. Точный. Дает вам общее представление о поведении. Но трудно определить независимые от государства правила. И модели государственных машин трудно обсуждать с нетехническими заинтересованными сторонами.

Я стою в третьем углу. Я нашел альтернативу автоматам состояний. Решение тот

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

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

Model model = Model.builder()
  .useCase(useCreditCard)
    .basicFlow()
        .step(assigningLimit).user(requestsToAssignLimit).systemPublish(assignedLimit)
        .step(withdrawingCard).user(requestsWithdrawingCard).systemPublish(withdrawnCard).reactWhile(accountIsOpen)
        .step(repaying).user(requestsRepay).systemPublish(repay).reactWhile(accountIsOpen)

    .flow("Withdraw again").after(repaying)
        .step(withdrawingCardAgain).user(requestsWithdrawingCard).systemPublish(withdrawnCard)
        .step(repeating).continuesAt(withdrawingCard)

    .flow("Cycle is over").anytime()
        .step(closingCycle).on(requestToCloseCycle).systemPublish(closedCycle)

    .flow("Limit can only be assigned once").condition(limitAlreadyAssigned)
        .step(assigningLimitTwice).user(requestsToAssignLimit).system(throwsAssignLimitException)

    .flow("Too many withdrawals").condition(tooManyWithdrawalsInCycle) 
        .step(withdrawingCardTooOften).user(requestsWithdrawingCard).system(throwsTooManyWithdrawalsException)
.build();
return model;

Как вы можете видеть, модель в коде . Бегун модели выполняет эту модель. Бегун реагирует на команды/события, подобно конечному автомату.

Основной поток – это “сценарий счастливого дня”. Шаги пользователя для достижения своей цели. Другие потоки охватывают альтернативные сценарии и сценарии ошибок.

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

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

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

Взгляните на дополнительные примеры , чтобы копнуть глубже.

Когда использовать требования в качестве кода

Многие приложения имеют динамическое внутреннее поведение. Это относится, в частности, к распределенным приложениям. Им нужно смириться с тем фактом, что “другая сторона” недоступна.

Но с точки зрения пользователя эти приложения выглядят вполне предсказуемыми и обычными. Когда я хочу посмотреть шоу на Netflix или Amazon Prime, я каждый раз выполняю одни и те же действия, пока не смогу его посмотреть. Похоже, что один шаг просто следует за другим.

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

Как теперь работает приложение для кредитной карты

  • Клиент отправляет команду в корневой каталог Агрегата кредитных карт
  • Корневой каталог Агрегата кредитных карт использует хранилище событий для воспроизведения всех событий для кредитной карты, чтобы восстановить ее
  • Корень Агрегата кредитных карт использует описанную выше модель для отправки команды в метод обработки команд
  • Метод обработки команд создает событие и применяет его к экземпляру кредитной карты .
  • Модель обработки событий экземпляра CreditCard отправляет событие в метод изменения состояния

Вывод

Надеюсь, вам понравилась моя статья. Пожалуйста, скажите мне, что вы думаете в комментариях.

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

Если вы хотите быть в курсе того, что я делаю, или напишите мне записку, следуйте за мной дальше сеть LinkedIn или Твиттер . Чтобы узнать больше об гибкой разработке программного обеспечения, посетите мой онлайн-курс . Последнее редактирование 27 апреля 2020 г.: обновленный процесс поиска событий

Оригинал: “https://dev.to/bertilmuth/kissing-the-state-machine-goodbye-34n9”