Георгий Минашин, наш разработчик-эксперт, опубликовал на портале «Хабр» пост о переключателях функциональности. Переключатели функциональности — незаменимый инструмент для разнесения во времени моментов публикации приложения и включения новой функциональности. Чем еще они могут быть полезны? Как внедрить эту технологию? Какое ПО выбрать для работы с переключателями в .NET? Об этом — в статье «Переключатели функциональности: виды, преимущества и работа с ними в .NET».

Переключатель функциональности — это инструмент, позволяющий переключаться со старой функциональности на новую, не пересобирая приложение и не выпуская его заново. Реализуется добавлением в код условного оператора (if), который дает возможность управлять поведением программы, просто меняя нужное значение в конфигурационном файле или базе данных. Если вы хоть раз редактировали настройки в ini-файле, то вам знакома эта технология.

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

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

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

Содержание

Общие сведения о переключателях функциональности

Что это

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

Переключатели что это.png

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

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

Это вообще актуально?

В своей статье создатель featureflag.tech утверждает, что переключатели становятся стандартной практикой в разработке ПО. Появляется все больше материалов о переключателях: теоретические статьи, рассказы о практике внедрения переключателей, доклады на конференциях. Я включил ссылки на самые интересные материалы в текст данной статьи; все ссылки собраны в разделе «Заключение».

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

Что касается инструментов, помогающих работать с переключателями, то их существует очень-очень много, причем для самых разных платформ. Вероятно, появление большого числа инструментов объясняется простотой, с которой можно реализовывать базовые возможности переключателей. Но добавление более сложных и «вкусных» плюшек может потребовать существенных трудозатрат, поэтому многие открытые проекты, реализующие переключатели функциональности, перестают развиваться и поддерживаться. И все же значительное число проектов, как платных, так и бесплатных, остается на плаву. Самые интересные из них (с точки зрения .NET) описаны во второй части статьи.

Немного подробнее

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

Работа переключателей.png

Начнем рассмотрение схемы с кода. Пусть в нашем примере новая функциональность — проверка правильности заполнения документа. Сделаем так, чтобы документ проверялся, только если эта функциональность включена. Сделаем точку переключения:

var document = GetDocument();
if (feature.IsEnabled("Feature #123. Document validation"))
{
    Validate(document);
}

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

Маршрутизатор пользуется доступной ему информацией: названием функциональности, которое мы передали в качестве аргумента, и тем, что можно извлечь из известных ему статических классов, — чтобы определить, должна ли в данном случае работать запрошенная функциональность. Маршрутизатор выясняет, как сконфигурированы переключатели в реестре. Если реестр недоступен, то нужно либо воспользоваться ранее закэшированными данными, либо прибегнуть к еще какой-нибудь стратегии на этот случай. Также можно представить маршрутизатор, который явно через аргументы метода принимает дополнительную информацию (контекст), помогающую ему решить, включать ли в данный момент функциональность или нет.

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

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

Возможные требования к системе переключателей

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

  1. Зависимость включения и выключения функциональности от разных параметров. Примеры параметров: пользователь (роль, идентификатор, географическое положение), время (светлое или темное время суток, рабочие или будние дни, конкретные временные промежутки), среда (для разработки, тестирования, промышленной эксплуатации).
  2. Сбор статистики использования при анализе процессов предметной области: какой пользователь при каких условиях увидел или не увидел функциональность, сколько раз воспользовались новой функциональностью.
  3. Аудит происходящего с самими переключателями: кто их нажимает, когда, что меняет.
  4. Предоставление гибкого выбора между локальным и удаленным реестром функциональности либо каких-то стратегий на случай недоступности удаленного реестра.
  5. Настройка доступа к системе переключателей для разных категорий сотрудников как у заказчика, так и у подрядчика: технических специалистов, специалистов в предметной области, а также самих пользователей приложения.

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

Технологическое дерево

