Русский
Русский
English
Статистика
Реклама

Блог компании аркадия

Делаем свой телеканал

13.10.2020 16:15:52 | Автор: admin


Вы, возможно, удивитесь, но телевидение всё ещё живо. Да, аудитория поредела и состарилась, а технологии приумножились и помолодели (IPTV, SmartTV, различные приставки), но всё-таки жизнь есть не только в YouTube и TikTok. Мало того, сейчас сделать свой телеканал можно при достаточно небольших инвестициях времени и финансов. В 2017 году мой брат (Ruler-ufa) попросил меня о помощи с технической реализацией нового музыкального телеканала на башкирском и татарском языках. О том, что у нас получилось, и пойдёт речь в этой статье. Сразу оговорюсь, что нюансов подбора контента, оформления эфира и подобных тем здесь не будет, т.к. я занимался исключительно технической частью. Кроме того, задача была сделать все максимально просто и дёшево, т.к. бюджет был ограничен, поэтому некоторые вещи можно было сделать по-другому правильнее, но гораздо дороже.

DISCLAIMER! Все упомянутые решения, компании, партнеры и операторы лишь отражение нашего личного опыта, а не реклама.

Создание картинки и генерация видеопотока




Что такое телеканал? По сути, это бесконечный аудиовидеопоток. И его необходимо как-то генерировать.

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

Что такое АСБ? В общем случае это другая аппаратная и студия, ответственные за производство конкретной программы в прямом эфире например, новостей. Центральная аппаратная связывает воедино все АСБ и иные входы и решает, что пойдёт в эфир в конкретный момент времени. Ну а видеосерверы создают линейный эфир, т.е. эфир по расписанию всё то, что не является прямым эфиром. Кроме того, они могут генерировать, например, настроечную таблицу, сигнал часов и т.п. На выходе центральной аппаратной аудиовидеопоток, который отправляется на передающее оборудование, спутник, сети распространения и т.д. Пример работы АСБ программы Время на Первом канале:



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

  • Intel Core i7-7700K (с интегрированной Intel HD Graphics 630),
  • RAM 24 Gb,
  • Windows 10 Pro,
  • 3 сетевые карты для интернета и отправки потока.




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

С железом разобрались, переходим к софту. В качестве ПО для генерации картинки мы выбрали Форвард ТС компании СофтЛаб-НСК, поскольку уже имели опыт работы с ним. Этот комплекс предоставляет широкий спектр возможностей для организации цифрового вещания и используется большим количеством телеканалов стран бывшего СНГ и не только. Список фич, которые используем мы:

  • подготовка эфира и составление расписания;
  • оформление эфира (титрование);
  • кодирование потока в H.264/MP3 и упаковка в MPEG-TS;
  • передача потока по UDP Multicast.




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

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

Настройка pipelineа (по-русски это обычно называют тракт) происходит в другой программе SLStreamerPro.



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

На графе видно, что эта самая плата является источником сигнала, с неё в граф попадает несжатый аудиовидеопоток (RAW). Затем он кодируется в определённый формат, в нашем случае H.264/MP3. Да, именно MP3, а не AAC, поскольку не все телевизоры могут воспроизвести AAC, а поток доходит до конечных телевизоров в неизменном виде операторы просто доставляют его от телеканалов до зрителей, никак не изменяя.

Заключительная группа компонентов графа получатели потока. Мы используем UDP Multicast и HLS segmenter. Первый нарезает MPEG-TS на UDP-дейтаграммы и отправляет на сетевую карту, а второй на HLS-манифест и сегменты и складывает их на диск, чтобы мы могли опубликовать их с помощью IIS. Кстати, ffmpeg забирает сигнал тоже с помощью UDP Multicast, но не через реальную сетевую карту, а через loopback (об этом совсем скоро).

Доставка до кабельных операторов и медиасервисов


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

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

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

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

Второй доставлять сигнал по земле через специализированных посредников. Самый популярный компания Медиалогистика, филиал MSK-IX. Что она делает: по локальной сети дата-центра (как правило, Останкино или M9) забирает ваш сигнал и по своей наземной инфраструктуре оптоволоконных сетей доставляет его до операторов по всей стране. Краткий ролик о том, как всё это работает:



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

Есть и такие операторы (на самом деле, их у нас большинство), которые готовы принять сигнал через дикий интернет. Как правило, забирают они его в формате HLS, для этого они получают ссылку вида streaming.matur-tv.ru/hls/h264_aac/stream.m3u8. В очень редких случаях партнёр (например, Яндекс.Эфир) не имеет технической возможности принять HLS, тогда мы можем отдать картинку по RTMP (pull) либо через HTTP-TS. Последний представляет собой HTTP-ссылку, которая, в отличие от сегментов HLS, является бесконечным виртуальным файлом с потоком MPEG-TS.

Поскольку операторов становится уже слишком много, ПО Форвард не поддерживает протокол RTMP и является недостаточно гибким, а также нам нужно иметь публичный HLS-поток для сайта и мобильного приложения (в случае DDoS-атаки на сервер не хотелось бы, чтобы умер весь эфир), мы решили развернуть ещё два виртуальных сервера только для ретрансляции потока. Один для операторов и сервисов, второй для публичного стриминга.



Основные инструменты, с помощью которых мы реализовали ретрансляцию это ffmpeg и nginx rtmp module. Об ffmpeg, думаю, слышал хоть раз практически каждый. Это швейцарский нож в деле обработки и конвертирования аудио-видео. С помощью этой мощной утилиты мы вытаскиваем поток из pipelineа Форвард и по RTMP отправляем на первый сервер ретрансляции. Там сигнал принимает nginx, который нарезает его на HLS и раздаёт всем операторам, у кого есть ссылка, а также отправляет его в YouTube, ВКонтакте и на второй сервер ретрансляции. Второй сервер практически ничем не отличается, только он не отправляет сигнал в YouTube и ВКонтакте и предназначен для раздачи потока для пользователей сайта и мобильного приложения.

Мониторинг


Отлично! Теперь у нас всё работает, и зрители могут наслаждаться музыкой! Но всегда есть но. Чем сложнее система, тем чаще она ломается. А когда она частично или полностью является набором из бесплатных open-source-решений, соединённых воедино это происходит ещё чаще. Не хотелось бы расстраивать зрителей отсутствием сигнала, и тут нам поможет мониторинг.



Велосипедов изобретать мы не стали, а развернули еще один сервер с Zabbix на борту и мониторингом всех трёх (четырёх вместе с Zabbix) серверов. Для удобства настроили оповещения в два чата в Telegram: один для незначительных проблем технического характера, второй для серьёзных аварий с полным или частичным отсутствием изображения. Теперь у нас есть мониторинг базовых метрик серверов. Но, как правило, с самими ОС всё хорошо, а нам ещё нужен мониторинг непосредственно медиапотока.

К сожалению, никаких готовых pluginов под ключ мы не нашли. Взяв за основу этот template (спасибо, Diversantos!) и немного доработав, мы получили следующие метрики для каждого из наших HLS-потоков:

  • существование сегмента;
  • размер сегмента;
  • время загрузки сегмента;
  • продолжительность сегмента;
  • громкость звука (добавили мы);
  • индекс структурного сходства (добавили мы).

Используя всё это, плюс встроенные возможности Zabbix, мы создали несколько важных для нас триггеров:

  • Битрейт нестабилен;
  • Сегмент не найден;
  • Загрузка сегмента заняла более 200 мс;
  • Продолжительность сегмента менее 4 секунд;
  • В потоке зафиксирована тишина;
  • В потоке зафиксировано статичное изображение;
  • Трансляция на сайте и в мобильном приложении недоступна;
  • Трансляция в YouTube недоступна;
  • Трансляция ВКонтакте недоступна.

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

Заключение


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

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

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



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

Применение CQRS amp Event Sourcing в создании платформы для проведения онлайн-аукционов

07.07.2020 14:13:48 | Автор: admin
Коллеги, добрый день! Меня зовут Миша, я работаю программистом.

В настоящей статье я хочу рассказать о том, как наша команда решила применить подход CQRS & Event Sourcing в проекте, представляющем собой площадку для проведения онлайн-аукционов. А также о том, что из этого получилось, какие из нашего опыта можно сделать выводы и на какие грабли важно не наступить тем, кто отправится путем CQRS & ES.
image


Прелюдия


Для начала немного истории и бизнесового бэкграунда. К нам пришел заказчик с платформой для проведения так называемых timed-аукционов, которая была уже в продакшене и по которой было собрано некоторое количество фидбэка. Заказчик хотел, чтоб мы сделали ему платформу для live-аукционов.

Теперь чуть-чуть терминологии. Аукцион это когда продаются некие предметы лоты (lots), а покупатели (bidders) делают ставки (bids). Обладателем лота становится покупатель, предложивший самую большую ставку. Timed-аукцион это когда у каждого лота заранее определен момент его закрытия. Покупатели делают ставки, в какой-то момент лот закрывается. Похоже на ebay.

Timed-платформа была сделана классически, с применением CRUD. Лоты закрывало отдельное приложение, запускаясь по расписанию. Работало все это не слишком надежно: какие-то ставки терялись, какие-то делались как будто бы от лица не того покупателя, лоты не закрывались или закрывались по несколько раз.

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

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

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

Какая еще есть специфика работы онлайн-аукционов:

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

Краткий обзор подхода CQRS & ES


