DEF CON / Нижний Новгород

State vs Status: в чем разница между этими полями в API?

Фёдор "Wire Snark" Ляхов опубликовал 5 ноября 2023 в блоге Беспроводной Снарк

На прошлой неделе я занимался проектированием схем протоколов IPC между компонентами нашего ПО (по сути - прошивки специального беспроводного маршрутизатора) и столкнулся со следующей проблемой. От компонентов требовалось отправлять свой текущий статус, и по большей части он получался бинарным: OK=0 (всё хорошо) и NOK=1 (компонент сломался). Однако для некоторых компонентов требовалось больше информации - помимо исправности, хотелось знать более детальное состояние. Например, что компонент сейчас работает в состоянии A или в состоянии B, или же он сломался и находится в состоянии ошибки.

У нас микросервисная архитектура, и под компонентами зачастую понимаются сервисы. Как следствие, у нас довольно много сервисов, и мне хотелось ввести единообразное поведение в отношении репорта статусов для них всех. Я стал гуглить, как вообще принято решать эту проблему, в чем же разница между State и Status, и нашел пару вопросов на Стек Оверфлоу:

Наиболее популярный ответ звучит так:

  status == how are you? [good/bad]
  state  == what are you doing? [resting/working]

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

Аргументы выглядели разумными, но и от единого стиля АПИ отказываться не хотелось. Оставить одно поле State, даже если переименовать его Status, не вязалось с тем пониманием Status и State, которое у меня сложилось. Я стал думать и копать дальше. Изначально у меня было ощущение, что Status - это нечто более общее, а State - опциональные детали, специфичные для предметной области.

Я стал искать примеры АПИ, где бы использовались и State, и Status. Стоит заметить, что одним из компонентов у нас является свой собственный оркестратор микросервисов для встроенных систем. В статус-сообщении у него должна быть возможность отдавать и статус каждого запущенного им сервиса, и его состояние.

Когда речь заходит об оркестраторе, референсом индустрии выступает Докер, поэтому я заглянул в Dockerd API и увидел такую картину:

// Inspect a container
// Return low-level information about a container
...
"State": {
  "Status": "running"
  "Health": {
    "Status": "healthy"
  }
  ...
}

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

  "Что делаешь?" "работаю"   // статус по отношению к текущему действию
  "Как здоровье?" "приболел" // статус по отношению к здоровью

В этот момент у меня родилось определение того, что есть Статус:

Статус — это проекция полного состояния сущности на какую-то выбранную ось (или на конечное множество).

Очевидно, это всегда отображение с потерей информации (много состояний могут иметь один и тот же статус). Мы вольны выбирать эти оси сами, и иметь много статусов. Часто это проекция на ось “всё хорошо/ошибка”, но это лишь одна из простейших проекций. Может быть проекция на ось “здоров/не здоров”, или “активен/пассивен”, или что-то ещё.

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

На основе статуса может быть построен другой статус. Например, из “Работает, спит, упал” можно построить статус “жив/мертв”, где первые два будут отображаться в “жив”. При этом не все проекции могут сработать, например из того же статуса о работе в общем случае нельзя построить статус “всё хорошо/ошибка”, т.к. нужно знать контекст, то есть интерпретировать тот или иной статус. Например, “спит”, когда должен “работать”, может быть ошибкой! А “упал” - наоборот, если мы говорим о тесте, который должен падать по дизайну.

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

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

Фёдор
Фёдор "Wire Snark" Ляхов, DC7831, НПП "Прима", Нижний Новгород

Один из основателей DC7831. В настоящее время архитектор и руководитель группы разработки авиационных сетей в НПП "Прима". Разработчик средне-системного уровня (сервисы, демоны). Стронник серого хакинга и независимого программирования. Ведет личный технический блог Телеграм:Беспроводной Снарк.