Обобщим рассмотренные сведения о переключателях функциональности, прибегнув к метафоре технологического дерева из видеоигр. Представим переключатели как технологию, позволяющую компании выкатывать новую функциональность, не привязываясь к моментам сборки и развертывания приложения. У этой технологии есть известная стоимость (расходы на поддержку жизненных циклов переключателей и реестра) и предпосылки внедрения: гибкая методология разработки плюс непрерывная интеграция. Конечно, непрерывная интеграция — это не обязательная предпосылка, но она делает переключатели существенно более эффективными и позволяет обеспечить непрерывную доставку приложения. Кроме того, «исследование» переключателей «открывает» другие технологии — A/B-тестирование и канареечные выпуски. О них будет сказано далее.

Технологическое дерево.png

Где может находиться реестр функциональности

Рассмотрим несколько вариантов размещения реестра функциональности, расположив их по возрастанию сложности реализации.

  1. Зашить конфигурацию прямо в код, например закомментировать ненужный код и раскомментировать нужный или воспользоваться директивами условной компиляции. Очевидно, этот вариант убивает суть внедрения переключателей, потому что при нем надо будет заново компилировать и развертывать приложение, чтобы увидеть новую функциональность. Еще следует отметить две трудности. Во-первых, при таком подходе от сотрудника, включающего функциональность, потребуются дополнительные технические навыки: редактирование исходного кода и умение работать с системой контроля версий (СКВ). Не бог весть какие навыки, конечно, но, скорее всего, включать функциональность все-таки придется самим программистам. Во-вторых, функциональность будет одинаковой на всех узлах, где была опубликована данная версия приложения — чтобы выбирать между старой и новой функциональностью, потребуется балансировщик и несколько узлов с приложением.
  2. Разместить конфигурацию в окружении приложения, например в переменных среды. Этот вариант представляется немного экстравагантным, потому что он чреват появлением излишних зависимостей от среды выполнения или от операционной системы.
  3. Воспользоваться конфигурационными файлами, которые являются вполне стандартным местом для хранения настроек приложения. Недостатком такого варианта будет необходимость вести конфигурационные файлы отдельно рядом с каждым экземпляром приложения. Этот недостаток присущ и предыдущему варианту.
  4. Вести в базе данных таблицу, описывающую переключатели функциональности. Экземпляры приложения будут стучаться в эту БД, чтобы понять, работает ли интересующая их функциональность. В этом варианте реестр централизуется (в отличие от предыдущего варианта), но оставляет возможность включать функциональность по отдельности для каждого узла, если это поддерживается выбранной инфраструктурой переключателей.
  5. Поднять сетевую службу, к которой экземпляры приложения будут обращаться по выбранному сетевому протоколу. Если в предыдущем варианте имелось в виду, что переключатели, скорее всего, будут храниться вместе с сущностями предметной области, и поэтому стоимость опроса переключателей будет предсказуемой, то здесь у нас будет дополнительное обращение по сети. Стоимость дополнительного обращения и возможность отказа службы являются серьезными недостатками этого варианта, но, конечно, кэшировать не запрещается. А для отказов надо предусмотреть поведение по умолчанию.

Рекомендации

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

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

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

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

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

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

Основные категории переключателей

Рассмотрим классификацию переключателей, приведенную в этой статье. Она основана на том, сколько времени «живет» переключатель и как часто меняется его состояние. Отнесение переключателя к конкретному классу помогает определиться с тем, как использовать переключатель в коде и как хранить состояние переключателя.

Переключатели для выпуска

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

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

Переключатели для проведения экспериментов

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

Технические переключатели

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

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

Переключатели для разграничения доступа

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

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

Интересные вещи, которые становятся проще с переключателями

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

A/B-тестирование. Сравнение поведения пользователей, пользующихся новой и старой версиями.

Канареечные выпуски. Постепенное увеличение количества пользователей, имеющих доступ к новой функциональности.

Сине-зеленые выпуски. Направление запросов с помощью балансировщика либо на сервер со старой версией, либо на сервер с новой версией.

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

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

Серьезные инфраструктурные изменения. Например, переход к другому способу хранения данных.

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

A/B-тестирование

Кратко рассмотрим, что такое A/B-тестирование. При подготовке A/B-тестирования формулируется какое-то измеряемое свойство работы информационной системы или поведения пользователей. Примеры такого свойства: 1) продолжительность конкретного процесса предметной области, 2) относительное количество пользователей, попавших на конкретную страницу, 3) количество ресурсов, которыми пользуется приложение. Кроме этого делается предположение, как значение этого свойства изменится при включении новой функциональности. Будут ли пользователи быстрее получать ответ? Будут ли больше покупать? Будет ли приложение меньше «кушать»?