Не буду подробно останавливаться на рассмотрении подхода CQRS & ES, материалы об этом есть в интернете и в частности на Хабре (например, вот: Введение в CQRS + Event Sourcing). Однако кратко все же напомню основные моменты:

  • Самое главное в event sourcing: система хранит не данные, а историю их изменения, то есть события. Текущее состояние системы получается последовательным применением событий.
  • Доменная модель делится на сущности, называемые агрегатами. Агрегат имеет версию. События применяются к агрегатам. Применение события к агрегату инкрементирует его версию.
  • События хранятся в write-базе. В одной и той же таблице хранятся события всех агрегатов системы в том порядке, в котором они произошли.
  • Изменения в системе инициируются командами. Команда применяется к одному агрегату. Команда применяется к последней, то есть текущей, версии агрегата. Агрегат для этого выстраивается последовательным применением всех своих событий. Этот процесс называется регидратацией.
  • Для того, чтобы не регидрировать каждый раз с самого начала, какие-то версии агрегата (обычно каждая N-я версия) можно хранить в системе в готовом виде. Такие снимки агрегата называются снапшотами. Тогда для получения агрегата последней версии при регидратации к самому свежему снапшоту агрегата применяются события, случившиеся после его создания.
  • Команда обрабатывается бизнес-логикой системы, в результате чего получается, в общем случае, несколько событий, которые сохраняются в write-базу.
  • Кроме write-базы, в системе может еще быть read-база, которая хранит данные в форме, в которой их удобно получать клиентам системы. Сущности read-базы не обязаны соответствовать один к одному агрегатам системы. Read-база обновляется обработчиками событий.
  • Таким образом, у нас получается разделение команд и запросов к системе Command Query Responsibility Segregation (CQRS): команды, изменяющие состояние системы, обрабатываются write-частью; запросы, не изменяющие состояние, обращаются к read-части.



Реализация. Тонкости и сложности.


Выбор фреймворка


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

В целом наш технологический стек это Microsoft, то есть .NET и C#. База данных Microsoft SQL Server. Хостится все в Azure. На этом стеке была сделана timed-платформа, логично было и live-платформу делать на нем.

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

Зачем вообще нужен фреймворк CQRS & ES? Он может из коробки решать такие задачи и поддерживать такие аспекты реализации как:

  • Сущности агрегата, команды, события, версионирование агрегатов, регидратация, механизм снапшотов.
  • Интерфейсы для работы с разными СУБД. Сохранение/загрузка событий и снапшотов агрегатов в/из write-базы (event store).
  • Интерфейсы для работы с очередями отправка в соответствующие очереди команд и событий, чтение команд и событий из очереди.
  • Интерфейс для работы с веб-сокетами.

Таким образом, с учетом использования Chinchilla, к нашему стеку добавились:

  • Azure Service Bus в качестве шины команд и событий, Chinchilla поддерживает его из коробки;
  • Write- и read-базы Microsoft SQL Server, то есть обе они SQL-базы. Не скажу, что это является результатом осознанного выбора, скорее по историческим причинам.

Да, фронтенд сделан на Angular.

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

Выбор агрегатов


Одной из первых вещей, которую надо сделать при реализации подхода CQRS & ES это определить, как доменная модель будет делиться на агрегаты.

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

public class Auction{     public AuctionState State { get; private set; }     public Guid? CurrentLotId { get; private set; }     public List<Guid> Lots { get; }}public class Lot{     public Guid? AuctionId { get; private set; }     public LotState State { get; private set; }     public decimal NextBid { get; private set; }     public Stack<Bid> Bids { get; }} public class Bid{     public decimal Amount { get; set; }     public Guid? BidderId { get; set; }}


У нас получилось два агрегата: Auction и Lot (с Bidами). В общем, логично, но мы не учли одного того, что при таком делении состояние системы у нас размазалось по двум агрегатам, и в ряде случаев для сохранения консистентности мы должны вносить изменения в оба агрегата, а не в один. Например, аукцион можно поставить на паузу. Если аукцион на паузе, то нельзя делать ставки на лот. Можно было бы ставить на паузу сам лот, но аукциону на паузе тоже нельзя обрабатывать никаких команд, кроме как снять с паузы.

В качестве альтернативного варианта можно было сделать только один агрегат, Auction, со всеми лотами и ставками внутри. Но такой объект будет довольно тяжелым, потому что лотов в аукционе может быть до нескольких тысяч и ставок на один лот может быть несколько десятков. За время жизни аукциона у такого агрегата будет очень много версий, и регидратация такого агрегата (последовательное применение к агрегату всех событий), если не делать снапшотов агрегатов, будет занимать довольно продолжительное время. Что для нашей ситуации неприемлемо. Если же использовать снапшоты (мы их используем), то сами снапшоты будут весить очень много.

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

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

Мы на данном этапе эволюции проекта живем с двумя агрегатами, Auction и Lot, и нарушаем архитектуру, меняя в рамках некоторых команд оба агрегата.

Применение команды к определенной версии агрегата


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

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

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

Ошибки при выполнении команды с использованием очереди


В нашей реализации, в большой степени обусловленной использованием Chinchilla, обработчик команд читает команды из очереди (Microsoft Azure Service Bus). Мы у себя явно разделяем ситуации, когда команда зафейлилась по техническим причинам (таймауты, ошибки подключения к очереди/базе) и когда по бизнесовым (попытка сделать на лот ставку той же величины, что уже была принята, и проч.). В первом случае попытка выполнить команду повторяется, пока не выйдет заданное в настройках очереди число повторений, после чего команда отправляется в Dead Letter Queue (отдельный топик для необработанных сообщений в Azure Service Bus). В случае бизнесового эксепшена команда отправляется в Dead Letter Queue сразу.



Ошибки при обработке событий с использованием очереди


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

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



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

В итоге, в качестве временной меры мы отказались от использования Azure Service Bus для передачи событий из write-части приложения в read-часть. Вместо нее используется так называемая In-Memory Bus, что позволяет обрабатывать команду и события в одной транзакции и в случае неудачи откатить все целиком.



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

Отправка команды в качестве реакции на событие


Такое в принципе уместно, но только в случае, когда невыполнение этой второй команды не ломает состояние системы.

Обработка множества событий одной команды


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



Обработка одного события несколькими обработчиками


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

Например, обработчик события в read-базе добавляет ставку на лот величиной 5 рублей. Первая попытка сделать это будет успешной, а вторую не даст выполнить constraint в базе.



Выводы/Заключение


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

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

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

Фильтры действий, или Как просто улучшить читаемость кода

25.01.2021 16:17:27 | Автор: admin

Введение


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

Роль фильтров в процессе обработки запроса


Сначала обсудим сами фильтры: для чего же они нужны? Фильтры позволяют выполнять определённые действия на различных стадиях обработки запроса в ASP.NET Core. Существуют следующие встроенные фильтры:

  • Фильтры авторизации (Authorization filters) выполняются самыми первыми и определяют, может ли пользователь выполнить текущий запрос.
  • Фильтры ресурсов (Resource filters) вызываются после фильтров авторизации и необходимы, как следует из названия, для обработки ресурсов. В частности, данный тип фильтров применяют в качестве механизма кэширования.
  • Фильтры действий (Action Filters) выполняют указанные в них операции до и после выполнения метода контроллера, обрабатывающего запрос.
  • Фильтры исключений (Exception Filters) используются для перехвата необработанных исключений, произошедших при создании контроллера, привязке модели и выполнении кода фильтров действий и методов контроллера.
  • И наконец, самыми последними вызываются фильтры результатов (Result Filters), если метод контроллера был выполнен успешно. Данный тип фильтров чаще всего используется, чтобы модифицировать конечные результаты, например, мы можем создать свой заголовок ответа, в котором добавим нужную нам информацию.


Ниже представлена схема, которая показывает, в каком порядке вызываются фильтры в процессе обработки запроса:



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

Внутреннее устройство фильтров действий


Фильтры действий в ASP.NET


Интерфейс IActionFilter, который нужно реализовать, чтобы создать фильтр действий, существовал ещё в ASP.NET MVC. Он определяет методы OnActionExecuting, который вызывается перед выполнением метода контроллера, и OnActionExecuted, который вызывается сразу после. Ниже представлен пример простейшего фильтра действий, который выводит информацию во время отладки приложения до и после выполнения метода контроллера:

public class CustomActionFilter:IActionFilter {         public void OnActionExecuting(ActionExecutingContext filterContext)         {             Debug.WriteLine("Before Action Execution");         }         public void OnActionExecuted(ActionExecutedContext filterContext)         {             Debug.WriteLine("After Action Execution");         } }


Чтобы использовать вышеуказанный фильтр, его нужно зарегистрировать. Для этого в файле FilterConfig.cs, который находится в папке App_Start, следует добавить следующую строку:

public static void RegisterGlobalFilters(GlobalFilterCollection filters) {         filters.Add(new HandleErrorAttribute());         filters.Add(new CustomActionFilter()); }


Но гораздо удобнее использовать фильтры как атрибуты. Для этих целей существует абстрактный класс ActionFilterAttribute, который унаследован от класса FilterAttribute, а также реализует интерфейсы IActionFilter и IResultFilter. Таким образом, наш класс можно переписать следующим образом:

public class CustomActionFilterAttribute:ActionFilterAttribute {         public override void OnActionExecuting(ActionExecutingContext filterContext)         {             Debug.WriteLine("Before Action Execution");         }         public override void OnActionExecuted(ActionExecutedContext filterContext)         {             Debug.WriteLine("After Action Execution");         } } 


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

public class HomeController : Controller {         [CustomActionFilter]         public ActionResult Index()         {             return View();         } }


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

Фильтры действий в ASP.NET Core


С появлением ASP.NET Core в фильтрах действий произошёл ряд изменений. Кроме интерфейса IActionFilter, теперь имеется ещё и IAsyncActionFilter, который определяет единственный метод OnActionExecutionAsync. Ниже приведён пример класса, реализующего интерфейс IAsyncActionFilter:

public class AsyncCustomActionFilterAttribute:Attribute, IAsyncActionFilter {         public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)         {             Debug.WriteLine("Before Action Execution");             await next();             Debug.WriteLine("After Action Execution");         } } 


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

Применяют такой фильтр так же, как и синхронный:

public class HomeController : Controller {         [AsyncCustomActionFilter]         public ActionResult Index()         {             return View();         } }


Также изменения затронули абстрактный класс ActionFilterAttribute: теперь он наследуется от класса Attribute и реализует синхронные и асинхронные интерфейсы для фильтров действий (IActionFilter и IAsyncActionFilter) и для фильтров результатов (IResultFilter и IAsyncResultFilter), а также интерфейс IOrderedFilter.

Фильтры действий в действии


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

public class Employee {         [Required(ErrorMessage = "First name is required")]         public string FirstName { get; set; }         [Required(ErrorMessage = "Last name is required")]         public string LastName { get; set; }         [AgeRestriction(MinAge = 18, ErrorMessage = "Date of birth is incorrect")]         public DateTime DateOfBirth { get; set; }         [StringLength(50, MinimumLength = 2)]         public string Position { get; set; }         [Range(45000, 200000)]         public int Salary { get; set; } } 


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

После того как были реализованы методы POST и PUT, мы видим, что оба метода содержат повторяющиеся части кода:

[HttpPost] public IActionResult Post([FromBody] Employee value) {             if (value == null)             {                 return BadRequest("Employee value cannot be null");             }             if (!ModelState.IsValid)             {                 return BadRequest(ModelState);             }             // Perform save actions             return Ok(); } [HttpPut] public IActionResult Put([FromBody] Employee value) {             if (value == null)             {                 return BadRequest("Employee value cannot be null");             }             if (!ModelState.IsValid)             {                 return BadRequest(ModelState);             }             // Perform update actions             return Ok(); } 


И здесь нам на помощь приходят фильтры действий. Создадим новый фильтр действий и перенесём в него повторяющиеся строки следующим образом:

public class EmployeeValidationFilterAttribute : ActionFilterAttribute {         public override void OnActionExecuting(ActionExecutingContext context)         {             var employeeObject = context.ActionArguments.SingleOrDefault(p => p.Value is Employee);             if (employeeObject.Value == null)             {                 context.Result = new BadRequestObjectResult("Employee value cannot be null");                 return;             }             if (!context.ModelState.IsValid)             {                 context.Result = new BadRequestObjectResult(context.ModelState);             }         } } 


Теперь удаляем ставшие ненужными части кода и применяем созданный нами атрибут-фильтр:

public class EmployeeController : ControllerBase {         [EmployeeValidationFilter]         [HttpPost]         public IActionResult Post([FromBody] Employee value)         {             // Perform save actions             return Ok();         }         [EmployeeValidationFilter]         [HttpPut]         public IActionResult Put([FromBody] Employee value)         {             // Perform update actions             return Ok();         } } 


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

[EmployeeValidationFilter] public class EmployeeController : ControllerBase {             // Perform update actions } 


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

Разработчики часто сталкиваются с задачей, когда требуется добавить логирование для определённых методов. Поэтому попробуем добавить в фильтры действий средство логирования, которое будет записывать информацию перед выполнением методов POST или PUT контроллера и сразу после. Наш фильтр будет выглядеть следующим образом:

public class LoggingFilter: IActionFilter {         private readonly ILogger _logger;         public LoggingFilter(ILoggerFactory loggerFactory)         {             _logger = loggerFactory.CreateLogger<LoggingFilter>();         }         public void OnActionExecuted(ActionExecutedContext context)         {             _logger.LogInformation($"{context.ActionDescriptor.DisplayName} executed");         }         public void OnActionExecuting(ActionExecutingContext context)         {             _logger.LogInformation($"{context.ActionDescriptor.DisplayName} is executing");         } } 


Теперь мы можем применить этот фильтр либо глобально, либо к конкретной области. Сначала попробуем зарегистрировать его глобально. Для этого нам в Startup.cs следует добавить следующие строки:

services.AddControllers(options => {                 options.Filters.Add<LoggingFilter>(); }); 


Если же нам нужно применить фильтр, например, к определённому методу контроллера, то следует его использовать вместе с ServiceFilterAttribute:

[HttpPost] [ServiceFilter(typeof(LoggingFilter))] public IActionResult Post([FromBody] Employee value) 


ServiceFilterAttribute является фабрикой для других фильтров, реализующей интерфейс IFilterFactory и использующей IServiceProvider для получения нужного фильтра. Поэтому в Startup.cs нам необходимо зарегистрировать наш фильтр следующим образом:

services.AddSingleton<LoggingFilter>(); 


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

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

public class ProviderFilter : IActionFilter {         private readonly IDataProvider _dataProvider;         public ProviderFilter(IDataProvider dataProvider)         {             _dataProvider = dataProvider;         }         public void OnActionExecuted(ActionExecutedContext context)         {         }         public void OnActionExecuting(ActionExecutingContext context)         {             object idValue;             if (!context.ActionArguments.TryGetValue("id", out idValue))             {                 throw new ArgumentException("id");             }             var id = (int)idValue;             var result = _dataProvider.GetElement(id);             if (result == null)             {                 context.Result = new NotFoundResult();             }             else             {                 context.HttpContext.Items.Add("result", result);             }         } } 


Применить этот фильтр можно так же, как и фильтр из предыдущего примера, с помощью ServiceFilterAttribute.

Фильтры действий раньше очень часто применяли, чтобы заблокировать контент для определённых браузеров на основе информации о User-Agent. На ранних этапах становления веб-разработки многие сайты создавались исключительно для наиболее популярных браузеров, остальные же считались запрещёнными. Сейчас данный подход является нежелательным, т.к. рекомендуется создавать такую HTML-разметку, которую смогло бы поддерживать большинство браузеров. Тем не менее, в некоторых случаях разработчику важно знать источник запроса. Ниже представлен пример получения User-Agent-информации в фильтре действий:

public class BrowserCheckFilter : IActionFilter {         public void OnActionExecuting(ActionExecutingContext context)         {             var userAgent = context.HttpContext.Request.Headers[HeaderNames.UserAgent].ToString().ToLower();             // Detect if a user uses IE             if (userAgent.Contains("msie") || userAgent.Contains("trident"))             {                 // Do some actions              }         }         public void OnActionExecuted(ActionExecutedContext context)         {         } } 


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

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

public class LocalizationActionFilterAttribute: ActionFilterAttribute {         public override void OnActionExecuting(ActionExecutingContext filterContext)         {             var language = (string)filterContext.RouteData.Values["language"] ?? "en";             var culture = (string)filterContext.RouteData.Values["culture"] ?? "GB";             Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo($"{language}-{culture}");             Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo($"{language}-{culture}");         } } 


Следующим шагом следует добавить маршрутизацию, которая будет перенаправлять URL с данными культуры на наш контроллер:

                endpoints.MapControllerRoute(name:"localizedRoute",                     pattern: "{language}-{culture}/{controller}/{action}/{id}",                     defaults: new                     {                         language = "en",                         culture = "GB",                         controller = "Date",                         action = "Index",                         id = "",                     });


Код выше создаёт маршрут с именем localizedRoute, у которого в шаблоне имеется параметр, отвечающий за локализацию. Значение по умолчанию для этого параметра en-GB.

Теперь создадим контроллер с именем DateController, который будет обрабатывать наш запрос, и представление, которое будет отображать локализованную дату. Код контроллера просто возвращает представлению текущую дату:

[LocalizationActionFilter] public class DateController : Controller {         public IActionResult Index()         {             ViewData["Date"] = DateTime.Now.ToShortDateString();             return View();         } }  


После того как пользователь перешёл по ссылке localhost:44338/Date, он увидит в браузере следующее:

image
На скриншоте выше текущая дата представлена с учётом локализации, заданной по умолчанию, т.е. с en-GB. Теперь, если пользователь перейдёт по ссылке, в которой будет явно указана культура, например, en-US, то он увидит следующее:


Таким образом, на этом примере мы можем увидеть, как сделать простую и быструю локализацию.

Заключение


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

Создаём по-настоящему надёжные плагины на платформе Managed Add-In Framework

22.04.2021 12:11:11 | Автор: admin

Однажды мы поняли, что для качественной и быстрой реализации разносторонних требований пользователей нам срочно нужны плагины. Изучив разнообразные платформ, мы выяснили, что наилучшим образом нам подойдёт Managed Add-In Framework (далее MAF) от Microsoft. Во-первых, она позволяет создавать плагины на базе .NET Framework, во-вторых, даёт возможность обмена данными и пользовательским интерфейсом между плагином и приложением-хостом, и в-третьих, обеспечивает безопасность и версионность, что делает плагины надёжными.

Жизнь показала, что мы были правы плагины работают, пользователи довольны, заказчик счастлив. Правда, у MAF есть ещё одна проблема недостаточное количество информации. Всё, что мы нашли это скудная документация да несколько постов на StackOverflow. Но этот пробел я частично заполню, описав, как создать плагин с нуля, с учетом всех нюансов работы с MAF. Эта статья будет полезна в качестве быстрого старта для тех, кто тоже решит освоить MAF для создания плагинов на базе .NET Framework.

Введение

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

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

Изучив различные платформы для создания плагинов, мы остановили свой выбор на MAF, которая позволяет создавать плагины на базе .NET Framework и содержит все необходимые для этого функции:

  • Поискплагинов, которыепридерживаются контрактов, поддерживаемых хост-приложением.

  • Активация: загрузка, запуск и установление связи с плагином.

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

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

  • Управлениедлительностью использования:предсказуемая и простая в использовании загрузка и выгрузка доменов приложений и процессов.

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

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

Представления, контракт и адаптеры являются сегментами, а связанные друг с другом сегменты являются конвейером (pipeline). Конвейер это основа, за счёт которой реализуется поиск и активация плагинов, изоляция, взаимодействие, управление длительностью использования и версионность.

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

Технология появилась в рамках .NET Framework 3.5, и с момента первого релиза она почти не развивалась. Из-за этого технология выглядит устаревшей, а её сложность отпугивает. Необходимость реализации 5 промежуточных слоёв между хостом и плагином может показаться избыточной, но именно благодаря этой многослойности MAF обеспечивает безопасность и версионность.

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

Начало

Проекты и структура папок

Для начала нужно посмотреть на структуру подключения плагинов черезMAF:

Представленияхоста(Host views of add-ins)

Содержат интерфейсы или абстрактные классы, представляющие,как выглядит плагин для хоста и какими типами данных они обмениваются.

Контракты(Contracts)

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

Требования: Все контракты должны наследоваться от IСontract, класс ContractBase содержит базовую реализацию IСontract. Для безопасности в контактах допускается использование только типов, унаследованных от IСontract, примитивных типов (целочисленные и булевые), сериализуемых типов (типы из mscorlib.dll; типы, определённые в контрактах, и ссылочные типы), коллекций из mscorlib.dll.

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

Представления плагина(Add-inviews)

Содержат интерфейсы или абстрактные классы, представляющие, как выглядит хост для плагина и какими типами данных они обмениваются; также содержат интерфейс плагина.

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

Адаптерыхоста(Host-side adapters)

Конвертируют представления хоста в контракт и наоборот в зависимости от направления вызова.

Адаптеры представления в контракт должны реализовывать соответствующий контракт (а значит, наследоваться от ContractBase), в то время как адаптеры контракта в представление должны реализовывать части представления, которые они конвертируют.

Для конструирования конвейера адаптеры необходимо отметить атрибутом HostAdapterAttribute.

Адаптерыплагина(Add-in-side adapters)

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

Для конструированияконвейера адаптеры отмечаютсяHostAdapterAttribute.

Каждому слою на физическом уровнесоответствуетсвой проект и своя папка(кроме хоста).Создаём все необходимые проекты дляконвейера (тип проектаClassLibrary):

  1. Pipeline.Contracts. Добавляем ссылки на System.AddIn и System.AddIn.Contract.

  2. Pipeline.AddInViews.ДобавляемссылкунаSystem.AddIn.

  3. Pipeline.HostViews.

  4. Pipeline.AddInAdapters.ДобавляемссылкинаSystem.AddIn,System.AddIn.Contract,Pipeline.AddInViews,Pipeline.Contracts.Для двух последних проектов отключаем в свойствах копирование (Copylocal=False),иначеMAFпри сборкеконвейера не сможет понять, какуюdllнадозагрузить в данной папкеи не подгрузит весь уровень.

  5. Pipeline.HostAdapters.Тоже самое, что и дляPipeline.AddInAdapters, только вместо проекта Pipeline.AddInViewsссылаемся наPipeline.HostViews.

  6. DemoPlugin(хост-приложение).

Все эти библиотеки должны располагаться в строгой иерархии папок:

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

Важно: каждый плагин должен лежать в отдельной папке, иначе MAF их не найдётВажно: каждый плагин должен лежать в отдельной папке, иначе MAF их не найдёт

Реализация взаимодействия хоста и плагина

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

[AddInBase]public interface IExportPluginView{  string DisplayName { get; }}

Когда используется MAF, интерфейсы на уровне хоста и плагина могут отличаться, а адаптеры будут подстраивать их друг под друга. Для простоты будем использовать одинаковые интерфейсы на всех уровнях конвейера. Так что создаём IExportPluginView в проектах Pipeline.AddInViews, Pipeline.Contracts и Pipeline.HostViews. Отличия между ними будут в том, что интерфейс в представлении плагина необходимо отметить атрибутом AddInBase, чтобы MAF по нему смог найти нужный плагин, а интерфейс в контрактах должен быть унаследован от IСontract, как упоминалось выше.

В свою очередь, в адаптерах прописываем стыковочные классы адаптеров. Сначала от представления плагина в контракты (проект Pipeline.AddInAdapters):

[AddInAdapter]public class ExportPluginViewToContractAdapter : ContractBase, Pipeline.Contracts.IExportPlugin{  private readonly Pipeline.AddInViews.IExportPluginView view;  public string DisplayName => view.DisplayName;    public ExportPluginViewToContractAdapter(Pipeline.AddInViews.IExportPluginView view)  {  this.view = view;  }}

В качестве аргумента конструктор принимает соответствующий интерфейс представления плагина. Также необходимо отметить этот класс атрибутом AddInAdapter для MAF.

Затем пишем адаптер от контрактов в представление хоста (проект Pipeline.HostAdapters):

[HostAdapter]public class ExportPluginContractToHostAdapter : IExportPluginView{  private readonly Contracts.IExportPlugin contract;  private readonly ContractHandle handle;    public string DisplayName => contract.DisplayName;    public ExportPluginContractToHostAdapter(Contracts.IExportPlugin contract)  {  this.contract = contract;    handle = new ContractHandle(contract);  }}

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

Активация и интеграция плагина

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

string pipelinePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Pipeline"); AddInStore.Update(pipelinePath); 

MAFсоздаётдвафайла:

  • Pipeline/PipelineSegments.storeкешсегментовконвейера

  • Pipeline/AddIns/AddIns.storeкешплагиноввпапкеAddIns

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

Если плагины располагаются в других папках, нужно сгенерировать кеши и в них:

AddInStore.UpdateAddIns(otherPluginsPath);

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

Затем нужно найти все плагины, которые реализуют нужный нам интерфейс, то есть IExportPlugin:

varaddInTokens=AddInStore.FindAddIns(typeof(IExportPluginView),pipelinePath);

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

Есть несколько вариантов активации плагина в зависимости от требуемой изоляции от хоста:

  1. Без изоляции: плагиныи хост запускаются в одном процессе и одном домене приложения.

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

  2. Средняя изоляция: каждый плагин запускается в своём домене приложения.

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

  3. Высокая изоляция: каждый плагин запускается в своём процессе.

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

Мы для себя выбрали второй вариант, так как он даёт достаточно безопасности и гибкости, но при этом не требует танцев с бубнами для налаживания взаимодействия между процессами. Для нашего демо выберем тоже средний уровень изоляции с минимальным уровнем безопасности:

varplugin =addInTokens.First().Activate<IExportPluginView>(AddInSecurityLevel.FullTrust);

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

MessageBox.Show($"Plugin '{plugin.DisplayName}' has been activated","MAF Demo message",MessageBoxButton.OK);

Расширение функционала

Реализация обратного взаимодействия от плагина к хосту

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

Пусть пока API для плагинов содержит метод получения даты последнего изменения файла. Создадим IPluginApi интерфейс в сегментах HostView, AddInView и Contracts (здесь не забываем отметить атрибутом AddInContract и отнаследоваться от IContract так же, как и в случае с IPluginView):

[AddInContract]public interface IPluginApi : IContract{DateTime GetLastModifiedDate(string path);}

Для него нужны адаптеры, так же как и для IPluginView, но в обратную сторону. Соответственно, на уровне адаптеров хоста это будет адаптер из представления хоста в представление контракта, а на уровне адаптеров плагина это будет адаптер из представления контракта в представление плагина:

[HostAdapter]public class PluginApiHostViewToContractAdapter : ContractBase, IPluginApi{  private readonly HostViews.IPluginApi view;    public PluginApiHostViewToContractAdapter(HostViews.IPluginApi view)  {  this.view = view;  }    public DateTime GetLastModifiedDate(string path)  {  return view.GetLastModifiedDate(path);  }}

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

Адаптерхоста:

public void Initialize(IPluginApi api){  contract.Initialize(new PluginApiHostViewToContractAdapter(api));}

Адаптерплагина:

public void Initialize(IPluginApi api){  view.Initialize(new PluginApiContractToPluginViewAdapter(api));}

В качестве финального штриха реализуем интерфейс Pipeline.HostView.IPluginApi на стороне хоста и инициализируем им наш плагин:

var plugin = addInTokens.First().Activate<IExportPluginView>(AddInSecurityLevel.FullTrust); plugin.Initialize(new PluginApi()); 

ДобавляемUIдляплагина

Для отображения пользовательского интерфейса плагина в хост-приложении в MAF существует специальный интерфейс INativeHandle, который предоставляет доступ к дескриптору окна (Hwnd). INativeHandle, получив дескриптор окна из ресурсов, передаётся между доменами приложений, таким образом хост может показать объект пользовательского интерфейса плагина.

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

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

FrameworkElementGetPanelUI();

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

INativeHandleContractGetPanelUI();

Для его создания в адаптереплагинаиспользуется конвертер, интегрированный вMAF:

public INativeHandleContract GetPanelUI(){  FrameworkElement frameworkElement = view.GetPanelUI();  return FrameworkElementAdapters.ViewToContractAdapter(frameworkElement);}

А для получения элемента интерфейса из дескриптора используется обратный метод конвертера:

public FrameworkElement GetPanelUI() {  INativeHandleContract handleContract = contract.GetPanelUI();  return FrameworkElementAdapters.ContractToViewAdapter(handleContract); } 

Для использования класса FrameworkElementAdapters необходимо подключить библиотеку System.Windows.Presentation.

Сделав всё это, остаётся только нарисовать контрол, который плагин будет отдавать хост-приложению по запросу:

public FrameworkElement GetPanelUI() {  return new PanelUI(); } 

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

С использованием пользовательского интерфейса, переданного таким образом, есть нюансы:

  • кконтролу плагина невозможно применить триггеры;

  • контрол плагина будет отображаться поверх других контролов этого окна вне зависимости от ZIndex, но другие окна приложения (диалоги, всплывающие окна) будут отображаться как положено (нам из-за этого пришлось переделать реализацию тост-сообщений в виде popup-окон);

  • контрол плагина не будет отображаться, если к окну применена прозрачность; кроме того, свойство AllowTransparenсy должно иметь значение false. (В нашем случае отображению контрола мешало свойство WindowChrome.GlassFrameThickness);

  • хост-приложение не может получить события от действий мышки, так же как и события GotFocus и LostFocus, а свойство IsMouseOver для контрола плагина всегда будет false;

  • в контроле плагина нельзя использовать VisualBrush, а также проигрывать медиа в MediaElement;

  • к контролу плагина не применяются трансформации: поворот, масштабирование, наклон;

  • и конечно, такой контрол плагина не поддерживает изменения его XAML на лету изменения не подтянутся во время исполнения;

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

Стилизация плагина

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

Для этого необходимо добавить новый проект типа Class library, в который добавить словари ресурсов (ResourceDictionary) с необходимыми стилями, а также создать один словарь, который будет агрегировать их все:

Для того чтобы использовать эти стили в плагине, в проект плагина нужно добавить созданный проект со стилями и потом указать ссылку на сводный словарь ресурсов в XAML-файле, где необходимо применить общие стили:

<UserControl.Resources>     <ResourceDictionary>        <ResourceDictionary.MergedDictionaries>             <ResourceDictionary Source="pack://application:,,,/Demo.Styles;component/AllStyles.xaml"/>          </ResourceDictionary.MergedDictionaries>     </ResourceDictionary> </UserControl.Resources> 

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

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

Деактивация и выгрузка плагинов

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

Выгрузка плагина состоит из двух этапов:

1. Закрыть ссылку на контракт.

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

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

[AddInAdapter] public class PluginApiContractToPluginViewAdapter : AddInViews.IPluginApi{  public void Unload()  {  handle.Dispose();  }} 

Чтобы этот метод можно было вызвать из хост-приложения, добавим его и в интерфейс IPluginHostView.

2. Выключить плагин.

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

var controller = AddInController.GetAddInController(plugin); controller.Shutdown();  

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

Заключение

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

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

P. S. Полный код демо-приложения можно найти на github.

Подробнее..

Маленькие тайны тестирования большой LMS

09.09.2020 12:15:30 | Автор: admin


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

Наш проект представляет собой систему дистанционного обучения (LMS, Learning Management System), которой пользуются более 7 миллионов человек в разных странах мира. В системе 1000+ веб-страниц и порядка 10 000 тест-кейсов.



Сейчас в проекте работает около 15 команд разработки со стороны заказчика в Норвегии и со стороны Аркадии в России. Я присоединилась к проекту 8 лет назад в качестве QA; последние 2 года я работаю в качестве QA lead, участвуя в оптимизации процесса тестирования.

Что входит в понятие оптимального процесса


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

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

Процесс разработки в целом:


a) Подход к разработке, удовлетворяющий нужды команды
Мы работаем, используя Scrum и спринты продолжительностью 3 недели. Перед спринтом проходит презентация его целей и формируется набор требований для данного спринта. Далее идет планирование, на котором мы оцениваем все задачи и определяем набор задач, который войдет в спринт. По окончании спринта проходит Sprint review, на котором мы демонстрируем все выполненные задачи и анонсируем достигнутые цели. Такой подход для нас является оптимальным: за спринт мы успеваем сделать достаточное количество новой функциональности и при этом исправить и протестировать определенное количество багов от конечных пользователей на такие баги выделяется 10% времени спринта.



Состав команды: team lead, разработчики, тестировщики. Соотношение разработчиков к тестировщикам в наших командах 3:1. Такое соотношение дает возможность достигать цели спринта равномерно нет периодов, когда кто-то из участников процесса простаивает: пока разработчики делают какое-либо изменение, тестировщики создают или обновляют тест-кейсы, относящиеся к этому изменению; когда разработка закончена, тестировщики проверяют изменения, а разработчики либо переходят к следующим задачам спринта, либо помогают в тестировании (это бывает необходимо при тестировании масштабных изменений).
Product Owner определяет цели и требования в начале каждого спринта и принимает в конце. Также у каждой команды есть Scrum Master, который помогает решать возникающие проблемы.



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

b) Четкие требования и хорошее планирование
Для того, чтобы планирование не затянулось, а спринт не стал провальным из-за неизвестных ранее деталей и трудоемких дополнительных изменений, добавленных уже в процессе спринта, мы стараемся брать в спринт только изменения с достаточно ясными и четкими требованиями. Если область проекта, которая касается изменений, неизвестна команде, или в течение планирования возникает много вопросов, на которые Product Owner не может дать ответов, команда может взять в спринт задачу на изучение данной области либо задачу на исследование, результатом которого становятся четкие требования или даже некий прототип.

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

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

e) И, на мой взгляд, основополагающее это хорошая коммуникация. И то, что есть у нас в компании, для меня является одним из главных преимуществ комфортной работы доброжелательность, желание идти на компромиссы.
Это касается всех: и каждого члена команды, и Product Owner, и Scrum Master, и менеджмента компании, и многих других участников процесса. Все открыты для вопросов и обсуждений, разработчики советуются с тестировщиками в вопросах требований и вместе решают, как лучше (и с точки зрения разработки, и с точки зрения тестирования) сделать то или иное изменение. Product Owner, в свою очередь, постоянно на связи с командой, оперативно решает все вопросы и всегда старается идти навстречу в достижении целей спринта. Scrum Master всегда готов помочь: найти дополнительные ресурсы (тестировщиков/разработчиков, если они требуются для спринта или для релиза) или подсказать, как лучше организовать спринт по времени.

Процесс тестирования:


a) QA-стандарты (guidelines), относящиеся к написанию тест-кейсов.
У нас в проекте тест-кейсы являются основной детальной документацией по функционалу системы, поэтому им уделяется большое внимание. Для каждого изменения, сделанного командой, пишется новый тест-кейс или обновляется уже существующий.
Раньше, когда система была еще не такой огромной, тест-кейсы писались достаточно детальные, с различными конкретизированными шагами, но сейчас этот подход стал неприемлем на обновление таких тест-кейсов уходит очень много времени. Поэтому мы (QA leads) разработали стандарты, основным требованием которых является написание сценариев тестирования с ожидаемыми результатами вместо детально расписанных шагов в тест-кейсах.

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

c) QA-стандарты, относящиеся к тестированию релизов.
Релизный процесс и стандарты, используемые в нем, более подробно рассмотрим ниже.

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

e) Взаимовыручка тестировщиков, помощь разработчиков.
У нас не очень много тестировщиков (в среднем 1 тестировщик на 3 разработчиков), к тому же время от времени они отвлекаются от задач спринта на тестирование релизов, и времени на всё может не хватать.
В таком случае всегда находится кто-то из тестировщиков других команд с меньшей нагрузкой в текущий момент, кто может помочь. Найти такого тестировщика помогает QA lead или Scrum Master. Кроме того, разработчики могут помочь с тестированием изменений в спринте, если по ним уже написаны тест-кейсы.

