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

Что такое конечные автоматы?


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

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

В чём польза конечных автоматов при бэкенд-разработке


Есть несколько причин, по которым конечные автоматы удобно использовать для бэкенд-разработки:
  • Простота: конечные автоматы упрощают проектирование и реализацию сложных потоков задач.
  • Масштабируемость: конечные автоматы хорошо приспособлены к масштабированию, могут обрабатывать большие объёмы данных и сложную логику. Кроме того, они легко интегрируются с другими системами и сервисами.
  • Надёжность: при помощи конечных автоматов удобно обеспечивать надёжность системы, навязывая соблюдение ограничений и гарантируя, что будут допускаться только валидные переходы. Так можно предотвращать ошибки и снижать риск системных отказов.
  • Гибкость: конечные автоматы отличаются гибкостью, и их легко приспосабливать к меняющимся бизнес-требованиям. Конечные автоматы легко обновлять или модифицировать, не затрагивая остальной части системы.

Какие вопросы затронуты в этом посте


В оставшейся части поста я объясню, как при помощи конечных автоматов решаются распространённые задачи, возникающие при бэкенд-разработки. В частности, коснёмся следующих тем:
  • Определения состояний и переходов
  • Действия, связанные с переходами
  • Валидация переходов
  • Долговременное хранение данных конечного автомата
  • Тестирование конечных автоматов

Проектирование конечного автомата


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

Идентифицировав таким образом все состояния, мы должны определить, какие события будут инициировать переходы между этими состояниями. В нашей системе аутентификации к числу таких событий могут относиться «пользователь сообщает учётные данные для входа в систему», «пользователь нажимает на кнопку выхода из системы» или «пользователь затребовал сброс пароля».

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

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

Проектирование конечного автомата в визуальном редакторе


Чтобы было тем проще спроектировать конечный автомат и схематически его изобразить, можно воспользоваться визуальным редактором с графическим пользовательским интерфейсом. Один из популярных инструментов такого рода — XState Visualizer, специально предназначенный для проектирования конечных автоматов в простом интерфейсе.

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

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

Реализация конечного автомата


Когда конечный автомат спроектирован, приходит время его реализовать. Чтобы проект получился успешным, чрезвычайно важно подобрать правильный фреймворк для разработки конечных автоматов. Есть несколько фреймворков для разных языков программирования, применяемых для такой работы, вот некоторые из них:
  • Python: PyTransitions, Automat
  • Java: StateMachineFramework, EasyFlow
  • C#: Stateless, Automatonymous
  • JavaScript: xstate, javascript-state-machine
В рамках этого поста воспользуемся библиотекой xstate, при помощи которой реализуем конечный автомат на Node.js.

Для начала установим xstate через менеджер пакетов npm:

npm install xstate

Установив xstate, настроим конечный автомат. Для этого определим для него исходное состояние и переходы между состояниями. В Xstate предоставляется простой синтаксис, при помощи которого можно определять конечные автоматы как JSON-объекты. Вот пример:

const signUpStateMachine = {
  id: 'signup',
  initial: 'idle',
  states: {
    idle: {
      on: { SIGNUP: 'pendingVerification' }
    },
    pendingVerification: {
      on: {
        VERIFICATION_SUCCESS: 'active',
        VERIFICATION_FAILURE: 'verificationFailed'
      }
    },
    verificationFailed: {
      on: { SIGNUP: 'pendingVerification' }
    },
    active: {
      type: 'final'
    }
  }
};

Свойство id — это уникальный идентификатор конечного автомата. В свойстве initial указано исходное состояние автомата, в данном случае это idle. В свойстве states содержатся все возможные состояния автомата и соответствующие переходы между ними.

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

const { Machine } = require('xstate');
const signUpMachine = Machine(signUpStateMachine);

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

const result = signUpMachine.send('SIGNUP');
console.log(result.value); // 'pendingVerification'

В данном примере мы инициировали событие SIGNUP, в результате которого конечный автомат переходит из состояния idle в состояние pendingVerification. Функция send возвращает объект, в котором содержится актуальное состояние автомата (result.value).

Тестирование конечного автомата


Тестирование – важнейший аспект разработки любых программ, и конечные автоматы — не исключение. В этом разделе обсудим различные типы тестирования, применимые с конечными автоматами для обеспечения их корректной и надёжной работы.

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

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

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

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

Продвинутые способы использования конечных автоматов в бэкенд-разработке


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

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

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

Заключение


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

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

Комментарии (18)


  1. devprodest
    27.04.2024 10:48
    +4

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


    1. Zenitchik
      27.04.2024 10:48

      Причём, узнал очень поверхностно.


  1. anzay911
    27.04.2024 10:48

    Это ромбики и овалы со стрелочками?


    1. Zenitchik
      27.04.2024 10:48
      +1

      Если коротко: нет.


  1. rukhi7
    27.04.2024 10:48

    В любой момент времени система находится в одном из определённых состояний, а переходы инициируются при наступлении ...

    Мне всегда было интересно послушать что скажут проповедники конечных автоматов на вопрос:

    А в каком состоянии находится конечный автомат в момент перехода?

    Но они почему-то всегда избегают ответа на этот вопрос.


    1. Zenitchik
      27.04.2024 10:48

      Сперва ответьте на два вопроса:

      1. Кто такие "проповедники конечных автоматов"?

      2. С какой стати в системе, где переход не может произойти мгновенно (синхронно, атомарно), применяется автоматный подход?


      1. rukhi7
        27.04.2024 10:48

        Интересно, а где это переход может произойти мгновенно?

        Пример можете привести?


        1. Zenitchik
          27.04.2024 10:48

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

          Это классический пример. Вы когда-нибудь задумывались, что "под капотом" у регулярных выражений? А там - конечный автомат (либо автомат с магазинной памятью).


          1. rukhi7
            27.04.2024 10:48

            Вы когда-нибудь задумывались, что "под капотом" у регулярных выражений? А там - конечный автомат (либо автомат с магазинной памятью).

            мне не надо задумываться, я код вижу.

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

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


            1. nergal-perm
              27.04.2024 10:48

              Конечные автоматы это концепция из прошлого века, если не сказать из прошлого тысячилетия.

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

              сколько же у вас состояний? больше чем атомов во вселенной?

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

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


            1. Zenitchik
              27.04.2024 10:48

              Примерно всё, что Вы написали в этом посте - неправда.

              И реализацию регулярных выражений Вы, очевидно, тоже не смотрели.


              1. rukhi7
                27.04.2024 10:48

                Это интересный аргумент!

                Видимо, правду здесь пишете только вы.


    1. nergal-perm
      27.04.2024 10:48

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


      1. rukhi7
        27.04.2024 10:48

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

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


        1. nergal-perm
          27.04.2024 10:48

          Давайте в этой ветке всегда будем уточнять, о состоянии чего в данный момент времени идет речь - о состоянии управляющего автомата или о состоянии управляемой системы.

          Будем красиво выглядеть в глазах аудиториии и, возможно, придем к имеющему практический смысл соглашению :)


          1. rukhi7
            27.04.2024 10:48

            красиво выглядеть в глазах аудиториии

            как-то так сложилось что это не моя специализация :)


    1. LaRN
      27.04.2024 10:48

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


  1. rm-hbr
    27.04.2024 10:48
    +1

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