Затем, во время A/B-тестирования, для одной группы новая функциональность включается, а для другой — остается выключенной. Выдвинутое при подготовке предположение проверяется, и на основании результатов делается вывод о том, дает ли новая функциональность что-то хорошее и не приводит ли она к чему-то плохому. Иногда пользователей разделяют на три группы, в двух из которых функциональность остается старой. Если результаты двух контрольных групп сильно отличаются, значит, и весь тест содержит какую-то ошибку, делающую недостоверными выводы на основе этого теста.

Переключатели функциональности позволяют легко разделять пользователей на группы и управлять ими. Для каждой группы можно включить и выключить новую функциональность, не дожидаясь очередного выпуска приложения. Это сокращает время, которое требуется для проверки предположений и для прохождения всего цикла A/B-тестирования. Многие компании стремятся наладить проверку сразу нескольких предположений в неделю.

Подробнее об этом методе можно узнать из этой заметки, здесь и здесь на «Хабре», и еще точно стоит посмотреть доклад «Feature Toggles, или Как выкатывать фичи без релиза», где раскрываются некоторые интересные тонкости A/B-тестирования с переключателями функциональности.

Канареечные и сине-зеленые выпуски

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

Отсюда, как я понимаю, и произошло выражение «свидетельство канарейки». Если вы живете в государстве, проводящем репрессивную внутреннюю политику (т. е. в любом), то можете обнаружить однажды, что вам пришло распоряжение не только передать личные данные ваших пользователей уполномоченным ведомствам, но еще и не сообщать никому ни о том, что вы их сдали, ни о том, что вы получили такое распоряжение. Выкрутиться, конечно, не получится, но в некоторых случаях можно регулярно отправлять своим пользователям сообщения такого вида: «В прошедшем месяце мы не получали распоряжений о раскрытии ваших личных данных». Вот так вы щебечете-щебечете, как канарейка, а когда получаете распоряжение о предоставлении личных данных, замолкаете. Тем самым вы и исполняете требование властей, и даете повод вашим пользователям насторожиться.

А еще есть канареечные выпуски. Здесь метафора работает так: когда какой-то маленькой части пользователей становится плохо, мы срочно «эвакуируемся» — сворачиваем новую функциональность, из-за которой пользователям поплохело, и не даем ей охватить всех пользователей (не даем «рвануть»). Для этого надо иметь возможность распространять новую функциональность на различные группы. Здесь можно обойтись и без переключателей. Например, у нас есть балансировщик и два узла, куда он перенаправляет запросы. На одном узле у нас развернута стабильная версия, а на втором — экспериментальная версия, содержащая новую функциональность. По умолчанию все запросы отправляются на узел со стабильной версией, и с помощью балансировщика мы начинаем некоторые запросы отправлять на узел с экспериментальной версией.

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

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

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

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

Чуть-чуть подробнее о канареечных выпусках — в соответствующей заметке на сайте Фаулера или на «Хабре», например здесь. О сине-зеленых выпусках — в обзорной статье. А об использовании переключателей функциональности в сине-зеленых выпусках можно почитать здесь.

Изменение способа хранения данных

Еще давно, при изучении таких приемов в программировании, как абстракция и разработка от интерфейсов, я часто встречал следующую рекомендацию: старайтесь, чтобы ваша программа не привязывалась к конкретному способу хранения данных, например, к определенной БД, потому что в будущем вам может понадобиться изменить способ хранения. Я это на ус намотал и делал как велено. С тех пор, однако, причины, по которым я разделяю логику предметной области и способ хранения, изменились, а к возможности смены БД в коммерческом продукте я, наоборот, начал относиться скептически.

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

Вкратце описать стратегию можно так. Процесс начинается с того момента, когда наше приложение работает с одной (старой) БД. Пользуясь переключателями функциональности, мы последовательно заставляем наше приложение сначала писать данные в новую БД, а потом — читать данные из новой БД. При этом взаимодействие со старой БД сохраняется: мы и пишем, и читаем из обеих БД!