f) Коммуникация между тестировщиками.
Для коммуникации используется чат в Microsoft Teams, в котором каждый может задавать вопросы и оперативно получать ответы, а остальные тестировщики при этом узнают актуальную информацию. Также у нас проходят ежемесячные QA-митинги, на которых тестировщики делятся друг с другом текущими задачами своей команды и могут обсуждать любые вопросы подход к тестированию и/или расположение тест-кейсов, касающихся незнакомой для тестировщика области; вопросы по релизу (состав будущей релизной команды, изменение сроков тестирования); дополнительные обязательные проверки, добавленные после пропуска критичного бага в релизе, и т.д.).

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

Чем хороши ежемесячные релизы и как мы к ним пришли: организация процесса тестирования во время релиза


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

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

Весь цикл разработки выглядел примерно так:



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

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

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

Стабилизация включает в себя:

a) тестирование нового функционала, включенного в данный релиз каждой командой;
b) тестирование критичных областей это проверка основной функциональности главных областей системы (что, очевидно, занимает гораздо меньше времени, чем полный цикл регрессии);
c) тестирование багов, найденных в командных изменениях для данного релиза.

Весь цикл разработки теперь выглядит так:



Рассмотрим, что еще необходимо для подготовки к релизу.

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

Можно подумать, что 2 недели подготовки к релизу это слишком много, и на тестирование в спринте остаётся мало времени. Но обычно у тестировщика подготовка к релизу занимает 4-6 дней. Это зависит от:

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

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

