На прошлой неделе я занимался проектированием схем протоколов IPC между компонентами нашего ПО (по сути - прошивки специального беспроводного маршрутизатора) и столкнулся со следующей проблемой. От компонентов требовалось отправлять свой текущий статус, и по большей части он получался бинарным: OK=0
(всё хорошо) и NOK=1
(компонент сломался). Однако для некоторых компонентов требовалось больше информации - помимо исправности, хотелось знать более детальное состояние. Например, что компонент сейчас работает в состоянии A
или в состоянии B
, или же он сломался и находится в состоянии ошибки.
У нас микросервисная архитектура, и под компонентами зачастую понимаются сервисы. Как следствие, у нас довольно много сервисов, и мне хотелось ввести единообразное поведение в отношении репорта статусов для них всех. Я стал гуглить, как вообще принято решать эту проблему, в чем же разница между State
и Status
, и нашел пару вопросов на Стек Оверфлоу:
Наиболее популярный ответ звучит так:
status == how are you? [good/bad]
state == what are you doing? [resting/working]
Исходя из этой парадигмы, первой моей реакцией было завести в сообщении протокола два поля: Status
, которое отвечает на вопрос, исправен ли компонент, и State
, в котором содержится имя текущего состояния компонента, специфичное для его машины состояний. Когда я показал этот вариант команде, то услышал резкое неприятие и возмущение! Конструктивные аргументы свелись к следующим:
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
, который теперь является специфическим для предметной области каждого конкретного сервиса. Команда тоже счастлива - их интуитивное понимание было верным, а теперь оно получило логическое обоснование.