Если с записью все более-менее понятно, то про чтение надо пояснить. Когда приложению нужны данные, оно читает их из обеих БД. Эти две точки чтения всегда находятся рядом таким образом, чтобы после чтения можно было сравнить полученные данные и проверить их согласованность. После стабилизации приложения (когда обе БД начинают стабильно возвращать одинаковые данные) приложение отключается от старой БД, а переключатели удаляются.

Далее представлено подробное описание стратегии перехода к новой БД на примере MongoDB и DynamoDB.

В примере рассматривается переход с MongoDB на DynamoDB. При подготовке к переходу создается вся необходима инфраструктура для работы с DynamoDB: подключаются драйверы БД и создаются репозитории (в приложении) по аналогии с репозиториями MongoDB. Создаются четыре переключателя — по два для каждой БД, один из которых показывает, нужно ли записывать в эту БД, а второй — нужно ли читать из этой БД. С помощью этих переключателей далее и осуществляется переключение на новую БД (DynamoDB).

Можно рассмотреть и вариант, когда часть данных должна храниться в MongoDB, а часть — в DynamoDB. Тогда группа из четырех переключателей заводится для каждого фрагмента данных, который предполагается изолированно переводить в DynamoDB (или оставлять в MongoDB). Далее будем считать, что мы переносим в DynamoDB все данные сразу и поэтому пользуемся ровно четырьмя переключателями.

Смена БД.png

На первом этапе мы дублируем в DynamoDB все, что записывается в MongoDB. При этом читаем мы по-прежнему из MongoDB. Помним, что переключатели позволяют нам выбирать любые комбинации чтения и записи. Кроме того, у нас есть возможность сделать не бинарный переключатель («включено» и «выключено»), а более гибкий. Так что по-хорошему мы начинаем дублировать запись в DynamoDB только для небольшого числа пользователей. Постепенно увеличивая это число, мы наблюдаем, как себя ведут приложение и БД. Если что — останавливаемся и работаем над ошибками.

На втором этапе мы начинаем читать и из MongoDB, и из DynamoDB. В месте, где требуются прочитанные объекты, мы сравниваем данные, полученные из разных БД, и, если они отличаются, кидаем исключение. Либо можно выйти из положения как-то более изящно, например все таки отдать наружу объект, загруженный из MongoDB, но зарегистрировать ошибку чтения в журнале, чтобы потом с ней разобраться. В конце концов мы должны добиться того, чтобы объекты, получаемые из разных БД, были полностью идентичными. При расследовании ошибок и отладке нам опять помогает постепенное увеличение числа пользователей, охваченных нововведением.

На заключительном этапе мы должны как писать все данные в обе БД, так и читать данные из обеих БД. Чтение уже должно быть отлажено и настроено так, чтобы программа работала только с объектами, полученными из DynamoDB. В этот момент у нас появляется возможность отключить как запись в MongoDB, так и чтение из нее.

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

Одновременное включение функциональности на разных платформах

В уже упомянутом докладе «Feature Toggles, или Как выкатывать фичи без релиза» рассказывалось о требовании включить новую функциональность одновременно (минута в минуту!) и на сайте, и в мобильном приложении. А потом еще может потребоваться выключить ее таким же образом.

Выполнить такое требование непросто, поскольку доставки новых версий для разных платформ очень трудно синхронизировать. Если серверы с сайтом находятся под вашем контролем, то здесь еще можно что-то подгадать (и надеяться, что в этот раз доставка отработает как часы), то магазин мобильных приложений может менять политику выпуска обновлений, как ему заблагорассудится. В любом случае обновление приложения — долгий процесс, который включает проверку публикуемого приложения самим магазином. Кроме того, не стоит надеяться, что мобильное приложение будет само регулярно запрашивать обновления у своего API и устанавливать их «внутри» себя. Эта лавочка, как я понял, тоже находится под тщательным надзором владельца платформы — с исполнением произвольного кода уж точно будут проблемы.

А вот переключатели помогают выполнить требование одновременного включения функциональности в условиях, когда вы не можете полностью контролировать доставку приложения. Очевидно, доставка все же должна гарантированно случиться до момента ожидаемого включения. Такой метод наверняка пригодится в периоды проведения временных акций, например «Черной пятницы».

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