Общее расписание тестирования релиза выглядит так:



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

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

  • определение релизной команды и проверка доступности всех ее участников на время релиза;
  • подготовка тест-планов для релиза;
  • запуск автотестов на этапе стабилизации и тестирования конфигурации;
  • координация работы тестировщиков во время тестирования релиза;
  • помощь тестировщикам в решении всевозможных вопросов, связанных с релизом;
  • контроль за выполнением тестирования и при необходимости перераспределение нагрузки для помощи в тестировании перегруженным командам (это могут быть либо тестировщики других команд, которые уже закончили релизное тестирование, либо разработчики команды, либо сам QA-координатор).


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

  • Проблема: иногда команды добавляют свои изменения в релиз в последний момент перед началом стабилизации, из-за этого стабилизация начинается позже, и обычно такие изменения содержат баги, потому что из-за спешки их могли не очень тщательно протестировать в спринте.
    Решение: стараемся избегать добавления таких опасных изменений.
  • Проблема: баги находятся на этапе тестирования конфигурации на pre-production окружении. Это слишком поздно приходится исправлять и пересобирать релизный пакет.
    Решение: после таких ситуаций мы обновляем критичные области на стабилизации.
  • Проблема: увеличение времени на тестирование релиза, что может быть причиной сдвига даты релиза.
    Причинами увеличения времени могут быть следующие факторы:
    a) сдвиг релиза в целом по каким-то веским причинам и тем самым больший объем командных изменений в релизе (время между релизами не месяц, а несколько месяцев),
    b) нестабильная работа окружения для тестирования стабилизации или конфигурации,
    c) конфигурационные баги окружения,
    d) большое количество нетривиальных багов команды, найденных на стабилизации. Как правило, это интеграционные баги, связанные с изменением одной области разными командами.
    Решение: в таких случаях мы стараемся успеть протестировать всё необходимое для релиза, даже если это требует участия в релизе все 2 недели или сверхурочную работу, так как релиз является более приоритетной задачей, чем задачи спринта.


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

Работа в условиях карантина: как обеспечить работу тестировщиков


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

В период пандемии, когда и Норвегия, и Россия отправили большинство населения на самоизоляцию, нам пришлось перейти на удаленную работу.

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

Коммуникация все так же оставалась на хорошем уровне приложение Teams покрывало все нужды: активные беседы шли в чате, митинги проходили без проблем; если были какие-то вопросы, которые нужно было срочно обсудить, созванивались с любым участником проекта.

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

Единственная проблема, с которой мы столкнулись, сидя дома на удаленной работе в силу особенностей VPN невозможно было тестировать систему на командном окружении с телефонов/планшетов. Но эту проблему удалось обойти спасибо менеджеру проекта и IT-службе, которые нашли решение. Мы стали использовать прокси при подключении по домашней сети, и теперь можем тестировать на мобильных устройствах из дома.

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

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

Заключение


Подводя итог вышесказанному, хочется отметить несколько моментов:

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

Эволюция процесса релиза LMS

20.11.2020 12:14:59 | Автор: admin


К чему вы стремитесь в работе? Мной всегда двигало желание быть причастным к чему-то, что действительно помогает людям решать важные задачи. Это стремление привело меня в проект онлайн-системы дистанционного обучения (Learning Management System, сокращённо LMS).

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

Что вы представляете, когда слышите об обучении в школе или университете? О чём бы вы сейчас ни подумали, скорее всего, это реализовано в LMS. Все инструменты, необходимые преподавателям для преподавания, ученикам для обучения, родителям для контроля, а директорам для администрирования учебного процесса, доступны в электронном виде. Платформой ежедневно пользуется более 7 000 000 пользователей из Европы и США, а трудится над ней распределённая команда из нескольких стран.

Особенности релиза


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

В 2013, когда я пришёл в проект, релизы случались 3-4 раза в год; им предшествовало регрессионное тестирование длительностью от 2 до 3 недель, в котором участвовали все разработчики и тестировщики. В период регрессии разработка полностью останавливалась, поскольку важно было пройти каждый тест-кейс вручную, а их, как можно догадаться, было немало. Помимо очевидных издержек, регрессия была весьма утомительной для всех её участников.

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

Монолитная архитектура


Проект к тому времени уже имел долгую историю разработки. Начавшись как студенческая курсовая работа, система перетекла в продолжительную фазу разработки на C++ (да, это было давно), затем шёл долгий этап развития на ASP.NET, который используется и по сей день. Многие актуальные сейчас подходы к разработке отсутствовали или только зарождались, .NET 2.0 вовсе не имел такую палитру возможностей, которая есть в современном .NET Core. То, что сейчас может видеться Франкенштейном, некоторое время назад не осознавалось как что-то ужасное. Архитектура системы была монолитна, как титановый шар, но до какого-то момента это всех устраивало.

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

Жёсткие требования


Если в России подобные системы даже на момент выхода статьи используются нечасто и почти всегда вся информация дублируется на бумаге, то в школах и университетах Европы LMS это ядро обучения. Все данные о студентах, учителях, родителях, их взаимодействии, посещаемости, успеваемости, учебные материалы, домашние работы и др. хранятся в электронном виде без двойного документооборота. Исходя из этого, к системе предъявляются крайне жёсткие требования безопасности, производительности и, что очень важно, доступности. Представьте, что ваш главный и единственный ресурс, обеспечивающий учебный процесс, вдруг сломался. Недоступность в течение 5 минут вызовет у вас дискомфорт, а часовой простой крайнюю степень возмущения. Поэтому uptime время, когда система доступна и полностью выполняет свои функции одна из ключевых метрик, которая является юридическим обязательством, за неисполнение которого предусмотрены большие штрафы. Uptime 99.9% это цифра, к которой мы стремимся.

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

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

Неоптимальные процессы


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

TL;DR


Дано: релизы раз в квартал, амбиции роста, сложный продукт с монолитной архитектурой, неоптимальные процессы, консервативность OPS.
Хотим: денег частых релизов новых фич.
Не хотим: штрафов даунтайма.

Трансформация


С осознанием глобальных проблем и чётким пониманием того, что хотелось бы иметь в итоге, компания начала постепенные изменения.

В этой статье я остановлюсь на изменениях в разработке и непосредственном процессе релиза, но стоит отметить, что немало изменений произошло и в работе OPS.

Устранение инфраструктурных проблем


Для начала нужно было устранить некоторые инфраструктурные проблемы.

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

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

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

Формализация и сокращение таймлайна


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

Мы начали с робких попыток ежемесячных релизов, затем постепенно дошли до релизов раз в 2 недели. Короткие циклы благотворно сказались на качестве. Меньше кода меньше рисков, больше вероятность заранее выявить все потенциальные проблемы.

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

Как устроен релизный цикл
При обновлениях каждые две недели релизная итерация занимала почти 5 недель. Как же так?

Разберём один двухнедельный релиз методом обратного планирования. Мы знаем дату релиза и сколько занимают стабилизационное тестирование и стейджинг релиз-кандидата (2 недели), тогда к началу стабилизации в релиз должны быть включены все новые фичи (code freeze). Всё, что не было включено вовремя (кроме редких заранее оговорённых случаев), переходит на следующий релиз. Чтобы включить новую разработку в релиз, нужно полностью протестировать её функциональность, включая переводы, и убедиться в отсутствии багов. Локализацию выполняет сторонняя компания, и ей требуется около недели, чтобы перевести новые строки. Поэтому за 2 недели до начала тестирования релиз-кандидата мы отправляем на перевод новые строки. После получения переводов у команд есть примерно неделя, чтобы закончить тестирование и попасть в релиз. Непосредственно после релиза идёт неделя мониторинга новых проблем на продакшене. За это время нужно убедиться, что релиз не привнёс никаких новых проблем, а если проблемы появились решить, требуется ли их устранить до следующего релиза.



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

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

Новая стратегия создания веток


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

Схема, к которой мы в итоге пришли, была придумана не нами, но идеально подошла под наши нужды и выглядит так:



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

Изменение подхода к тестированию релиз-кандидата


Мы приняли решение отказаться от ручной регрессии в привычном виде, поскольку новая парадигма не позволяла тратить на тестирование так много времени. Регрессия трансформировалась в тестирование критических областей: были выявлены самые часто используемые и самые важные функции, которые нужно обязательно проверить перед выходом релиза. Мы постепенно покрываем эти области Selenium-тестами, уменьшая объём ручного тестирования.

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

Отказываться от полноценного регрессионного тестирования было страшно, но, к счастью, заметной деградации качества не произошло.

Изменения в архитектуре