Инструменты для работы с переключателями в .NET

Инструменты для работы с переключателями можно разбить на три группы. Во-первых, это универсальные тяжеловесные продукты (комбайны), которые обычно включают сетевую службу, работающую у поставщика, и набор библиотек, позволяющих общаться с этой службой, для разных языков программирования. Почти всегда за использование таких продуктов надо платить (и немало). Во-вторых, это проекты, представляющие собой сетевые службы, которые надо запускать на своих компьютерах и к которым клиенты могут обращаться по REST API. Из проектов этой группы (для краткости будем называть их серверами) в обзор попали только те, у которых есть хорошо документированный API или официальный клиент для .NET. В-третьих, это простенькие (относительно двух предыдущих групп) библиотеки для .NET. Они предлагают хранить реестр функциональности в конфигурационных файлах или обращаться по сети к удаленному реестру, который не входит в состав этих проектов.

Внимательный читатель заметит, что есть еще одна группа программных средств, которая не попала в обзор. Речь идет о продуктах, которые можно использовать в виде хранилища конфигурации для распределенной системы. К известным представителям таких продуктов относятся Apache ZooKeeper, Consul и etcd. Действительно, по своим базовым возможностям переключатели очень схожи с хранилищами конфигурации, поэтому указанные инструменты можно использовать как отправную точку для создания своей инфраструктуры переключателей. Тем не менее из-за того, что назначение переключателей функциональности обладает определенной спецификой, с развитием культуры переключателей в рамках проекта начнут ощущаться недостатки универсальных хранилищ конфигураций. По этой причине подобные продукты далее не рассматриваются.

Комбайны

LaunchDarkly

Похоже, это самый раскрученный и «зубодробительный» продукт в области переключателей функциональности. Складывается ощущение, что в этом продукте уместили почти все, что только может прийти в голову, когда говорят о переключателях. Это касается и возможностей самой платформы, и разнообразия доступных клиентов, и видов интеграции с разными инструментами типа Jira и Visual Studio Code. Все документировано очень подробно и с примерами. Инструмент, конечно, платный, но в течение пробного 30-дневного периода им можно пользоваться бесплатно.

Компания Catamorphic Co., производящая этот продукт, также опекает специальный сайт-справочник о переключателях функциональности.

Rollout

Сайт выглядит мило, но при попытке получить конкретную информацию начинаются проблемы. Например, указано, что продукт имеет много интересных возможностей для аудита переключателей, наблюдения за использованием функциональности и проведения экспериментов. Но при этом производители не раскрывают ценовую политику (ясно только, что есть 14-дневная бесплатная пробная версия) и ведут довольно сырую документацию своего REST API. Документация к клиентским библиотекам, впрочем, в порядке. Система поддерживает хранение переключателей в конфигурационных файлах.

Optimizely

В целом Optimizely — это какая-то крутая платформа для сбора аналитики и проведения экспериментов. У системы есть бесплатная часть Rollouts, предоставляющая базовые возможности переключателей. Написано, что, кроме поддержки таргетирования («раскатывания» функциональности на определенные группы пользователей), эта бесплатная часть поддерживает бесконечное количество переключателей и проектов. Кроме того, пользоваться этой системой может сколько угодно сотрудников. Стоит отметить, что стоимость многих других продуктов определяется именно этими количественными характеристиками. Сколько будет стоить переход на полностью функциональную платформу — секрет.

Bullet train

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

Split

Еще одна платформа, которая стесняется своих цен. Из дополнительных «фишек» предлагает интеграции с немногими другими инструментами разработки и поддержку проведения экспериментов (A/B-тестирования).

ConfigCat

А вот продукт с веселеньким сайтом, где есть котики. Выглядит задорно. Однако подозрительно, что на нем не размещена документация к REST API. С клиентом под .NET все в порядке. Кстати, есть бесплатный план.

Серверы

Unleash

Довольно зрелый открытый проект, который предполагает размещение системы на серверах компании. Включает простенький сайт для управления переключателями. Есть встроенные простейшие средства для учета использования функциональности. Установка довольно простая; требуется PostgreSQL.

Официальной клиентской библиотеки .NET не имеется, но уже создано несколько сторонних. Кроме того, благодаря документированному API, теоретически, можно создать свою собственную библиотеку.

GitLab feature flags

GitLab построил свою систему переключателей поверх Unleash, поэтому для взаимодействия с GitLab используются те же самые клиентские библиотеки, что и для Unleash. От самого Unleash отличается тем, что нужно меньше копаться с установкой (если не считать установку самого GitLab), но при этом нужно платить: переключатели функциональности доступны начиная с планов GitLab Premium (если GitLab запускается на ваших собственных серверах) и GitLab Silver (если GitLab запускается на серверах поставщика).

Feature flags API in Go

Этот открытый проект представляет собой службу с API (немного документирован), через который можно управлять переключателями. Видимо, графический интерфейс отсутствует. Написан на Go; использует встроенные БД и сетевой сервер, поэтому настройка, судя по инструкции, тривиальна. Предоставляет зачаточные возможности для таргетирования. Последняя правка в репозитории: 20 февраля 2019.

Bandiera

Тоже открытый проект. Служба с API и графическим интерфейсом. Написана на Ruby; для работы требует MySQL или PostgreSQL, либо можно поставить из Docker. Есть клиенты для Ruby, Node, Scala, PHP, но не для .NET. Поддерживает довольно пестрый набор видов переключателей.

Flagr

Еще один открытый проект на Go. Устанавливается из Docker. Есть графический интерфейс, а также клиенты для Ruby, Go, JavaScript, Python. Предлагает встроенные простейшие средства для учета использования функциональности и проведения экспериментов. Каждый переключатель можно гибко настроить и снабдить собственной конфигурацией.

Клиентские библиотеки

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

Esquio

Самый интересный, хорошо документированный и функциональный проект своей группы. Встроенный механизм хранения — либо конфигурационный файл, либо EF Core. Немного интегрируется с ASP.NET Core (как Microsoft.FeatureManagement); в документации сказано, что с помощью Azure можно упростить конфигурирование переключателей.

FeatureSwitch

Может запрашивать состояние переключателя и из конфигурационного файла, и удаленно (настраивается с помощью классов пространства FeatureSwitch.Strategies). В комплекте есть каркас простенького сайта, позволяющего управлять переключателями. Непонятно, есть ли возможность сделать более сложный переключатель, чем «включено/выключено».

Toggle.Net

Простейшая библиотека для работы с переключателями, которые хранит в текстовом файле. Видимо, поддерживает только переключатели вида «включено/выключено».

RimDev.FeatureFlags

Клиентская библиотека для ASP.NET Core. Помимо того, что позволяет использовать переключатели функциональности, поднимает вместе с основным сайтом проекта дополнительную страничку, на которой можно управлять переключателями. Встроенный механизм хранения конфигурации — SQL Server.

Сравнение в виде таблицы

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

Сравнительная таблица.PNG

Содержимое колонки «Плюшки» расшифровывается примерно следующим образом.

Аудит. Инструмент ведет учет, кто, когда и как изменил состояние переключателя.

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

Поддержка экспериментов. Инструмент собирает статистику использования функциональности и некоторых параметров приложения, упрощая проведение A/B-тестирования.

Аналитика. Инструмент представляет собранную по процессам предметной области статистику в удобном для анализа виде.

Интеграции. Для инструмента налажены интеграции с другими популярными средствами разработки и управления проектами.

Заключение

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

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

В качестве итогов перечислю особенности переключателей функциональности и волюнтаристски (как водится) разделю их на преимущества и недостатки.

Преимущества

  1. Основное преимущество, которое дает внедрение переключателей — это возможность включить функциональность не во время публикации, а в любой момент после публикации версии, для которой готовилась эта функциональность.
  2. Чтобы откатить плохо работающую функциональность, можно просто нажать переключатель. Не надо восстанавливать старую версию всего приложения из резервной копии или из фиксации в СКВ и затрагивать все, что было опубликовано вместе с проблемной функциональностью.
  3. Пользуясь переключателями, разработчики более внимательно относятся к сохранению старой функциональности. Например, если система обрабатывает события и формат события меняется, то при использовании переключателя явно видны части кода, ведущие к обработчикам события как в старом формате, так и в новом.
  4. Можно существенно уменьшить количество веток в СКВ, например делать отдельные ветки только для существенных изменений. В результате уменьшается количество и сложность слияний веток. Слияние как бы переносится в самое начало работы над задачей, когда нужно сделать «слияние» существующего кода и свежего переключателя.
  5. Можно скорее приступить к ловле ошибок, потому что новый код становится доступным для исполнения в тестовой среде намного быстрее. Если новая функциональность затрагивает несколько систем, то с переключателями быстрее достигаются границы межсистемного взаимодействия. В результате есть возможность заранее обнаруживать непредвиденные проблемы и принимать такие архитектурные решения, которые стоили бы слишком дорого после продолжительной разработки.

Недостатки

  1. Расходы на управление жизненным циклом переключателя. Возможно, придется изменить некоторые процессы разработки и публикации. Предполагаемое решение по устранению недостатка: внедрять переключатели только там, где они действительно понадобятся. Например, не делать переключатели в небольших задачах, у которых есть четкая постановка и которые можно сделать за одну версию.
  2. Расходы на поддержку процессов предметной области. Например, произошла ошибка, которую нужно расследовать службе поддержки. С переключателями функциональности у службы поддержки появляется новая трудность: надо помнить, что управление могло пойти как по одной ветке, так и по другой, поэтому нужно определять, по какой именно. Предполагаемое решение по устранению недостатка: более детально журналировать характеристики и контекст запроса (включая выбранные ветки функциональности), во время которого произошла ошибка.
  3. Расходы на организационные изменения и обучение. Кто должен включать и выключать флаги? Кто должен помнить о том, что устаревшие флаги надо подчищать? От ответственного сотрудника могут потребоваться специальные навыки. Например, если инструмент управления переключателями собран на коленке и предназначен для технических сотрудников, то и пользоваться им смогут только технические сотрудники. Где хранить удаленный реестр функциональности? Как его защищать? Предполагаемое решение по устранению недостатка: пользоваться самыми простыми инструментами, поддерживающими необходимые возможности.
  4. Если инфраструктура переключателей — это корпоративное решение, то его надо поддерживать как отдельный проект. Если это бесплатное открытое решение, то его тоже надо поддерживать после установки на компьютеры компании. Предполагаемое решение по устранению недостатка: купить нормальное решение (шутка); не усложнять собственный корпоративный инструмент (пусть он поддерживает только необходимый минимум); пользоваться только самым необходимым в инсталляциях бесплатного открытого решения и вести собственную документацию.
  5. Если инфраструктура переключателей — это покупное решение, то надо платить денежки. Предполагаемое решение по устранению недостатка: сделать собственное корпоративное решение (шутка); выбрать решение с подходящим соотношением «функциональность/стоимость».
  6. Если реестр функциональности удаленный, то будут дополнительные запросы по сети. Предполагаемое решение по устранению недостатка: использовать удаленный реестр только при необходимости; кэшировать состояние реестра локально (возможно, заранее).
  7. Дополнительная точка отказа. Что делать, если реестр функциональности недоступен? Предполагаемое решение по устранению недостатка: выработать стратегию на случай отказа, например писать код так, чтобы отказ реестра был ожидаем, а поведение по умолчанию было приемлемым (эта стратегия не всегда подходит).

Ссылки на источники информации

Обзорные материалы

Что такое переключатели функциональности (сайт Мартина Фаулера)

Начало работы с переключателями функциональности (портал Medium)

Как выкатывать фичи без релиза с помощью переключателей функциональности (доклад на конференции HighLoad++)

Сине-зеленые выпуски (сайт Мартина Фаулера)

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

ААБ-тестирование («Хабр»)

Проверка гипотез для мобильных приложений («Хабр»)

Безопасное тестирование в продакшене («Хабр»)

Сине-зеленые выпуски (сайт Featureflags.io)

Изменение способа хранения данных (сайт Featureflags.io)

Все о канарейках

Канареечные выпуски (сайт Мартина Фаулера)

Использование канареек в шахтах (журнал Smithsonian)

Свидетельство канарейки («Хабр»)