Монолитную систему релизить долго, сложно и рискованно. Переход к частым релизам форсировал декомпозицию на мелкие компоненты, которые можно деплоить отдельно (single deployable). Ввиду размеров проекта этот процесс до сих пор не завершён, но шаг за шагом мы вычленяем новые компоненты, релиз которых может происходить отдельно от релиза главного приложения. Новые компоненты сразу разрабатываются обособленно.

TL;DR


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

Релиз-менеджмент: из креатива в рутину


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

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

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

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

Обязанности менеджера релиза
Вот неполный перечень того, что делает менеджер релиза:

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


Что НЕ входит в задачи менеджера релиза:

  • развёртывание обновления в продакшене для этого есть OPS;
  • исправление найденных багов за это ответственны команды.


TL;DR


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

Так ли всё идеально?


Если у вас сложилось впечатление, что в проекте теперь всё идеально, то спешу вас уверить, что идеала не существует (что не мешает нам к нему стремиться).

Как и в других проектах, мы часто балансируем между созданием новых фич и техническими улучшениями, которые не добавляют прямой ценности с точки зрения конечных пользователей. Поэтому автоматизация регрессии не закончена на 100%, иногда в релиз-кандидате проскакивают баги, и приходится быстро принимать решение о дальнейших действиях. Иногда релизы отменяются или переносятся по разным причинам, начиная с долгих праздничных дней в начале года и мае и заканчивая поздно найденными критическими багами или форс-мажорами. А ещё случаются активности OPS обновления серверов, миграции данных, установка новой инфраструктуры, на время которых релизы останавливаются.

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

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



От маленьких, быстро тестируемых и малорискованных релизов произошёл откат в сторону долгой стабилизации (и как следствие, более долгой итерации), а также большего количества багов в релиз-кандидате, что было очень непривычно.
Стоит отметить, что даже при таком положении дел релизы оказались менее трудозатратны и рискованны, чем раньше.

Дальнейшее развитие


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

Вектор дальнейшего улучшения релиза предопределён:

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


Выводы


В разработке сложного веб-приложения релиз новой версии занимает особое место. За несколько лет мы сумели перейти с ежеквартальных релизов к релизам раз в 2 недели, но впоследствии остановились на ежемесячных. Это оказалось непросто и увлекательно, трансформация потребовала комплексных изменений, которые коснулись разработчиков, тестировщиков, OPS и менеджмента. Релизы новой версии стали менее объёмными, менее рискованными и более прозрачными с точки зрения сроков закончилась эпоха, когда релизы были СОБТИЕМ. К сожалению, традиция есть мороженое по случаю релиза тоже закончилась. ;(
Подробнее..

Стратегия тестирования краткосрочного проекта

18.02.2021 16:06:37 | Автор: admin

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

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

Как всё начинается

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

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

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

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

Вся эта основа для проекта попадает к нашей команде. На краткосрочных проектах она невелика и состоит из менеджера проекта, двух-трёх программистов, UX/UI-дизайнера и тестировщика.

Когда и как начинается тестирование

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

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

Источник картинки:https://devopedia.org/shift-left

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

Вопросник: тестирование концепции

Целевая аудитория и условия использования ПО

Что выясняем

Пример

Откуда берём информацию

Что за программный продукт мы делаем?

Краткое описание продукта?

Основные функции?

Приложение с упражнениями по английской грамматике, 5 упражнений в день

Опрашиваем заказчика

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

Для школ.Ученики 5-9 классов

В каких условиях будут пользоваться будущим ПО?

Типы устройств?

Тип подключения к сети?

С мобильных устройств, стабильный Wi-Fi в классе

Материальная база тестирования на чём тестируем

Что выясняем

Пример

Откуда берём информацию

Платформы

iOS
Android
Windows

Определяется из ответов заказчика на предыдущие вопросы + смотрим статистику по целевой аудитории

Операционные системы (конкретные)

iOS versions
iOS 13
iOS 12

Android OS versions
Pie
10
Oreo

Устройства, на которых должно работать ПО (список устройств)

iPhone..
SamsungGalaxy..
Huawei..

Интеграция с тем, что уже есть

Что выясняем

Пример

Откуда берём информацию

Системы, с которыми приложение должно интегрироваться

Аккаунт на десктопной версии сайта

Команда заказчика, технические специалисты, документация по системам, тестирование систем

Способы и инструменты для проверки того, что ничего и нигде не было потеряно

API

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

Тестирование требований

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

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

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

Тестировщик берёт требования в том виде, в котором они есть. Допустим, это будут user stories (пользовательские истории).

1. User story

Userstory(пользовательская история) состоитиз заголовка, представляющего собой основной сценарийиспользованияи дополнительныхсценариевсконкретикой.

Схема Userstory:

As a <role> I want <functionality>so that <benefit>

Как <роль>,я хочу <функциональность>,чтобы <получить выгоду/ достичь цели>

Пример Userstory:

Как пользователь,я хочу иметь доступ к упражнениям с 9 утра до 9 вечера

Дополнительные сценарии могут быть описаны в критериях приёмки (acceptance criteria). О том, как их можно конкретизировать, я расскажу позже.

AcceptanceCriteria

AcceptanceCriterion1

Как пользователь,я могу просматривать упражнения дня с 9 утра до 9 вечера

AcceptanceCriterion2

Как пользователь,я могу создавать ответы на упражнения дня с 9 утра до 9 вечера

AcceptanceCriterion3

Как пользователь,я могу редактировать ответы на упражнения дня с 9 утра до 9 вечера

AcceptanceCriterion4

Как пользователь,я могу отправлять ответы на упражнения дня на проверку с 9 утра до 9 вечера

2. Глоссарий проекта

Поскольку пользовательские истории пишутся человеческим, а не техническим языком, важно удостовериться, что:

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

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

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

Пример глоссария

Понятие и все слова, которыми мыобозначаемэто понятие

Определение понятия

Пример

Упражнения, набор упражнений, задание, домашняя работа

Набор упражнений, которые учащийся должен сделать за день (с по )

5 упражнений по грамматике до 23:59 в данном часовом поясе

3. Умолчания и дополнения

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

Исходный текст с умолчаниями

Умолчания восстановлены

меняем вадминке

меняем вадминка-тесты-курс-семестр-тест-папка с настройками-xml

атрибутА

атрибут Акласса Б

После восстановления умолчаний задаём вопросы для получения дополнительной информации:

Понятие и все слова, которыми мы называем понятие

Определение понятия

Пример

Вопросы

Упражнения, набор упражнений, задание, домашнее задание

Набор упражнений, которые учащийся должен сделать за день

Ежедневные упражнения по грамматике, которые должны быть сданы сегодня

До какого точно времени учащийся должен отправить задания?

Сколько упражнений в ежедневном наборе?

4. Acceptance Criteria основа для чек-листа

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

а) конкретизируемAcceptanceCriteria;

б) закладываем основу для чек-листа.

Далее переходим к созданию основы чек-листа. На этом этапе выкидываем обороты типа система должна и также ожидаемо и отжимаем текст от воды до списка требований по пунктам.

Исходный текст

Текст без воды

Система должна давать учащимся возможность выполнять упражнения ежедневно во временной промежуток с 9 утра до 9 вечера

-упражнения доступны с 9:00 утра до 9:00 вечера

-упражнения недоступны с 9:01 вечера до 8:59 утра

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

После того, как были конкретизированыAcceptanceCriteria, переходим к тест-дизайну.

Тест-дизайн и тестирование прототипа

Заголовок user story становится заголовком для группы проверок. Acceptance Criteria становится заголовком отдельного чек-листа или интеллект-карты, куда мы выписываем и ранжируем проверки.

Что получается в результате

ИсходнаяUserStory

Чек-лист

User story

As a<role>I want<feature>So that<benefit>

Как пользователь,я хочу иметь доступ к упражнениям с 9 утра до 9 вечера

User role can do <> to get ...

Пользователь имеет доступ к упражнениям с 9 утра до 9 вечера

Acceptance Criterion 1

Given<initial context>When<event occurs>Then<ensure come outcomes>

Checklist...

1. Задания можно просмотреть с 9 утра до 9 вечера

2. Задания можно выполнить с 9 утра до 9 вечера

3. Ответы на задания можно отправить на проверку с 9 утра до 9 вечера

4. Ответы на задания можно отредактировать с 9 утра до 9 вечера

5. Задания недоступны для просмотра и выполнения с 9 вечера до 9 утра

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

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

Примеринтеллект-карты

Источник картинки: автор статьи

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

В параллель с тем, как тестировщик пишет чек-листы, UX/UI-дизайнер создаёт прототип. Тестировщик проверяет интерфейс прототипа, корректирует чек-листы, обсуждает найденное с командой.

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

Специфика тестирования в краткосрочном проекте

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

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

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

Пример позитивного кейса

<Пользователь может делать задания с 9:00 утра до 8:59 вечера>

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

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

<Задания недоступны для просмотра и выполнения с 9:00 вечера до 8:59 утра>

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

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

Жизнь после релиза

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

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

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

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

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

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

Заключение

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

Далее следует этап тестирования требований, во время которого тестировщик создаёт максимально целостное и непротиворечивое описание продукта и его работы. Здесь идёт работа с user story и acceptance criteria, на основе которых тестировщик составляет чек-листы и интеллект-карты со списком конкретных проверок.

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

Подробнее..

Вначале былworkflow

08.12.2020 14:22:23 | Автор: admin

Добрый день! Меня зовут Кирилл,и яDevOps-инженер.За свою карьеру мне нераз приходилось внедрятьDevOps-практики как всуществующие,так и в новые команды, поэтому хочу поделиться своим опытом и мыслями по поводу стратегий ветвления. Существует множество различных типов рабочихпроцессов,и чтобы разобраться что к чему, предлагаю рассмотреть пример создания новогопрограммногопродукта.

Часть 1: Рабочий процесс

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

А далее как обычно бывает: всплывают первые запросы от пользователей на добавление новых фич/устранение багов ит.д., разработка кипит. Для того чтобы ускорить выход новых версий, принимается решение расширить командуDevOpsом, и для решения насущных проблемDevOpsпредлагает построитьCI/CD-конвейер (pipeline). И вот пришло время рассмотреть, как жеCI/CD-конвейерляжет на нашрабочий процесс,где у нас сейчас только мастер.

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

А теперь рассмотрим ситуацию, когдаконвейерпрервался на тестах.

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

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

  • уменьшение производительности,

  • впустую потраченное время,

  • много головной боли.

Для решения насущных проблемможноприбегнуть к изменениюрабочего процесса.

Первым делом добавимнебезызвестныеfeature-ветки.

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

И в очередной разпроиграемпроблему: вfeature-ветке обнаружен баг.

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

Но что,еслина окружение попал новый мастер,и спустя какое-то время обнаружен баг (не углядели, всякое бывает).

Соответственно, это уже критическая ситуация: клиент не доволен, бизнес не доволен. Нужно срочно исправлять! Логичным решением будет откатиться. Но куда? Заэтовремя мастер продолжал пополняться новыми коммитами. Даже если быстро найти коммит,в котором допущена ошибка,и откатить состояние мастера, то что делать с новыми фичами, которые попали в мастер после злосчастного коммита? Опять появляется много вопросов.

Что ж, давайте не будем поддаваться панике,и попробуем ещёраз изменить нашрабочий процесс,добавив теги.

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

Но вот в очередной раз пропущен баг в тегеv2.0.0, который уже на окружении.

Как решить проблему теперь?

Правильно, мы можем повторно развернуть версиюv1.0.0, считая её заведомо рабочей.

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

  • сэкономили время и,как следствие,деньги,

  • восстановили работоспособность окружения,

  • предотвратили хаос,

  • локализовали проблему в версииv2.0.0.

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

Для примера возьмём и рассмотримдавновсем известныйGitFlow:

Сравним его с нашим последним примером иувидим,что у нас нетdevelop-ветки, а ещёмы не использовалиhotfixes-ветки. Следовательно,мы не можем сказать, что использовали именноGitFlow. Однако мы немного изменим наш пример, добавивdevelop-иrelease-ветки.

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

Что ж, наGitFlowжизнь не заканчивается, ведь есть не менее известныйGitHubFlow.

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

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

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

Часть 2: Участь DevOps'а

В первой части мы рассмотрели, как выглядитрабочий процесс, а теперь посмотрим, почему для DevOps-инженератак важен корректно настроенный рабочий процесс.Для этого вернёмся к последнему примеру,аименно к построению того самогоконвейерадля реализации процесса CI/CD.

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

Собственно, построениеконвейераможно изобразить вот такой простой картинкой:

Ну или одним вопросом: как связать между собой код в репозитории и окружение?

Следовательно, нужно понимать,какой именно код должен попасть в окружение, а какой нет. К примеру, еслив ответна вопрос: Какойрабочий процессиспользуется? мы услышим: GitHubFlow, то автоматически мы будем искать нужный код вmaster-ветке. И ровно наоборот, если не построен никакойрабочий процесси куски рабочего кода разбросаны по всему репозиторию, то сначала нужно разобраться срабочим процессом, а лишь потом начинать строитьконвейер.Иначе рано или поздно на окружение попадёт то, что возможно не должно там быть, и как следствие,пользователь останется без сервиса/услуги.

Самконвейерможет состоять из множества шагов, в том числе у нас может быть несколько окружений.

Нодля наглядностидалее рассмотрим два основных этапа вCI/CD- конвейерах: build и deployment/delivery. И начнем мы,пожалуй,с первогоbuild.

Buildпроцесс, конечным результатом которого является артефакт.

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

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

И вот пример из реальной жизни.

Представьте ситуацию, когда вы хотите загрузить новую версиюUbuntu, и вместо такого списка версий:

...увас будет списокхешейкоммитов. Следовательно, это может бытьнеудобноне только для команды, но и для пользователя.

Бываютслучаи, когда мы можем пренебречь именованием. Поэтому рассмотрим ещёодин небольшой пример:у нас нет конкретногорабочего процесса;как следствие,у нас нет понимания,что именно мы должны хранить в нашем хранилище. Что,в свою очередь,может бытьчреватопоследствиями, так как хранилище так или иначе ограничено: либо деньгами, либо местом, либо и тем,и другим. Поэтому в ситуации, когда у нас нет конкретногорабочего процесса,мы можем начать публиковать артефакт из каждойfeature-ветки (так как чёткой определённости у нас нет), но в таком случае рано или поздно возникнет ситуация, когда ресурсы закончатся, и придётся расширяться, что опять же несёт за собой трату как человеческих, так и денежных ресурсов.

Конечно,на этом примеры не заканчиваются, но думаю,чтотеперь мы можем перейти к delivery/deployment.

Deliveryпроцесс,в рамках которого развёртка приложения на окружении происходит вручную.

Deploymentпроцесс,в рамках которого развёртка приложения происходит автоматически.

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

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

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

Сейчас мы рассмотрели лишь две основных стадии при построенииконвейера, но однозначно можно сказать, что беспорядок врабочем процессебудет влиять на каждый этап реализации процессовCI/CD. И под беспорядком имеется в виду не только отсутствиерабочего процесса, но и его избыточность.

Заключение

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

Подробнее..

Студенческие IT стажировки как мы стараемся делать их наиболее эффективными

30.09.2020 14:10:05 | Автор: admin

Привет!Меня зовутДенис Александров,и помимо работывкачествеAndroid-разработчика,я уже более семи лет готовлю стажеров вразных компанияхизанимаюсь студенческими практиками.Мне приходилосьиработать с перспективными студентамиперсонально, и проводить групповые стажировки с отсевом.Ясобралсамыеважные и полезные тонкостипроведениястажировок для студентов,благодаря которыммы делаем наши стажировки максимально полезными и эффективными для всех ее участников.

Когда(и кого)начинать стажировать

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

  1. Не брать студентов, а дождаться их выпускас надеждой, что выпускники будут лучше подготовлены к рабочей деятельности.

  2. Работать со студентами, но дать имвозможностьуспешно закончить учебу.

  3. Брать студентов в штат без учета их образовательной деятельности.

Мы стараемся придерживаться второго подхода, и на это есть несколько причин.

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

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

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

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

Яболее11 летработаю впреподавании, и сейчас ребята, которых я готовил еще к ЕГЭ, ужеработаютв крупныхкомпаниях.Я периодически пересекаюсь со своими бывшими студентами нахакатонахи олимпиадах, и ставлю себе мысленные заметкивотстудент, который прошел ожидаемый путь развития;былобы неплохопосмотреть, как он можетприменитьопытна проекте, где я сейчас работаю.Конечно, это очень долгосрочная и туманная по результатам работа, нов плане результатов игра стоит свеч.

Нашу работу со студентами можно разделить на 3 основных этапа:

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

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

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

Сотрудничество свузами

Вотчто мы делаем для того, чтобы найти и привлечьлучшихстудентов:

  • Участие в университетскиххакатонах, олимпиадах и дипломных работах в качестве экспертов.

  • Преподавание ввузе.

  • Открытые занятия и семинары на территории вуза.

  • Студенческая практика.

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

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

В вузестудентастоит научитьбазовым вещамязыку C++,принципамассемблирования и компиляции, способам организации памяти нажелезномуровне и др. Да, этогоне будет в списке требований кAndroid-кандидату. Как,наверно,не будет и в 98% другихвакансий.Вуз даеттутеоретическую базу,котораяпозволяет, например, видетьпротекающие дыры в абстракциях (по выражению ДжоэлаСпольски, сформулировавшего Закон дырявых абстракций).

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

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

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

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

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

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

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

Студенческая практика

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

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

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

Наш воркшоп QARATE.Внем принялиучастие в том числе и мои стажеры.

Обязательное время в офисе и минимум часов мы не обозначаем.Посещатьзанятия тоже не обязательноматериалы можно изучить самостоятельно (хотя это и очень сложно).

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

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

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

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

Стажировка

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

Мы сформулировали несколько причин, почему стоит максимально отложить привлечение начинающих специалистов к боевым проектам:

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

  2. Поэтапное внедрение в процессы. Тяжело с ходу разобраться во всех особенностях работы в IT. Стажировкаодин из способов поэтапного внедрения в компанию.

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

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

Стажировка и стресс

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

Что мы делаем для поддержания студента:

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

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

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

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

Стажировка и оплата

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

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

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

Что считается успешным завершением стажировки

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

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

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

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

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

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

Что дальше?

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

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

Вместо выводов

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

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

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

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

Полезные ссылки:

Зачем студенту стажировка: 7 аргументов за на hh.ru

Рабочие будни программиста: ожидания и реальность в блоге Mail.ru Group

Топ-10 качество программиста. Мнение лучших работодателей на geekbrains.ru

Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru