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

Slack

Перевод Не выходя из прачечной. Как СЕО Google, Amazon, Nasdaq и Slack работают из дома

22.06.2020 16:10:43 | Автор: admin


Пока коронавирус шагает по планете, даже руководители крупнейших в мире IT-компаний которые привыкли летать из города в город, со встречи на конференцию, на частных самолетах вынуждены сидеть в самоизоляции. В США правила отличаются от штата к штату, но в основном СЕО относятся к ситуации довольно серьезно, и не собираются подвергать свою жизнь неоправданному риску. Во многих компаниях Google, Microsoft, Adobe, Facebook они даже разрешили сотрудникам (если это возможно) не приходить в офис до конца 2020-го.

А как сами СЕО справляются с непривычным ритмом новой жизни?

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

New York Times поговорила с лидерами of нескольких самых крупных и характерных компаний об их опыте за это время. А мы добавили несколько других недавних интервью от Цукерберга и Безоса.


Никто к этому не готовился: Чак Роббинс, Cisco



Чак Роббинс, руководитель Cisco, испытывает проблемы с соединением

Гендиректор Cisco Systems, каждое утро четверга проводит большую видеоконференцию со всей компанией. Он руководит ей из своего дома в Кремниевой долине. На этот раз соединение было стабильным, но качество что-то хромало. Фото тоже, как видите, вышло довольно посредственным.
Я вам говорю. Вся эта удаленная работа хоть мы и продаем её покупателям, я не уверен, что хочу таким заниматься 100% своего времени.

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

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


Это чудо, что так можно управлять компанией: Сундар Пичаи, Alphabet




В определенном (специально неназванном) месте Кремниевой долины у Сундара Пичаи, руководителя Alphabet, материнской компании Google, есть уютный домашний офис. Высокие потолки, двухтонные книжные полки, растения, чистый декор. Всё как нужно для одного из самых богатых топ-менеджеров в IT-мире. Сундар Пичаи не основывал никаких компаний, и вообще родился в Индии. Но его состояние превышает $600 млн всё за счет его таланта управленца и менеджера.

Сейчас Пичаи руководит всей продукцией Google и вопросами холдинга Alphabet. В последний входит, например, YouTube, которому из-за возросшего трафика и оттока рекламодателей во время карантина пришлось очень несладко. Но больше всего, по словам Пичаи, его волнует контроль дезинформации. Например, он сделал так, чтобы релевантные новости о распространении Covid-19 были всегда доступны в топе выдачи Google. И чтобы первые ссылки из поисковика не вели на сайты с конспирологией.

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

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

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


Сейчас атмосфера огромного стресса: Адена Фридман, Nasdaq




Гендиректор Nasdaq, член совета директоров Федерального резервного банка и первая женщина, возглавившая глобальную биржу, Адена Фридман известна тем, что превратила Nasdaq в настоящую технологическую компанию. Она руководит ей как IT-фирмой, делая упор на обработке данных. Адена является одним из пропонентов идей модернизации капитализма, и ведёт лекции на эту тему. Тем не менее от последних (вынужденных) инноваций она не в восторге, и заниматься бизнесом по-старому ей нравилось больше.

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

Такое решение в начале марта выглядело чересчур резким и даже опрометчивым. Но впоследствии оказалось очень здравым учитывая, что Нью-Йорк, в центре которого располагается Nasdaq, впоследствии стал центром распространения коронавируса в США.

Когда наступает её черед проводить неделю вне офиса, Фридман работает из своей резиденции в Мэриленде, около Вашингтона. Вместе с мужем и двумя сыновьями, которые тоже трудятся из дома. При этом она следует той же рабочей рутине, что и в обычные дни. Встает в 5:30 утра, едет на тренажере, съедает завтрак и начинает работать около восьми. Она следит за состоянием рынков. Пытается сделать так, чтобы банки, у которых тоже большинство сотрудников работают удаленно, без ошибок осуществляли многомиллиардные сделки. И проводит встречи с руководителями крупных компаний которые тоже сидят у себя дома.

В интервью New York Times по телефону она рассказывает:
Что во всём этом действительно весело, так это иметь возможность чуть поближе узнать личную жизнь коллег. Иногда в комнату на совещании входит ребенок. Моя собака лает круглый день.

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


Не время думать о прибыли, лучше присядьте: Джефф Безос, Amazon




Самый богатый человек на планете уже давно напрямую не управляет своей компанией. Он занимается благотворительностью, общается с лидерами государств и пускает в космос свои ракеты Blue Origin (на это хобби он ежегодно тратит $1-$2 млрд).

Вместо Безоса его фирмой руководят другие два Джеффа Уилке и Блэкбёрн. Один управляет интернет-магазином, второй видеосервисами (Prime Video) и рекламой. Есть ещё Энди Джасси, занимающийся облаком AWS, и Дейв Кларк, руководящий супермаркетами (Amazon Go и Whole Foods).

Когда новость о коронавирусе распространялась по свету, Безос отдыхал во Франции, ходя со своей подругой на светские рауты и приёмы президента Макрона в Елисейском дворце. Но потом его сотрудники стали заболевать. Вирус начал свой путь по США. На Amazon посыпалась волна критики, требуемых мер защиты не хватало. И в такие критические моменты судьбоносные решения, от которых может зависеть вся судьба компании, может принимать только её глава.

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

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

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

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


Все прочь из прачечной: Стюарт Баттерфилд, Slack




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

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

Slack очень сильно вырос за время коронавируса. Количество отправляемых сообщений увеличилось в разы. За первую неделю марта сервис вырос на 25%, а за последующие два месяца Slack добавил 80% новых платящих клиентов. Акции фирмы выросли до $20 млрд, побив новый рекорд.

Стюарт Баттерфилд говорит, такой приток клиентов зарядил его команду новой энергией. Это самое продуктивное время в истории компании

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


Меня волнует потеря атмосферы долины: Марк Цукерберг, Facebook




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

Марк Цукерберг предсказывает, что больше половины сотрудников Facebook через 10 лет будут работать из дома. При том, что ещё полгода назад компания доплачивала новым сотрудникам $15 000, чтобы они могли жить поближе к её штаб-квартире.

У него взяли большое интервью о таком неожиданном переходе, где он делится своими откровениями, которые он сам получил за последние месяцы. И говорит, например, что тренд на удалёнку даже без COVID был бы неизбежен, а вирус просто ускорил такие процессы. Больше всего Цукерберг переживает, не потеряет ли Кремниевая долина своё конкурентное преимущество:
Я думаю самый важный вопрос, в долгосрочной перспективе, это социальные связи, культура, креативность. То, что мы смогли создать здесь в Кремниевой долине. Не исчезнет ли всё это при работе из дома?

В какой степени мы сейчас просто черпаем идеи из той культуры, которую мы создали за последние 10 лет? Будет ли нам дальше сложнее подходить к работе творчески, продолжать улучшаться?


Начинаем ежедневные медитации: Марк Бениофф, Salesforce




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

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

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

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

Security Week 36 критическая уязвимость в Slack

31.08.2020 20:10:55 | Автор: admin
На прошлой неделе появились детали нескольких критических уязвимостей в мессенджере Slack. Исследователь Оскарс Вегерис нашел метод практически полного взлома корпоративного аккаунта с утечкой данных, выполнением произвольного кода на компьютере жертвы, возможностью рассылки зараженного сообщения другим пользователям и хостинга вредоносного скрипта на серверах Slack.



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

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



В общем, все закончилось хорошо. Однако примечательна сумма вознаграждения: 1750 долларов за серьезную прореху в безопасности. Мало того, легко эксплуатируемую достаточно иметь доступ к атакуемому чату. Так как исследователь не стал публиковать статью самостоятельно, а попросил сделать публичным тикет в HackerOne, можно посмотреть на полную переписку независимого специалиста с вендором. Претензия к столь низкой сумме вознаграждения поступила не от самого исследователя, а от возмущенной общественности. Да, действительно, продать такую прореху легитимному брокеру уязвимостей можно было бы дороже. На черном рынке еще выгоднее. С другой стороны, Slack, в отличие от более крупных компаний, много денег и не обещает: у них прямо на странице bug bounty указан потолок в 1500 долларов. Так что дело не только в деньгах: даже теперь, когда у большинства вендоров работают программы вознаграждения за поиск багов, выбор на чьей стороне быть все равно остается.

Что еще произошло:


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

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

Уязвимость в браузере Safari позволяет красть файлы пользователя. В этом случае тоже есть спорный момент в программе bug bounty: специалисты Apple признали наличие бага, но пообещали закрыть его весной 2021-го, почти через год после уведомления. Другую критическую уязвимость закрыли уже в браузере Chrome.

Компания Facebook предупреждает рекламных партнеров о том, что нововведения в iOS 14 серьезно затруднят профилирование пользователей для показа релевантной рекламы. Речь идет о запрете использования единого рекламного идентификатора устройства, если того пожелает владелец устройства.
Подробнее..

Перевод Падение Slack 4 января 2021

10.05.2021 14:18:51 | Автор: admin


4 января 2021 года для многих людей во всем мире, также как и для большинства работников Slack был первым рабочим днем после нового года (за исключением специалистов горячей линии и службы поддержки, которые никогда не спят). В день Азии и утро в Европе прошло спокойно, но когда забрезжил рассвет в Америке мы стали получать сообщения от внешней службы мониторинга о росте количества ошибок. Мы начали разбираться, в чем дело. Ситуация с ошибками ухудшалась и мы инициировали процесс расследования инцидентов (о том, как у нас устроено управление инцидентами подробнее можно почитать в статье Райана Каткова (Ryan Katkov) All Hands on Deck https://slack.engineering/all-hands-on-deck/).

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

Чтобы сократить список возможных причин мы по-быстрому откатили некоторые изменения, которые были сделаны сегодня (забегая вперед дело было не в них). Также мы подключили еще нескольких человек из инфраструктурных групп, потому что процесс поиска сбоя шел медленно из-за того, что не работали панели мониторинга и оповещения. У нас сохранялся доступ к различным внутренним консолям и страницам статуса, к некоторым консольным утилитам, а также к системе сбора логов. Система сбора метрик тоже функционировала и мы могли запускать запросы к ней напрямую, но это было и совсем не так результативно, как как использование наших панелей мониторинга с преднастроенными запросами. Хотя наша инфраструктура в целом функционировала, мы видели признаки деградации сети, о чем мы сообщили AWS, нашему основному облачному провайдеру. На тот момент Slack работал в 6:57 по тихоокеанскому стандартному времени 99% сообщений успешно доставлялись (хотя это не было нормой, поскольку наше обычное значение этого параметра 99.999%).

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

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

Наше звено веб-сервисов масштабируется на основании двух видов сигналов. Один из них, это загрузка процессоров (метрика, которая используется для масштабирования практически везде) а другой, это загрузка доступных рабочих потоков Apache. Проблемы с сетью до 7:00 означали, что потоки находились больше времени в режиме ожидания, что приводило к уменьшению загрузки процессоров, а это инициировало автоматическое уменьшения количества инстансов. Поскольку состояние сети продолжало ухудшаться, из-за чего звено веб-служб больше времени находилось в ожидании ответа от бэкэнда, что приводило к увеличению загрузки рабочих потоков, и система автоматически увеличила количество инстансов веб-служб. Между 7:01 и 7:15 мы попытались добавить 1200 серверов в наше звено веб-сервисов.


К сожалению, наше масштабирование не отработало как полагается. У нас работает сервис, удачно названный службой обеспечения (в оригинале provision-service прим. пер.) и его название полностью отражает его функционал, в который входит настройка и тестирование новых инстансов, а также выполнение роли управляющего для инфраструктуры. Службе обеспечения нужно взаимодействовать с внутренними системами Slack и c API AWS, а поскольку это взаимодействие происходило по той же нестабильной сети и поскольку, как и большинство систем Slack на тот момент, он тратил больше времени на соединение и получение ответа, и использовал больше ресурсов, чем обычно. Пиковая нагрузка, связанная с необходимостью ввести одновременно большое число инстансов в условиях нестабильной сети привело к тому, что служба обеспечения уперлась в системные ограничения (наиболее значимым из которых было ограничение количества открытых файлов в Linux, но также были превышены и квоты AWS).

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

В один прекрасный момент служба обеспечения заработала (было около 8:15) и начала запускать работающие инстансы. Ситуация стала понемногу улучшаться. У нас по-прежнему были некоторые проблемы с продом, часть из которых удалось смягчить, а другая была в процессе решения. Также мы до сих пор испытывали проблемы связанные с повышенным уровнем потери пакетов в сети. Несмотря на это в 9:15 у нашего звена веб-сервисов было достаточно работающих узлов чтобы переваривать входящий трафик. Из-за проблем с сетью балансировщики нагрузки показывали большое число проблемных узлов, но к счастью у них был режим panic mode https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/panic_threshold в котором балансеры начинают распределять нагрузку на все узлы независимо от результатов проверки их состояния. Это, плюс повторные соединения и паттерн circuit breaking, помогли нам возобновить работу сервиса. Да, Slack был медленнее, чем обычно, и частота ошибок была выше, но в 9:15 он уже работал, а не лежал, как до этого. Целый час ушел на то, чтобы снизить частоту ошибок до приемлемого уровня по двум причинам. Во-первых, из-за нестабильности сети нам требовалось больше инстансов, чем обычно, чтобы нормально обслуживать входящий трафик. Во-вторых, больше времени ушло на процесс развертывания опять же из-за проблем с сетью.


К тому времени, как Slack восстановился, инженеры AWS нашли причину сбоя: часть нашей сетевой инфраструктуры AWS действительно была перегружена, что и приводило к потерям пакетов.

Чтобы было проще понять, что же произошло, стоит рассказать немного подробней о некоторых архитектурных особенностях Slack. На старте, не так уж много лет назад, все, что касается работы Slack работало в одном аккаунте AWS. По мере роста размера, сложности и количества специалистов, задействованных в обслуживании системы, мы отказались от этого решения и разнесли сервисы по различным аккаунтам и VPC (Virtual Private Clouds). Это решение позволило нам добиться большей изолированности между различными сервисами, и позволяло более точно контролировать привилегии операторов. Для того, чтобы связать наши VPC, в качестве хаба мы использовали AWS Transit Gateways (TGWs).

4 января наши TGWs оказались перегружены. TGWs обслуживаются AWS и предполагается, что они должны масштабироваться незаметно для нас. Но в первые дни после нового года трафик Slack имеет необычную структуру, он ниже в выходные дни, поскольку все отвлекаются от работы (молодцы, что поддерживаете баланс работы и лично жизни, пользователи Slack, так держать!). В первый рабочий день кэши клиентов не прогреты и при первом подключении они подгружают больше данных, чем обычно, после нескольких дней затишья нагрузка растет до самых больших значений за год буквально в течении одной ночи.

Наши системы позволяют быстро отмасштабировать производительность систем, чтобы переварить нагрузку такого рода (и в прошлые годы мы всегда хорошо с ней справлялись). Но наши TGWs не смогли отмасштабироваться достаточно быстро. В ходе данного инцидента специалисты AWS были оповещены о нашей проблеме с потерей пакетов их внутренним мониторингом и вручную увеличили емкость TGWs. К 10:40 это изменение вступило в силу во всех зонах доступности (Availability Zones) и наша сеть вернулась к нормальному режиму работы, а с ним вернулись обычные уровни ошибок и задержки.


AWS заверили нас, в процессе разбора данного инцидента ими были пересмотрены алгоритмы масштабирования TGW для резких скачков объема трафика. А мы поставили себе напомнание (конечно же это было напоминание Slack slack.com/intl/en-ie/help/articles/208423427-Set-a-reminder) превентивно увеличить емкость TGWs в конце следующих новогодних каникул.

Выводы


Мониторинг является одним из наших наиболее критичных сервисов благодаря ему мы узнаем в порядке ли службы, с которыми взаимодействуют пользователи, а помимо этого это один из наших наиболее важных инструментов для выявления проблем. Мы прилагаем все усилия, чтобы наши инструменты мониторинга были как можно более независимыми от инфраструктуры Slack, чтобы они были доступны тогда, когда мы нуждаемся в них больше всего. В данной ситуации наши панели мониторинга и оповещения упали, поскольку базы данных, из которых они брали информацию оказались расположены в другом VPC, что привело к зависимости от TGWs. Запуск их в одном VPS позволит избавиться от этой зависимости.

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

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



Облачные серверы от Маклауд быстрые и безопасные.

Зарегистрируйтесь по ссылке выше или кликнув на баннер и получите 10% скидку на первый месяц аренды сервера любой конфигурации!

Подробнее..

Из песочницы Urban Bot или как писать чат-ботов для Telegram, Slack, Facebook на React.js

27.07.2020 22:10:16 | Автор: admin

image


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


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

В отличии от большинства чат-бот библиотек, которые чаще всего просто оборачивают http запросы в функции с готовыми аргументами и предоставляют подписки вида bot.on('message', callback), иногда позволяя передавать контекст между вызовами, Urban Bot предлагает совершенно иной подход к разработке чат-ботов через декларативное программирование и компонентный подход. Живой пример, написанный на Urban Bot, вы можете попробовать в Telegram, cсылка на чат-бот, и посмотреть код на GitHub.


Как мы заметили выше, чат-боты это полноценные UI приложения. А какой язык в 2020 и какая библиотека наиболее подходит для разработки UI приложений? Правильно, JavaScript и React. Такая интеграция позволяет легко и непринужденно строить чат-боты любой сложности без единого знания об API мессенджеров. Далее я расскажу, как создавать простые компоненты и на их основе строить сложных чат-ботов, работать с навигацией, создавать диалоги любой вложенности, писать одно приложение и запускать в любых мессенджерах, и многое другое.


Отправка сообщений


Так выглядит самый простой пример на Urban Bot. Для отправки текстового сообщения нам нужно создать функцию и вернуть из него готовый компонент Text с текстом внутри, который мы хотим отправить. Когда компонент отрендериться, все пользователи чат-бота получат сообщение "Hello, world!".


import React from 'react';import { Text } from '@urban-bot/core';function App() {    return (        <Text>           Hello, world!        </Text>    );}

Изображение можно отправить так:


import React from 'react';import { Image } from '@urban-bot/core';function App() {    const imageByURL =  'https://some-link.com/image.jpg';    return <Image file={imageByURL} />;}

Urban Bot имеет готовый набор компонентов, для каждого вида сообщений, для файлов File, для кнопок ButtonGroup и много других, подробнее можно взглянуть здесь. В каждый из них можно передать определенный набор пропсов, например, имитировать будто бот печатает сообщение 1 секунду <Text simulateTyping={1000}>.


Получение сообщений


Мы рассмотрели как посылать сообщения, давайте разберемся как подписываться на сообщения от пользователей. За подписки в Urban Bot отвечают React Hooks.


Чтобы подписаться на текстовые сообщения, мы можем использовать хук useText.


import React from 'react';import { Text, useText } from '@urban-bot/core';function App() {    useText(({ text }) => {        console.log(`Пользователь отправил сообщение ${text}`);    });    return (        <Text>            Hello, world!        </Text>    );}

Urban Bot предоставляет готовы набор хуков для разных типов сообщений. Например, useImage, если пользователь отправил изображение, useFile и т.д. Полный список здесь. В каждый хук также приходит мета информация, кто отправил сообщение и т.д.


Эхо бот


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


В этом компоненте мы впервые добавим работу с переменными через React хук useState. Этот хук возвращает переменную и функцию, чтобы ее изменять. React.useState нужен, чтобы изменение переменной приводило к ререндеру и, соответсвенно, отправке нового сообщения. Мы определим начальное значение переменной text как "Привет" и передадим в компонент Text. Также мы подпишемся на сообщения от пользователей с помощью хука useText, и будем изменять text через функцию setText. После вызова setText React перерендерит компонент Echo с новым значением, и пользователь получит новое сообщение с тем что он сам отправил боту.


import React from 'react';import { Text, useText } from '@urban-bot/core';function Echo() {    const [text, setText] = React.useState('Привет!');    useText(({ text }) => {        setText(text);    });    return (        <Text>            {text}        </Text>    );}

Кнопки


Давайте также напишем пример с кнопками, сделаем простейший счетчик. Для этого нам понадобятся компоненты ButtonGroup и Button. Каждой кнопке мы определим свой обработчик, который будет менять count на +1 или -1 и будем передавать результат в ButtonGroup в проп title. Мы установим проп isNewMessageEveryRender как false, чтобы при последующих ререндерах отправлялось не новое сообщение с новыми кнопками, а просто изменялось начальное сообщение.


import React from 'react';import { ButtonGroup, Button } from '@urban-bot/core';function Counter() {    const [count, setCount] = React.useState(0);    const increment = () => setCount(count + 1);    const decrement = () => setCount(count - 1);    return (        <ButtonGroup title={count} isNewMessageEveryRender={false}>            <Button onClick={increment}>+1</Button>            <Button onClick={decrement}>-1</Button>        </ButtonGroup>    );}


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


Теперь, когда пользователь напишет "/echo" отрендериться компонент Echo, когда "/counter" управление перейдет в Counter. Роуты также могут принимать path как regexp.


import React from 'react';import { Router, Route } from '@urban-bot/core';import { Echo } from './Echo';import { Counter } from './Counter';function App() {    return (        <Router>            <Route path="/echo">                <Echo />            </Route>            <Route path="/counter">                <Counter />            </Route>        </Router>    );}

Визуальный пример, чтобы увидеть как будет работать код.


image


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


Форматирование текста


Urban Bot позволяет стилизовать сообщения через привычные HTML теги. Писать жирным <b>, курсивом <i>, зачеркнутым <s>, переносить строки <br /> и так далее, полный список здесь.


Пример
const someCode = `function sum2() {    return 2 + 2;}if (sum2() !== 4) {    console.log('WTF');}`;<Text>    Usual text    <br />    <b>Bold text</b>    <br />    <i>Italic text</i>    <br />    <u>Underscore text</u>    <br />    <s>Strikethrough text</s>    <br />    <q>quote</q>    <br />    <b>        Bold and <s>Strikethrough text</s>    </b>    <br />    <code >Code 2 + 2</code >    <br />    <pre>{someCode}</pre>    <br />    <a href="http://personeltest.ru/aways/github.com/urban-bot/urban-bot">External link</a></Text>


Диалоги


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


Пример плоского диалога. Бот задаст первый вопрос, после ответа задаст второй, потом третий, потом запустит callback onFinish с готовыми ответами. На каждом шаге можно обрабатывать ответ пользователя через callback onNext.


import React from 'react';import { Dialog, DialogStep, Text } from '@urban-bot/core';function FlatDialogExample() {    return (        <Dialog onFinish={(answers) => console.log(answers)}>            <DialogStep                content={<Text>Привет, как тебя зовут?</Text>}                 id="name"                onNext={(name) => console.log(name)}            >                <DialogStep                    content={<Text>Cколько тебе лет?</Text>}                    id="age"                >                    <DialogStep                         content={<Text>Из какого ты города?</Text>}                        id="city"                    />                </DialogStep>            </DialogStep>        </Dialog>    );}

Можно получать на следующем шаге прошлый ответ через паттерн render-props .


function FlatDialogExample() {    return (        <Dialog>            <DialogStep content={<Text>Привет, как тебя зовут?</Text>}>                {(name) => (                    <DialogStep                         content={<Text>{`${name}, cколько тебе лет?`}</Text>}                    />                )}            </DialogStep>        </Dialog>    );}

Можно добавить валидацию на каждый шаг.


function FlatDialogExample() {    return (        <Dialog onFinish={(answers) => console.log(answers)}>            <DialogStep                content={<Text>Привет, как тебя зовут?</Text>}                id="name"                validation={{                     isValid: (answer) => answer !== 'Самуэль',                     errorText: 'Самуэль заблокирован.'                 }}            >                // ...            </DialogStep>        </Dialog>    );}

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


import React from 'react';import { Dialog, DialogStep, Text, ButtonGroup, Button } from '@urban-bot/core';function TreeDialogExample() {    return (        <Dialog>            <DialogStep                content={                    <ButtonGroup title="Привет, что вы хотите купить?">                        <Button id="hat">Футболка</Button>                        <Button id="glasses">Очки</Button>                    </ButtonGroup>                }            >                <DialogStep                    match="hat"                    content={                        <ButtonGroup title="Футболка какого размера?">                            <Button id="m">S</Button>                            <Button id="s">M</Button>                            <Button id="l">L</Button>                        </ButtonGroup>                    }                />                <DialogStep                    match="glasses"                    content={                        <ButtonGroup title="Очки какого цвета?">                            <Button id="black">Черный</Button>                            <Button id="white">Белый</Button>                        </ButtonGroup>                    }                />            </DialogStep>        </Dialog>    );}

Состояние


Что вы можете использовать для управления состоянием? Все то же что и в любом React приложении. Можете использовать React useState и передавать состояние ниже по дереву компонентов через пропсы или React Context. Можно использовать библиотеки для управления состоянием: Redux (пример), MobX (пример), Apollo и любые другие, которые обычно используют вместе с React, вы можете даже переиспользовать готовые части из готовых React Web или React Native приложений, так как Urban Bot использует тот же чистый React, который работает в миллионах приложений.


Сессия


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


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


function Counter() {    const [count, setCount] = React.useState(0);    const increment = () => setCount(count + 1);    const decrement = () => setCount(count - 1);    return (        <ButtonGroup title={count} isNewMessageEveryRender={false}>            <Button onClick={increment}>+1</Button>            <Button onClick={decrement}>-1</Button>        </ButtonGroup>    );}

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


function Counter() {    const [count, setCount] = useGlobalCount();   // ...}

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


import React from 'react';import { Text, useText, useBotContext } from '@urban-bot/core';function UserId() {    const { chat } = useBotContext();    useText(({ from }) => console.log(`Пришло сообщение от ${from.username}`));   return <Text>Чат id {chat.id}</Text>;}

Типизация


Urban Bot написан на TypeScript, соответсвенно проект полностью типизирован, и если вы пишете на TypeScript, вам будет очень удобно.


Запуск в мессенджерах


Большой плюс Urban Bot, что он не привязан ни к одному мессенджеру. Есть основной пакет @urban-bot/core, который позволяет создавать абстрактных чат-ботов, а уже их подключать к определенным мессенджерам. В данный момент есть поддержка Telegram, Slack, Facebook. В дальнейшем, мы планируем добавлять любые мессенджеры, где есть чат-боты и открытое API. Если вам интересно, и вы хотите писать Urban Bot приложения для других мессенджеров, скажем Viber, Discord или у вас есть свой мессенджер то пишите к нам в группу https://t.me/urbanbotjs, одной просьбы будет достаточно, чтобы появилось большая мотивация реализовать ваш функционал.


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


Скажем, у нас есть готовое приложение App и мы хотим его запустить его в Telegram. Для этого нам понадобится класс UrbanBotTelegram из пакет @urban-bot/telegram. Функция render из @urban-bot/core подобная ReactDOM.render и компонент Root. Мы создаем экземпляр UrbanBotTelegram и передаем туда бот токен из Telegram, также можно передать isPolling, чтобы не настраивать вебхук, и бот работал локально. Готовый экземпляр мы передаем в компонент Root, и оборачиваем наше готовое приложение и, соответсвенно, передаем все в функцию render, которая запустит все процессы.


Более подробная инструкция с видео как разрабатывать локально и деплоить Telegram бота можно прочитать в инструкции.


import React from 'react';import { render, Root } from '@urban-bot/core';import { UrbanBotTelegram } from '@urban-bot/telegram';import { App } from './App';const urbanBotTelegram = new UrbanBotTelegram({    token: 'telegramToken',    isPolling: true,});render(    <Root bot={urbanBotTelegram}>        <App />    </Root>);

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


// ...import { UrbanBotSlack } from '@urban-bot/slack';// ...render(    <Root bot={urbanBotTelegram}>        <App />    </Root>);const urbanBotSlack = new UrbanBotSlack({    signingSecret: 'slackSigningSecret',    token: 'slackToken',});render(    <Root bot={urbanBotSlack}>        <App />    </Root>);

Прямой вызов API


С помощью Urban Bot вы можете создавать чат-ботов просто описывая их через компоненты. А что если вам будет нужно вручную вызвать API? Каждый экземпляр UrbanBot* содержит в себе API клиент для активного мессенджера. Рассмотрим пример для Telegram.


Мы можем получить bot с помощью хука useBotContext. bot содержит client и type c типом мессенджера. client будет представлять собой экземпляр библиотеки node-telegram-bot-api . В любом месте приложения можно получить client и вызвать любой метод на ваше усмотрение, скажем блокировать пользователя, если он написал нецензурное сообщение.


import React from 'react';import { useText, useBotContext } from '@urban-bot/core';function SomeComponent() {    const { bot } = useBotContext();    useText(({ text, chat, from }) => {        if (text.includes('бл***')) {            bot.client.kickChatMember(chat.id, from.id);        }    });    // ...}

В каждом мессенджере уникальный API. Если вы разрабатываете несколько мессенджеров одновременно, можно отделять функционал сравнивая bot.type.


import { useBotContext } from '@urban-bot/core';import { UrbanBotTelegram } from '@urban-bot/telegram';import { UrbanBotSlack } from '@urban-bot/slack';function SomeComponent() {    const { bot } = useBotContext();    if (bot.type === UrbanBotTelegram.type) {        // telegram api        bot.client.kickChatMember(/* ... */);    }    if (bot.type === UrbanBotSlack.type) {        // slack api        bot.client.conversations.kick(/* ... */);    }    // ...}

Как попробовать?


У Urban Bot есть стартер, который позволит вам начать разрабатывать чат-ботов за минуту, сделан по аналогии с create-rect-app. Все что вам нужно, чтобы попробовать Urban Bot это выполнить команду в терминале для


TypeScript


npx create-urban-bot my-app

JavaScript


npx create-urban-bot my-app --template js

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


Итого


Несмотря на то что Urban Bot запустился только недавно, на мой взгляд это библиотека с огромным потенциалом. Только представьте, если у вас есть базовые знания React, вы можете написать чат-бот любой сложности на все возможные платформы, создавать и использовать библиотеки с готовым набором компонентов на манер ui-kit, переиспользовать код между вашими другими UI приложеними на React, будь то web или mobile.


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


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


Сайт
Github
Группа в Telegram
Наглядный чат-бот в Telegram, с открытым кодом.
Как создать Todo List чат-бот в Telegram с помощью React.js

Подробнее..

Пишем Slack бота для Scrum покера на Go. Часть 1

04.03.2021 00:14:15 | Автор: admin

Здравствуйте! Сегодня мы напишем Slack бота для Scrum покера на языке Go. Писать будем по возможности без фреймворков и внешних библиотек, так как наша цель разобраться с языком программирования Go и проверить, насколько этот язык удобен для разработки подобных проектов.

Дисклеймер

Я только познаю Go и многих вещей еще не знаю. Мой основной язык разработки Python. Поэтому часто буду отсылать к нему в тех местах, где по моему мнению в Python что-то сделано удобнее или проще. Цель этих отсылок в том, чтобы породить дискуссию, ведь вполне вероятно, что эти "удобные вещи" также присутствуют в Go, просто я их не нашел.

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

Хватит прелюдий, вперед в бой!

Итоговый результат

Анимация работы будущего бота

Для тех, кому читать код интересней, чем статью прошу сюда.

Структура приложения

Разобьем нашу программу на следующие слои. У нас предполагается слой взаимодействия (web), слой для рисования интерфейса средствами Slack UI Block Kit (ui), слой для сохранения / получения результатов (storage), а также место для хранения настроек (config). Давайте создадим следующие папки в проекте:

config/storage/ui/web/-- clients/-- server/main.go

Сервер

Для сервера будем использовать стандартный сервер из пакета http. Создадим структуру Server следующего вида в web -> server:

server.go
package serverimport ("context""log""net/http""os""os/signal""sync/atomic""time")type Server struct {  // Здесь мы будем определять все необходимые нам зависимости и передавать их на старте приложения в main.gohealthy        int32logger         *log.Logger}func NewServer(logger *log.Logger) *Server {return &Server{logger: logger,}}

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

server.go
func (s *Server) setupRouter() http.Handler {  // TODOrouter := http.NewServeMux()  return router}func (s *Server) Serve(address string) {server := &http.Server{Addr:         address,    Handler:      s.setupRouter(),ErrorLog:     s.logger, // Наш логгерReadTimeout:  5 * time.Second,WriteTimeout: 10 * time.Second,IdleTimeout:  15 * time.Second,}  // Создаем каналы для корректного завершения процессаdone := make(chan bool)quit := make(chan os.Signal, 1)  // Настраиваем сигнал для корректного завершения процессаsignal.Notify(quit, os.Interrupt)go func() {<-quits.logger.Println("Server is shutting down...")    // Эта переменная пригодится для healthcheck'а напримерatomic.StoreInt32(&s.healthy, 0)    // Даем клиентам 30 секунд для завершения всех операций, прежде чем сервер будет остановленctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()    // Информируем сервер о том, что не нужно держать существующие коннектыserver.SetKeepAlivesEnabled(false)    // Выключаем серверif err := server.Shutdown(ctx); err != nil {s.logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)}close(done)}()s.logger.Println("Server is ready to handle requests at", address)  // Переменная для проверки того, что сервер запустился и все хорошоatomic.StoreInt32(&s.healthy, 1)  // Запускаем серверif err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {s.logger.Fatalf("Could not listen on %s: %v\n", address, err)}  // Когда сервер остановлен и все хорошо, снова получаем управление и логируем результат<-dones.logger.Println("Server stopped")}

Теперь давайте создадим первый хэндлер. Создадим папку в web -> server -> handlers:

healthcheck.go
package handlersimport ("net/http")func Healthcheck() http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {w.Write("OK")})}

Добавим наш хэндлер в роутер:

server.go
// Наш код вышеfunc (s *Server) setupRouter() http.Handler {router := http.NewServeMux()router.Handle("/healthcheck",handlers.Healthcheck(),)  return router}// Наш код ниже

Идем в main.go и пробуем запустить наш сервер:

package mainimport ("log"  "os"  "go-scrum-poker-bot/web/server")func main() {  // Создаем логгер со стандартными флагами и префиксом "INFO:".   // Писать он будет только в stdoutlogger := log.New(os.Stdout, "INFO: ", log.LstdFlags)app := server.NewServer(logger)app.Serve(":8000")}

Пробуем запустить проект:

go run main.go

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

NGROK

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

ngrok http 8000

Если все хорошо, то вы увидите что-то вроде этого:

ngrok by @inconshreveable                                                                                                            (Ctrl+C to quit)                                                                                                                                                     Session Status                online                                                                                                                 Account                       Sayakhov Ilya (Plan: Free)                                                                                             Version                       2.3.35                                                                                                                 Region                        United States (us)                                                                                                     Web Interface                 http://127.0.0.1:4040                                                                                                  Forwarding                    http://ffd3cfcc460c.ngrok.io -> http://localhost:8000                                                                  Forwarding                    https://ffd3cfcc460c.ngrok.io -> http://localhost:8000                                                                                                                                                                                                                      Connections                   ttl     opn     rt1     rt5     p50     p90                                                                                                          0       0       0.00    0.00    0.00    0.00     

Нас интересует строчка https://ffd3cfcc460c.ngrok.io. Она нам понадобится дальше.

Slash commands

Создадим наше приложение в Slack. Для этого нужно перейти сюда -> Create New App. Далее указываем имя GoScrumPokerBot и добавляем его в свой Workspace. Далее, нам нужно дать нашему боту права. Для этого идем в OAuth & Permissions -> Scopes и добавляем следующие права: chat:write, commands. Первый набор прав нужен, чтобы бот мог писать в каналы, а второй для slash команд. И наконец нажимаем на Reinstall to Workspace. Готово! Теперь идем в раздел Slash commands и добавляем нашу команду /poker .

В Request URL нужно вписать адрес из пункта выше + путь. Пусть будет так: https://ffd3cfcc460c.ngrok.io/play-poker.

Slash command handler

Теперь создадим хэндлер для обработки событий на только созданную команду. Идем в web -> server -> handlers и создаем файл play_poker.go:

func PlayPokerCommand() http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {    w.Header().Set("Content-Type", "application/json")    w.Write([]byte(`{"response_type": "ephemeral", "text": "Hello world!"}`))})}

Добавляем наш хэндлер в роутер:

server.go
func (s *Server) setupRouter() http.Handler {router := http.NewServeMux()router.Handle("/healthcheck",handlers.Healthcheck(),)router.Handle("/play-poker",handlers.PlayPokerCommand(),)  return router}

Идем в Slack и пробуем выполнить эту команду: /poker. В ответ вы должны получить что-то вроде этого:

Но это не единственный вариант взаимодействия со Slack. Мы также можем слать сообщения в канал. Этот вариант мне понравился больше и плюс у него больше возможностей в сравнении с ответом на команду. Например вы можете послать сообщение в фоне (если оно требует долгих вычислений). Давайте напишем наш http клиента. Идем в web -> clients. Создаем файл client.go:

client.go
package clients// Создадим новый тип для наших хэндлеровtype Handler func(request *Request) *Response// Создадим новый тип для middleware (о них чуть позже)type Middleware func(handler Handler, request *Request) Handler// Создадим интерфейс http клиентаtype Client interface {Make(request *Request) *Response}// Наша реализация клиентаtype BasicClient struct {client     *http.Clientmiddleware []Middleware}func NewBasicClient(client *http.Client, middleware []Middleware) Client {return &BasicClient{client: client, middleware: middleware}}// Приватный метод для всей грязной работыfunc (c *BasicClient) makeRequest(request *Request) *Response {payload, err := request.ToBytes() // TODOif err != nil {return &Response{Error: err}}  // Создаем новый request, передаем в него данныеreq, err := http.NewRequest(request.Method, request.URL, bytes.NewBuffer(payload))if err != nil {return &Response{Error: err}}  // Применяем заголовкиfor name, value := range request.Headers {req.Header.Add(name, value)}  // Выполняем запросresp, err := c.client.Do(req)if err != nil {return &Response{Error: err}}defer resp.Body.Close()  // Читаем тело ответаbody, err := ioutil.ReadAll(resp.Body)if err != nil {return &Response{Error: err}}err = nil  // Если вернулось что-то отличное выше или ниже 20x, то ошибкаif resp.StatusCode > http.StatusIMUsed || resp.StatusCode < http.StatusOK {err = fmt.Errorf("Bad response. Status: %d, Body: %s", resp.StatusCode, string(body))}return &Response{Status:  resp.StatusCode,Body:    body,Headers: resp.Header,Error:   err,}}// Наш публичный метод для запросовfunc (c *BasicClient) Make(request *Request) *Response {if request.Headers == nil {request.Headers = make(map[string]string)}    // Применяем middlewarehandler := c.makeRequestfor _, middleware := range c.middleware {handler = middleware(handler, request)}return handler(request)}

Теперь создадим файл web -> clients:

request.go
package clientsimport "encoding/json"type Request struct {URL     stringMethod  stringHeaders map[string]stringJson    interface{}}func (r *Request) ToBytes() ([]byte, error) {if r.Json != nil {result, err := json.Marshal(r.Json)if err != nil {return []byte{}, err}return result, nil}return []byte{}, nil}

Сразу напишем тесты к методу ToBytes(). Для тестов я взял testify/assert, так как без нее была бы куча if'ов, а меня они напрягают :) . К тому же, я привык к pytest и его assert, да и как-то глазу приятнее:

request_test.go
package clients_testimport ("encoding/json""go-scrum-poker-bot/web/clients""reflect""testing""github.com/stretchr/testify/assert")func TestRequestToBytes(t *testing.T) {  // Здесь мы делаем что-то вроде pytest.parametrize (жаль, что в Go нет сахара для декораторов, это было бы удобнее)testCases := []struct {json interface{}data []byteerr  error}{{map[string]string{"test_key": "test_value"}, []byte("{\"test_key\":\"test_value\"}"), nil},{nil, []byte{}, nil},{make(chan int), []byte{}, &json.UnsupportedTypeError{Type: reflect.TypeOf(make(chan int))}},}  // Проходимся по нашим тест кейсамfor _, testCase := range testCases {request := clients.Request{URL:     "https://example.com",Method:  "GET",Headers: nil,Json:    testCase.json,}actual, err := request.ToBytes()    // Проверяем результатыassert.Equal(t, testCase.err, err)assert.Equal(t, testCase.data, actual)}}

И нам нужен web -> clients:

response.go
package clientsimport "encoding/json"type Response struct {Status  intHeaders map[string][]stringBody    []byteError   error}// Я намеренно сделал универсальный метод, чтобы можно было привезти любой ответ к нужному и не писать каждый раз эти богомерзкие if err != nilfunc (r *Response) Json(to interface{}) error {if r.Error != nil {return r.Error}return json.Unmarshal(r.Body, to)}

И также, напишем тесты для метода Json(to interface{}):

response_test.go
package clients_testimport ("errors""go-scrum-poker-bot/web/clients""testing""github.com/stretchr/testify/assert")// Один тест на позитивный кейсfunc TestResponseJson(t *testing.T) {to := struct {TestKey string `json:"test_key"`}{}response := clients.Response{Status:  200,Headers: nil,Body:    []byte(`{"test_key": "test_value"}`),Error:   nil,}err := response.Json(&to)assert.Equal(t, nil, err)assert.Equal(t, "test_value", to.TestKey)}// Один тест на ошибкуfunc TestResponseJsonError(t *testing.T) {expectedErr := errors.New("Error!")response := clients.Response{Status:  200,Headers: nil,Body:    nil,Error:   expectedErr,}err := response.Json(map[string]string{})assert.Equal(t, expectedErr, err)}

Теперь, когда у нас есть все необходимое, нам нужно написать тесты для клиента. Есть несколько вариантов написания тестов для http клиента. Я выбрал вариант с подменой http транспорта. Однако есть и другие варианты, но этот мне показался удобнее:

client_test.go
package clients_testimport ("bytes""go-scrum-poker-bot/web/clients""io/ioutil""net/http""testing""github.com/stretchr/testify/assert")// Для удобства объявим новый типtype RoundTripFunc func(request *http.Request) *http.Responsefunc (f RoundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {return f(request), nil}// Создание mock тестового клиентаfunc NewTestClient(fn RoundTripFunc) *http.Client {return &http.Client{Transport: RoundTripFunc(fn),}}// Валидный тестfunc TestMakeRequest(t *testing.T) {url := "https://example.com/ok"  // Создаем mock клиента и пишем нужный нам ответhttpClient := NewTestClient(func(req *http.Request) *http.Response {assert.Equal(t, req.URL.String(), url)return &http.Response{StatusCode: http.StatusOK,Body:       ioutil.NopCloser(bytes.NewBufferString("OK")),Header:     make(http.Header),}})  // Создаем нашего http клиента с замоканным http клиентомwebClient := clients.NewBasicClient(httpClient, nil)response := webClient.Make(&clients.Request{URL:     url,Method:  "GET",Headers: map[string]string{"Content-Type": "application/json"},Json:    nil,})assert.Equal(t, http.StatusOK, response.Status)}// Тест на ошибочный responsefunc TestMakeRequestError(t *testing.T) {url := "https://example.com/error"httpClient := NewTestClient(func(req *http.Request) *http.Response {assert.Equal(t, req.URL.String(), url)return &http.Response{StatusCode: http.StatusBadGateway,Body:       ioutil.NopCloser(bytes.NewBufferString("Bad gateway")),Header:     make(http.Header),}})webClient := clients.NewBasicClient(httpClient, nil)response := webClient.Make(&clients.Request{URL:     url,Method:  "GET",Headers: map[string]string{"Content-Type": "application/json"},Json:    nil,})assert.Equal(t, http.StatusBadGateway, response.Status)}

Отлично! Теперь давайте напишем middleware. Я привык для каждой, даже самой маленькой задачи, писать отдельную маленькую middleware. Так можно легко переиспользовать такой код в разных проектах / для разных API с разными требованиями к заголовкам / авторизации и так далее. Slack требует при отправке сообщений в канал указывать Authorization заголовок с токеном, который вы сможете найти в разделе OAuth & Permissions. Создаем в web -> clients -> middleware:

auth.go
package middlewareimport ("fmt""go-scrum-poker-bot/web/clients")// Токен будем передавать при определении middleware на этапе инициализации клиентаfunc Auth(token string) clients.Middleware {return func(handler clients.Handler, request *clients.Request) clients.Handler {return func(request *clients.Request) *clients.Response {request.Headers["Authorization"] = fmt.Sprintf("Bearer %s", token)return handler(request)}}}

И напишем тест к ней:

auth_test.go
package middleware_testimport ("fmt""go-scrum-poker-bot/web/clients""go-scrum-poker-bot/web/clients/middleware""testing""github.com/stretchr/testify/assert")func TestAuthMiddleware(t *testing.T) {token := "test"request := &clients.Request{Headers: map[string]string{},}handler := middleware.Auth(token)(func(request *clients.Request) *clients.Response {return &clients.Response{}},request,)handler(request)assert.Equal(t, map[string]string{"Authorization": fmt.Sprintf("Bearer %s", token)}, request.Headers)}

Также в репозитории вы сможете найти middleware для логирования и установки Content-Type: application/json. Здесь я не буду приводить этот код в целях экономии времени и места :).

Давайте перепишем наш PlayPoker хэндлер:

play_poker.go
package handlersimport ("errors""go-scrum-poker-bot/ui""go-scrum-poker-bot/web/clients""go-scrum-poker-bot/web/server/models""net/http""github.com/google/uuid")func PlayPokerCommand(webClient clients.Client, uiBuilder *ui.Builder) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {    // Добавим проверку, что нам пришли данные из POST Form с текстом и ID каналаif r.PostFormValue("channel_id") == "" || r.PostFormValue("text") == "" {w.Write(models.ResponseError(errors.New("Please write correct subject"))) // TODOreturn}resp := webClient.Make(&clients.Request{URL:    "https://slack.com/api/chat.postMessage",Method: "POST",      Json: uiBuilder.Build( // TODO: Напишем builder позжеr.PostFormValue("channel_id"),uuid.New().String(),r.PostFormValue("text"),nil,false,),})if resp.Error != nil {w.Write(models.ResponseError(resp.Error)) // TODOreturn}})}

И создадим в web -> server -> models . Файл errors.go для быстрого формирования ошибок:

errors.go
package modelsimport ("encoding/json""fmt")type SlackError struct {ResponseType string `json:"response_type"`Text         string `json:"text"`}func ResponseError(err error) []byte {resp, err := json.Marshal(SlackError{ResponseType: "ephemeral",Text:         fmt.Sprintf("Sorry, there is some error happened. Error: %s", err.Error()),},)if err != nil {return []byte("Sorry. Some error happened")}return resp}

Напишем тесты для хэндлера:

play_poker_test.go
package handlers_testimport ("errors""go-scrum-poker-bot/config""go-scrum-poker-bot/ui""go-scrum-poker-bot/web/server/handlers""go-scrum-poker-bot/web/server/models""net/http""net/http/httptest""net/url""strings""testing""github.com/stretchr/testify/assert")func TestPlayPokerHandler(t *testing.T) {config := config.NewConfig() // TODOmockClient := &MockClient{}uiBuilder := ui.NewBuilder(config) // TODOresponseRec := httptest.NewRecorder()router := http.NewServeMux()router.Handle("/play-poker", handlers.PlayPokerCommand(mockClient, uiBuilder))payload := url.Values{"channel_id": {"test"}, "text": {"test"}}.Encode()request, err := http.NewRequest("POST", "/play-poker", strings.NewReader(payload))request.Header.Set("Content-Type", "application/x-www-form-urlencoded")router.ServeHTTP(responseRec, request)assert.Nil(t, err)assert.Equal(t, http.StatusOK, responseRec.Code)assert.Empty(t, responseRec.Body.String())assert.Equal(t, true, mockClient.Called)}func TestPlayPokerHandlerEmptyBodyError(t *testing.T) {config := config.NewConfig()mockClient := &MockClient{}uiBuilder := ui.NewBuilder(config)responseRec := httptest.NewRecorder()router := http.NewServeMux()router.Handle("/play-poker", handlers.PlayPokerCommand(mockClient, uiBuilder))payload := url.Values{}.Encode()request, _ := http.NewRequest("POST", "/play-poker", strings.NewReader(payload))request.Header.Set("Content-Type", "application/x-www-form-urlencoded")router.ServeHTTP(responseRec, request)expected := string(models.ResponseError(errors.New("Please write correct subject")))assert.Equal(t, http.StatusOK, responseRec.Code)assert.Equal(t, expected, responseRec.Body.String())assert.Equal(t, false, mockClient.Called)}func TestPlayPokerHandlerRequestError(t *testing.T) {errMsg := "Error msg"config := config.NewConfig() // TODOmockClient := &MockClient{Error: errMsg}uiBuilder := ui.NewBuilder(config) // TODOresponseRec := httptest.NewRecorder()router := http.NewServeMux()router.Handle("/play-poker", handlers.PlayPokerCommand(mockClient, uiBuilder))payload := url.Values{"channel_id": {"test"}, "text": {"test"}}.Encode()request, _ := http.NewRequest("POST", "/play-poker", strings.NewReader(payload))request.Header.Set("Content-Type", "application/x-www-form-urlencoded")router.ServeHTTP(responseRec, request)expected := string(models.ResponseError(errors.New(errMsg)))assert.Equal(t, http.StatusOK, responseRec.Code)assert.Equal(t, expected, responseRec.Body.String())assert.Equal(t, true, mockClient.Called)}

Теперь нам нужно написать mock для нашего http клиента:

common_test.go
package handlers_testimport ("errors""go-scrum-poker-bot/web/clients")type MockClient struct {Called boolError  string}func (c *MockClient) Make(request *clients.Request) *clients.Response {c.Called = truevar err error = nilif c.Error != "" {err = errors.New(c.Error)}return &clients.Response{Error: err}}

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

Теперь можно приступить к написанию UI строителя интерфейсов для Slack UI Block Kit. Там все довольно просто, но много однотипного кода. Отмечу лишь, что Slack API мне не очень понравился и было тяжело с ним работать. Сам UI Builder можно глянуть в папке ui здесь. А здесь, в целях экономии времени, я не буду на нем заострять внимания. Отмечу лишь, что в качестве якоря для понимания того, событие от какого сообщения пришло и какой был текст для голосования (его мы не будем сохранять у себя, а будем брать непосредственно из события) будем использовать block_id. А для определения типа события будем смотреть на action_id.

Давайте создадим конфиг для нашего приложения. Идем в config и создаем:

config.go
package configtype Config struct {App   *AppSlack *SlackRedis *Redis}func NewConfig() *Config {return &Config{App: &App{ServerAddress: getStrEnv("WEB_SERVER_ADDRESS", ":8000"),PokerRanks:    getListStrEnv("POKER_RANKS", "?,0,0.5,1,2,3,5,8,13,20,40,100"),},Slack: &Slack{Token: getStrEnv("SLACK_TOKEN", "FILL_ME"),},    // Скоро понадобитсяRedis: &Redis{Host: getStrEnv("REDIS_HOST", "0.0.0.0"),Port: getIntEnv("REDIS_PORT", "6379"),DB:   getIntEnv("REDIS_DB", "0"),},}}// Получаем значение из env или выставляем defaultfunc getStrEnv(key string, defaultValue string) string {if value, ok := os.LookupEnv(key); ok {return value}return defaultValue}// Получаем int значение из env или выставляем defaultfunc getIntEnv(key string, defaultValue string) int {value, err := strconv.Atoi(getStrEnv(key, defaultValue))if err != nil {panic(fmt.Sprintf("Incorrect env value for %s", key))}return value}// Получаем список (e.g. 0,1,2,3,4,5) из env или выставляем defaultfunc getListStrEnv(key string, defaultValue string) []string {value := []string{}for _, item := range strings.Split(getStrEnv(key, defaultValue), ",") {value = append(value, strings.TrimSpace(item))}return value}

И напишем тесты к нему. Будем тестировать только публичные методы:

config_test.go
package config_testimport (    "go-scrum-poker-bot/config"    "os"    "testing"    "github.com/stretchr/testify/assert")func TestNewConfig(t *testing.T) {    c := config.NewConfig()    assert.Equal(t, "0.0.0.0", c.Redis.Host)    assert.Equal(t, 6379, c.Redis.Port)    assert.Equal(t, 0, c.Redis.DB)    assert.Equal(t, []string{"?", "0", "0.5", "1", "2", "3", "5", "8", "13", "20", "40", "100"}, c.App.PokerRanks)}func TestNewConfigIncorrectIntFromEnv(t *testing.T) {    os.Setenv("REDIS_PORT", "-")    assert.Panics(t, func() { config.NewConfig() })}

Я намеренно сделал обязательность выставления значений по умолчанию, хотя это не самый правильный путь. Изменим main.go:

main.go
package mainimport ("fmt""go-scrum-poker-bot/config""go-scrum-poker-bot/ui""go-scrum-poker-bot/web/clients"clients_middleware "go-scrum-poker-bot/web/clients/middleware""go-scrum-poker-bot/web/server"  "log""net/http""os""time")func main() {logger := log.New(os.Stdout, "INFO: ", log.LstdFlags)config := config.NewConfig()builder := ui.NewBuilder(config)webClient := clients.NewBasicClient(&http.Client{Timeout: 5 * time.Second,},[]clients.Middleware{ // Наши middlewareclients_middleware.Auth(config.Slack.Token),clients_middleware.JsonContentType,clients_middleware.Log(logger),},)app := server.NewServer(logger,webClient,builder,)app.Serve(config.App.ServerAddress)}

Теперь при запуске команды /poker мы в ответ получим наш симпатичный минималистичный интерфейс.

Slack Interactivity

Давайте научимся реагировать на события при взаимодействии пользователя с ним. Зайдем Your apps -> Наш бот -> Interactivity & Shortcuts. В Request URL введем:

https://ffd3cfcc460c.ngrok.io/interactivity

Создадим еще один хэндлер InteractionCallback в web -> server -> handlers:

interaction_callback.go
package handlersimport ("go-scrum-poker-bot/storage""go-scrum-poker-bot/ui""go-scrum-poker-bot/ui/blocks""go-scrum-poker-bot/web/clients""go-scrum-poker-bot/web/server/models""net/http")func InteractionCallback(userStorage storage.UserStorage,sessionStorage storage.SessionStorage,uiBuilder *ui.Builder,webClient clients.Client,) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {var callback models.Callback    // Об этом нижеdata, err := callback.SerializedData([]byte(r.PostFormValue("payload")))if err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}    // TODO: Скоро доберемся до нихusers := userStorage.All(data.SessionID)visible := sessionStorage.GetVisibility(data.SessionID)err = nil    // Определяем какое событие к нам поступило и реализуем немного логики исходя из негоswitch data.Action.ActionID {case ui.VOTE_ACTION_ID:users[callback.User.Username] = data.Action.SelectedOption.Valueerr = userStorage.Save(data.SessionID, callback.User.Username, data.Action.SelectedOption.Value)case ui.RESULTS_VISIBILITY_ACTION_ID:visible = !visibleerr = sessionStorage.SetVisibility(data.SessionID, visible)}if err != nil {http.Error(w, err.Error(), http.StatusInternalServerError)return}    // Шлем ответ перерисовывая интерфейс сообщения через response URL. Для пользователя все пройдет незаметноresp := webClient.Make(&clients.Request{URL:    callback.ResponseURL,Method: "POST",Json: &blocks.Interactive{ReplaceOriginal: true,Blocks:          uiBuilder.BuildBlocks(data.Subject, users, data.SessionID, visible),LinkNames:       true,},})if resp.Error != nil {http.Error(w, resp.Error.Error(), http.StatusInternalServerError)return}})}

Мы пока не определили наше хранилище. Давайте определим их интерфейсы и напишем тест на этот хэндлер. Идем в storage:

storage.go
package storagetype UserStorage interface {All(sessionID string) map[string]stringSave(sessionID string, username string, value string) error}type SessionStorage interface {GetVisibility(sessionID string) boolSetVisibility(sessionID string, state bool) error}

Я намеренно разбил логику на два хранилища, поскольку так удобнее тестировать и если будет нужно, то легко можно будет перевести например хранение голосов пользователей в базу данных, а настройки сессии оставить в Redis (как пример).

Теперь нужно создать модель Callback. Идем в web -> server -> models:

callback.go
package modelsimport ("encoding/json""errors""go-scrum-poker-bot/ui")type User struct {Username string `json:"username"`}type Text struct {Type string `json:"type"`Text string `json:"text"`}type Block struct {Type    string `json:"type"`BlockID string `json:"block_id"`Text    *Text  `json:"text,omitempty"`}type Message struct {Blocks []*Block `json:"blocks,omitempty"`}type SelectedOption struct {Value string `json:"value"`}type Action struct {BlockID        string          `json:"block_id"`ActionID       string          `json:"action_id"`Value          string          `json:"value,omitempty"`SelectedOption *SelectedOption `json:"selected_option,omitempty"`}type SerializedData struct {SessionID stringSubject   stringAction    *Action}type Callback struct {ResponseURL string    `json:"response_url"`User        *User     `json:"user"`Actions     []*Action `json:"actions"`Message     *Message  `json:"message,omitempty"`}// Грязно достаем ID сессии, но другого способа я не смог придуматьfunc (c *Callback) getSessionID() (string, error) {for _, action := range c.Actions {if action.BlockID != "" {return action.BlockID, nil}}return "", errors.New("Invalid session ID")}// Текст для голосованияfunc (c *Callback) getSubject() (string, error) {for _, block := range c.Message.Blocks {if block.BlockID == ui.SUBJECT_BLOCK_ID && block.Text != nil {return block.Text.Text, nil}}return "", errors.New("Invalid subject")}// Какое событие к нам пришлоfunc (c *Callback) getAction() (*Action, error) {for _, action := range c.Actions {if action.ActionID == ui.VOTE_ACTION_ID || action.ActionID == ui.RESULTS_VISIBILITY_ACTION_ID {return action, nil}}return nil, errors.New("Invalid action")}func (c *Callback) SerializedData(data []byte) (*SerializedData, error) {err := json.Unmarshal(data, c)if err != nil {return nil, err}sessionID, err := c.getSessionID()if err != nil {return nil, err}subject, err := c.getSubject()if err != nil {return nil, err}action, err := c.getAction()if err != nil {return nil, err}return &SerializedData{SessionID: sessionID,Subject:   subject,Action:    action,}, nil}

Давайте напишем тест на наш хэндлер:

interaction_callback_test.go
package handlers_testimport ("encoding/json""go-scrum-poker-bot/config""go-scrum-poker-bot/ui""go-scrum-poker-bot/web/server/handlers""go-scrum-poker-bot/web/server/models""net/http""net/http/httptest""net/url""strings""testing""github.com/stretchr/testify/assert")func TestInteractionCallbackHandlerActions(t *testing.T) {config := config.NewConfig()mockClient := &MockClient{}mockUserStorage := &MockUserStorage{}mockSessionStorage := &MockSessionStorage{}uiBuilder := ui.NewBuilder(config)router := http.NewServeMux()router.Handle("/interactivity",handlers.InteractionCallback(mockUserStorage, mockSessionStorage, uiBuilder, mockClient),)actions := []*models.Action{{BlockID:        "test",ActionID:       ui.RESULTS_VISIBILITY_ACTION_ID,Value:          "test",SelectedOption: nil,},{BlockID:        "test",ActionID:       ui.VOTE_ACTION_ID,Value:          "test",SelectedOption: &models.SelectedOption{Value: "1"},},}  // Проверяем на двух разных типах событийfor _, action := range actions {responseRec := httptest.NewRecorder()data, _ := json.Marshal(models.Callback{ResponseURL: "test",User:        &models.User{Username: "test"},Actions:     []*models.Action{action},Message: &models.Message{Blocks: []*models.Block{{Type:    "test",BlockID: ui.SUBJECT_BLOCK_ID,Text:    &models.Text{Type: "test", Text: "test"},},},},})payload := url.Values{"payload": {string(data)}}.Encode()request, err := http.NewRequest("POST", "/interactivity", strings.NewReader(payload))request.Header.Set("Content-Type", "application/x-www-form-urlencoded")router.ServeHTTP(responseRec, request)assert.Nil(t, err)assert.Equal(t, http.StatusOK, responseRec.Code)assert.Empty(t, responseRec.Body.String())assert.Equal(t, true, mockClient.Called)}}

Осталось определить mock для наших хранилищ. Обновим файл common_test.go:

common_test.go
// Существующий кодtype MockUserStorage struct{}func (s *MockUserStorage) All(sessionID string) map[string]string {return map[string]string{"user": "1"}}func (s *MockUserStorage) Save(sessionID string, username string, value string) error {return nil}type MockSessionStorage struct{}func (s *MockSessionStorage) GetVisibility(sessionID string) bool {return true}func (s *MockSessionStorage) SetVisibility(sessionID string, state bool) error {return nil}

Добавив в роутер новый хэндлер:

server.go
// Существующий кодfunc (s *Server) setupRouter() http.Handler {router := http.NewServeMux()router.Handle("/healthcheck",handlers.Healthcheck(),)router.Handle("/play-poker",handlers.PlayPokerCommand(s.webClient, s.uiBuilder),)router.Handle("/interactivity",handlers.InteractionCallback(s.userStorage, s.sessionStorage, s.uiBuilder, s.webClient),)return router}// Существующий код

Все хорошо, но наш сервер никак не уведомляет нас о том, что к нему поступил запрос + если мы где-то поймаем панику, то сервер может упасть. Давайте это исправим через middleware. Создаем папку web -> server -> middleware:

log.go
package middlewareimport ("log""net/http")func Log(logger *log.Logger) func(http.Handler) http.Handler {return func(next http.Handler) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {defer func() {logger.Printf("Handle request: [%s]: %s - %s - %s",r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent(),)}()next.ServeHTTP(w, r)})}}

И напишем для нее тест:

log_test.go
package middleware_testimport ("bytes""go-scrum-poker-bot/web/server/middleware""log""net/http""net/http/httptest""os""strings""testing""github.com/stretchr/testify/assert")type logHandler struct{}func (h *logHandler) ServeHTTP(http.ResponseWriter, *http.Request) {}func TestLogMiddleware(t *testing.T) {var buf bytes.Bufferlogger := log.New(os.Stdout, "INFO: ", log.LstdFlags)  // Выставляем для логгера output наш буффер, чтобы все писалось в негоlogger.SetOutput(&buf)handler := &logHandler{}  // Берем mock recorder из стандартной библиотеки GoresponseRec := httptest.NewRecorder()router := http.NewServeMux()router.Handle("/test", middleware.Log(logger)(handler))request, err := http.NewRequest("GET", "/test", strings.NewReader(""))router.ServeHTTP(responseRec, request)assert.Nil(t, err)assert.Equal(t, http.StatusOK, responseRec.Code)  // Проверяем, что в буффер что-то пришло. Этого нам достаточно, чтобы понять, что middleware успешно отработалаassert.NotEmpty(t, buf.String())}

Остальные middleware можете найти здесь.

Ну и наконец слой хранения данных. Я решил взять Redis, так как это проще, да и не нужно для такого рода задач что-то большее, как мне кажется. Воспользуемся библиотекой go-redis и там же возьмем redismock для тестов.

Для начала научимся сохранять и получать всех пользователей переданной Scrum Poker сессии. Идем в storage:

users.go
package storageimport ("context""fmt""github.com/go-redis/redis/v8")// Шаблоны ключейconst SESSION_USERS_TPL = "SESSION:%s:USERS"const USER_VOTE_TPL = "SESSION:%s:USERNAME:%s:VOTE"type UserRedisStorage struct {redis   *redis.Clientcontext context.Context}func NewUserRedisStorage(redisClient *redis.Client) *UserRedisStorage {return &UserRedisStorage{redis:   redisClient,context: context.Background(),}}func (s *UserRedisStorage) All(sessionID string) map[string]string {users := make(map[string]string)  // Пользователей будем хранить в set, так как сортировка для нас не принципиальна.   // Заодно избавимся от необходимости искать дубликатыfor _, username := range s.redis.SMembers(s.context, fmt.Sprintf(SESSION_USERS_TPL, sessionID)).Val() {users[username] = s.redis.Get(s.context, fmt.Sprintf(USER_VOTE_TPL, sessionID, username)).Val()}return users}func (s *UserRedisStorage) Save(sessionID string, username string, value string) error {err := s.redis.SAdd(s.context, fmt.Sprintf(SESSION_USERS_TPL, sessionID), username).Err()if err != nil {return err}  // Голоса пользователей будем хранить в обычных ключах.   // Я сделал вечное хранение, но это легко можно поменять, изменив -1 на нужное значениеerr = s.redis.Set(s.context, fmt.Sprintf(USER_VOTE_TPL, sessionID, username), value, -1).Err()if err != nil {return err}return nil}

Напишем тесты:

users_test.go
package storage_testimport ("errors""fmt""go-scrum-poker-bot/storage""testing""github.com/go-redis/redismock/v8""github.com/stretchr/testify/assert")func TestAll(t *testing.T) {sessionID, username, value := "test", "user", "1"redisClient, mock := redismock.NewClientMock()usersStorage := storage.NewUserRedisStorage(redisClient)  // Redis mock требует обязательного указания всех ожидаемых команд и результаты их выполненияmock.ExpectSMembers(fmt.Sprintf(storage.SESSION_USERS_TPL, sessionID),).SetVal([]string{username})mock.ExpectGet(fmt.Sprintf(storage.USER_VOTE_TPL, sessionID, username),).SetVal(value)assert.Equal(t, map[string]string{username: value}, usersStorage.All(sessionID))}func TestSave(t *testing.T) {sessionID, username, value := "test", "user", "1"redisClient, mock := redismock.NewClientMock()usersStorage := storage.NewUserRedisStorage(redisClient)mock.ExpectSAdd(fmt.Sprintf(storage.SESSION_USERS_TPL, sessionID),username,).SetVal(1)mock.ExpectSet(fmt.Sprintf(storage.USER_VOTE_TPL, sessionID, username),value,-1,).SetVal(value)assert.Equal(t, nil, usersStorage.Save(sessionID, username, value))}func TestSaveSAddErr(t *testing.T) {sessionID, username, value, err := "test", "user", "1", errors.New("ERROR")redisClient, mock := redismock.NewClientMock()usersStorage := storage.NewUserRedisStorage(redisClient)mock.ExpectSAdd(fmt.Sprintf(storage.SESSION_USERS_TPL, sessionID),username,).SetErr(err)assert.Equal(t, err, usersStorage.Save(sessionID, username, value))}func TestSaveSetErr(t *testing.T) {sessionID, username, value, err := "test", "user", "1", errors.New("ERROR")redisClient, mock := redismock.NewClientMock()usersStorage := storage.NewUserRedisStorage(redisClient)mock.ExpectSAdd(fmt.Sprintf(storage.SESSION_USERS_TPL, sessionID),username,).SetVal(1)mock.ExpectSet(fmt.Sprintf(storage.USER_VOTE_TPL, sessionID, username),value,-1,).SetErr(err)assert.Equal(t, err, usersStorage.Save(sessionID, username, value))}

Теперь определим хранилище для "покерной" сессии. Пока там будет лежать статус видимости голосов:

sessions.go
package storageimport ("context""fmt""strconv""github.com/go-redis/redis/v8")// Шаблон для ключейconst SESSION_VOTES_HIDDEN_TPL = "SESSION:%s:VOTES_HIDDEN"type SessionRedisStorage struct {redis   *redis.Clientcontext context.Context}func NewSessionRedisStorage(redisClient *redis.Client) *SessionRedisStorage {return &SessionRedisStorage{redis:   redisClient,context: context.Background(),}}func (s *SessionRedisStorage) GetVisibility(sessionID string) bool {value, _ := strconv.ParseBool(s.redis.Get(s.context, fmt.Sprintf(SESSION_VOTES_HIDDEN_TPL, sessionID)).Val(),)return value}func (s *SessionRedisStorage) SetVisibility(sessionID string, state bool) error {return s.redis.Set(s.context,fmt.Sprintf(SESSION_VOTES_HIDDEN_TPL, sessionID),strconv.FormatBool(state),-1,).Err()}

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

sessions_test.go
package storage_testimport ("errors""fmt""go-scrum-poker-bot/storage""strconv""testing""github.com/go-redis/redismock/v8""github.com/stretchr/testify/assert")func TestGetVisibility(t *testing.T) {sessionID, state := "test", trueredisClient, mock := redismock.NewClientMock()mock.ExpectGet(fmt.Sprintf(storage.SESSION_VOTES_HIDDEN_TPL, sessionID),).SetVal(strconv.FormatBool(state))sessionStorage := storage.NewSessionRedisStorage(redisClient)assert.Equal(t, state, sessionStorage.GetVisibility(sessionID))}func TestSetVisibility(t *testing.T) {sessionID, state := "test", trueredisClient, mock := redismock.NewClientMock()mock.ExpectSet(fmt.Sprintf(storage.SESSION_VOTES_HIDDEN_TPL, sessionID),strconv.FormatBool(state),-1,).SetVal("1")sessionStorage := storage.NewSessionRedisStorage(redisClient)assert.Equal(t, nil, sessionStorage.SetVisibility(sessionID, state))}func TestSetVisibilityErr(t *testing.T) {sessionID, state, err := "test", true, errors.New("ERROR")redisClient, mock := redismock.NewClientMock()mock.ExpectSet(fmt.Sprintf(storage.SESSION_VOTES_HIDDEN_TPL, sessionID),strconv.FormatBool(state),-1,).SetErr(err)sessionStorage := storage.NewSessionRedisStorage(redisClient)assert.Equal(t, err, sessionStorage.SetVisibility(sessionID, state))}

Отлично! Осталось изменить main.go и server.go:

server.go
package serverimport ("context""go-scrum-poker-bot/storage""go-scrum-poker-bot/ui""go-scrum-poker-bot/web/clients""go-scrum-poker-bot/web/server/handlers""log""net/http""os""os/signal""sync/atomic""time")// Новый тип для middlewaretype Middleware func(next http.Handler) http.Handler// Все зависимости здесьtype Server struct {healthy        int32middleware     []Middlewarelogger         *log.LoggerwebClient      clients.ClientuiBuilder      *ui.BuilderuserStorage    storage.UserStoragesessionStorage storage.SessionStorage}// Добавляем их при инициализации сервераfunc NewServer(logger *log.Logger,webClient clients.Client,uiBuilder *ui.Builder,userStorage storage.UserStorage,sessionStorage storage.SessionStorage,middleware []Middleware,) *Server {return &Server{logger:         logger,webClient:      webClient,uiBuilder:      uiBuilder,userStorage:    userStorage,sessionStorage: sessionStorage,middleware:     middleware,}}func (s *Server) setupRouter() http.Handler {router := http.NewServeMux()router.Handle("/healthcheck",handlers.Healthcheck(),)router.Handle("/play-poker",handlers.PlayPokerCommand(s.webClient, s.uiBuilder),)router.Handle("/interactivity",handlers.InteractionCallback(s.userStorage, s.sessionStorage, s.uiBuilder, s.webClient),)return router}func (s *Server) setupMiddleware(router http.Handler) http.Handler {handler := routerfor _, middleware := range s.middleware {handler = middleware(handler)}return handler}func (s *Server) Serve(address string) {server := &http.Server{Addr:         address,Handler:      s.setupMiddleware(s.setupRouter()),ErrorLog:     s.logger,ReadTimeout:  5 * time.Second,WriteTimeout: 10 * time.Second,IdleTimeout:  15 * time.Second,}done := make(chan bool)quit := make(chan os.Signal, 1)signal.Notify(quit, os.Interrupt)go func() {<-quits.logger.Println("Server is shutting down...")atomic.StoreInt32(&s.healthy, 0)ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)defer cancel()server.SetKeepAlivesEnabled(false)if err := server.Shutdown(ctx); err != nil {s.logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)}close(done)}()s.logger.Println("Server is ready to handle requests at", address)atomic.StoreInt32(&s.healthy, 1)if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {s.logger.Fatalf("Could not listen on %s: %v\n", address, err)}<-dones.logger.Println("Server stopped")}
main.go
package mainimport ("fmt""go-scrum-poker-bot/config""go-scrum-poker-bot/storage""go-scrum-poker-bot/ui""go-scrum-poker-bot/web/clients"clients_middleware "go-scrum-poker-bot/web/clients/middleware""go-scrum-poker-bot/web/server"server_middleware "go-scrum-poker-bot/web/server/middleware""log""net/http""os""time""github.com/go-redis/redis/v8")func main() {logger := log.New(os.Stdout, "INFO: ", log.LstdFlags)config := config.NewConfig()  // Объявляем Redis клиентredisCLI := redis.NewClient(&redis.Options{Addr: fmt.Sprintf("%s:%d", config.Redis.Host, config.Redis.Port),DB:   config.Redis.DB,})  // Наш users storageuserStorage := storage.NewUserRedisStorage(redisCLI)  // Наш sessions storagesessionStorage := storage.NewSessionRedisStorage(redisCLI)builder := ui.NewBuilder(config)webClient := clients.NewBasicClient(&http.Client{Timeout: 5 * time.Second,},[]clients.Middleware{clients_middleware.Auth(config.Slack.Token),clients_middleware.JsonContentType,clients_middleware.Log(logger),},)  // В Server теперь есть middlewareapp := server.NewServer(logger,webClient,builder,userStorage,sessionStorage,[]server.Middleware{server_middleware.Recover(logger), server_middleware.Log(logger), server_middleware.Json},)app.Serve(config.App.ServerAddress)}

Запустим тесты:

go test ./... -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic

Результат:

go tool cover -func coverage.txt
$ go tool cover -func coverage.txtgo-scrum-poker-bot/config/config.go:9:                                  NewConfig               100.0%go-scrum-poker-bot/config/helpers.go:10:                                getStrEnv               100.0%go-scrum-poker-bot/config/helpers.go:17:                                getIntEnv               100.0%go-scrum-poker-bot/config/helpers.go:26:                                getListStrEnv           100.0%go-scrum-poker-bot/main.go:22:                                          main                    0.0%go-scrum-poker-bot/storage/sessions.go:18:                              NewSessionRedisStorage  100.0%go-scrum-poker-bot/storage/sessions.go:25:                              GetVisibility           100.0%go-scrum-poker-bot/storage/sessions.go:33:                              SetVisibility           100.0%go-scrum-poker-bot/storage/users.go:18:                                 NewUserRedisStorage     100.0%go-scrum-poker-bot/storage/users.go:25:                                 All                     100.0%go-scrum-poker-bot/storage/users.go:34:                                 Save                    100.0%go-scrum-poker-bot/ui/blocks/action.go:9:                               BlockType               100.0%go-scrum-poker-bot/ui/blocks/button.go:11:                              BlockType               100.0%go-scrum-poker-bot/ui/blocks/context.go:9:                              BlockType               100.0%go-scrum-poker-bot/ui/blocks/section.go:9:                              BlockType               100.0%go-scrum-poker-bot/ui/blocks/select.go:10:                              BlockType               100.0%go-scrum-poker-bot/ui/builder.go:14:                                    NewBuilder              100.0%go-scrum-poker-bot/ui/builder.go:18:                                    getGetResultsText       100.0%go-scrum-poker-bot/ui/builder.go:26:                                    getResults              100.0%go-scrum-poker-bot/ui/builder.go:41:                                    getOptions              100.0%go-scrum-poker-bot/ui/builder.go:50:                                    BuildBlocks             100.0%go-scrum-poker-bot/ui/builder.go:100:                                   Build                   100.0%go-scrum-poker-bot/web/clients/client.go:22:                            NewBasicClient          100.0%go-scrum-poker-bot/web/clients/client.go:26:                            makeRequest             78.9%go-scrum-poker-bot/web/clients/client.go:65:                            Make                    66.7%go-scrum-poker-bot/web/clients/middleware/auth.go:8:                    Auth                    100.0%go-scrum-poker-bot/web/clients/middleware/json.go:5:                    JsonContentType         100.0%go-scrum-poker-bot/web/clients/middleware/log.go:8:                     Log                     87.5%go-scrum-poker-bot/web/clients/request.go:12:                           ToBytes                 100.0%go-scrum-poker-bot/web/clients/response.go:12:                          Json                    100.0%go-scrum-poker-bot/web/server/handlers/healthcheck.go:10:               Healthcheck             66.7%go-scrum-poker-bot/web/server/handlers/interaction_callback.go:12:      InteractionCallback     71.4%go-scrum-poker-bot/web/server/handlers/play_poker.go:13:                PlayPokerCommand        100.0%go-scrum-poker-bot/web/server/middleware/json.go:5:                     Json                    100.0%go-scrum-poker-bot/web/server/middleware/log.go:8:                      Log                     100.0%go-scrum-poker-bot/web/server/middleware/recover.go:9:                  Recover                 100.0%go-scrum-poker-bot/web/server/models/callback.go:52:                    getSessionID            100.0%go-scrum-poker-bot/web/server/models/callback.go:62:                    getSubject              100.0%go-scrum-poker-bot/web/server/models/callback.go:72:                    getAction               100.0%go-scrum-poker-bot/web/server/models/callback.go:82:                    SerializedData          92.3%go-scrum-poker-bot/web/server/models/errors.go:13:                      ResponseError           75.0%go-scrum-poker-bot/web/server/server.go:31:                             NewServer               0.0%go-scrum-poker-bot/web/server/server.go:49:                             setupRouter             0.0%go-scrum-poker-bot/web/server/server.go:67:                             setupMiddleware         0.0%go-scrum-poker-bot/web/server/server.go:76:                             Serve                   0.0%total:                                                                  (statements)            75.1%

Неплохо, но нам не нужно учитывать в coverage main.go (мое мнение) и server.go (здесь можно поспорить), поэтому есть хак :). Нужно добавить в начало файлов, которые мы хотим исключить из оценки следующую строчку с тегами:

//+build !test

Перезапустим с тегом:

go test ./... -race -coverpkg=./... -coverprofile=coverage.txt -covermode=atomic -tags=test

Результат:

go tool cover -func coverage.txt
$ go tool cover -func coverage.txtgo-scrum-poker-bot/config/config.go:9:                                  NewConfig               100.0%go-scrum-poker-bot/config/helpers.go:10:                                getStrEnv               100.0%go-scrum-poker-bot/config/helpers.go:17:                                getIntEnv               100.0%go-scrum-poker-bot/config/helpers.go:26:                                getListStrEnv           100.0%go-scrum-poker-bot/storage/sessions.go:18:                              NewSessionRedisStorage  100.0%go-scrum-poker-bot/storage/sessions.go:25:                              GetVisibility           100.0%go-scrum-poker-bot/storage/sessions.go:33:                              SetVisibility           100.0%go-scrum-poker-bot/storage/users.go:18:                                 NewUserRedisStorage     100.0%go-scrum-poker-bot/storage/users.go:25:                                 All                     100.0%go-scrum-poker-bot/storage/users.go:34:                                 Save                    100.0%go-scrum-poker-bot/ui/blocks/action.go:9:                               BlockType               100.0%go-scrum-poker-bot/ui/blocks/button.go:11:                              BlockType               100.0%go-scrum-poker-bot/ui/blocks/context.go:9:                              BlockType               100.0%go-scrum-poker-bot/ui/blocks/section.go:9:                              BlockType               100.0%go-scrum-poker-bot/ui/blocks/select.go:10:                              BlockType               100.0%go-scrum-poker-bot/ui/builder.go:14:                                    NewBuilder              100.0%go-scrum-poker-bot/ui/builder.go:18:                                    getGetResultsText       100.0%go-scrum-poker-bot/ui/builder.go:26:                                    getResults              100.0%go-scrum-poker-bot/ui/builder.go:41:                                    getOptions              100.0%go-scrum-poker-bot/ui/builder.go:50:                                    BuildBlocks             100.0%go-scrum-poker-bot/ui/builder.go:100:                                   Build                   100.0%go-scrum-poker-bot/web/clients/client.go:22:                            NewBasicClient          100.0%go-scrum-poker-bot/web/clients/client.go:26:                            makeRequest             78.9%go-scrum-poker-bot/web/clients/client.go:65:                            Make                    66.7%go-scrum-poker-bot/web/clients/middleware/auth.go:8:                    Auth                    100.0%go-scrum-poker-bot/web/clients/middleware/json.go:5:                    JsonContentType         100.0%go-scrum-poker-bot/web/clients/middleware/log.go:8:                     Log                     87.5%go-scrum-poker-bot/web/clients/request.go:12:                           ToBytes                 100.0%go-scrum-poker-bot/web/clients/response.go:12:                          Json                    100.0%go-scrum-poker-bot/web/server/handlers/healthcheck.go:10:               Healthcheck             66.7%go-scrum-poker-bot/web/server/handlers/interaction_callback.go:12:      InteractionCallback     71.4%go-scrum-poker-bot/web/server/handlers/play_poker.go:13:                PlayPokerCommand        100.0%go-scrum-poker-bot/web/server/middleware/json.go:5:                     Json                    100.0%go-scrum-poker-bot/web/server/middleware/log.go:8:                      Log                     100.0%go-scrum-poker-bot/web/server/middleware/recover.go:9:                  Recover                 100.0%go-scrum-poker-bot/web/server/models/callback.go:52:                    getSessionID            100.0%go-scrum-poker-bot/web/server/models/callback.go:62:                    getSubject              100.0%go-scrum-poker-bot/web/server/models/callback.go:72:                    getAction               100.0%go-scrum-poker-bot/web/server/models/callback.go:82:                    SerializedData          92.3%go-scrum-poker-bot/web/server/models/errors.go:13:                      ResponseError           75.0%total:                                                                  (statements)            90.9%

Такой результат мне нравится больше :)

На этом пожалуй остановлюсь. Весь код можете найти здесь. Спасибо за внимание!

Подробнее..

Перевод 5 опенсорсных альтернатив Slack для группового чата

08.07.2020 22:16:49 | Автор: admin
При любом сотрудничестве важно иметь хорошие инструменты для общения. Безусловно, они индивидуальны и зависят от вашей ситуации, но могут включать в себя рассылки для общения по электронной почте, Git или Subversion для управления версиями, Википедию или Etherpad для совместной работы, расшеренный список задач для организации рабочего процесса или даже полноценный пакет управления проектами.

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

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

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

Slack быстро стал любимчиком в кругах разработчиков программного обеспечения и вытеснил другие инструменты. В статье The Next Web написано следующее: Slack тихо и ненамеренно убивает IRC, многие сообщества с открытым ходом также совершили этот переход. Например, разработчики Wordpress перешли на Slack для командного общения.

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

Mattermost


Mattermost очень современный подход с групповому чату, предлагающий как самостоятельный, так и другие варианты хостинга. Он написан на Golang с хорошим куском JavaScript под фреймворком React. В нем имеются личные и публичные чаты, включая общение один на один, хорошую архивную поддержку и интерфейс, похожий на Slack, включая множество ожидаемых функций. Вообще, если вы уже используете Slack, вы можете легко импортировать ваши текущие каналы и архивы. Mattermost также интегрируется в существующие системы аутентификации LDAP или Active Directory вашей организации.

Функция, которая мне нравится возможность загружать звуки, видео или картинки прямо с вашего мобильного устройства это очень удобно при общении на ходу. Mattermost лицензирован под Apache-wrapped AGPL. Зацените исходный код на GitHub, а потом попробуйте его в использовании.



Zulip


Zulip ещё один межплатформенный мультимедийный сервис с чатом, выпущенный под лицензией Apache и предлагающий варианты хостинга. В нем есть много того, чего вы будете ожидать, если знакомы со Slack: встраивание изображений, @-упоминания, загрузка файлов, наличие лога и ещё много всего. В нем есть несколько каналов (комнат) вы обязательно будете использовать их для работы в команде. Это бесплатный сервис с платными планами для увеличения внутреннего хранилища, LDAP и интеграцией Активного каталога, а также с локальной поддержкой.

Функция импорта вашей организации из Slack, HipChat, Mattermost и Gitter здесь также имеется.
Zulip работает на Linux, Mac, Windows, iOS и Android.



Rocket.Chat


Rocket.Chat написан на CoffeeScript и JavaScript поверх Meteor фреймворка. Rocket.Chat предназначен для локальной загрузки и работы с клиентским интерфейсом как для рабочего стола, так и для мобильного устройства. Он содержит множество похожих функций как и другие современные приложения от уведомлений на рабочем столе до загрузки картинок и файлов в архив с поиском, и интеграций с LDAP. В планах также и родное Android приложение, поддержка Kerberos и интеграции с многими другими инструментами с GitLab, а также антивирусное средства для прикрепленных файлов.
Rocket.Chat имеет демонстрационную онлайн версию, вы также можете посмотреть его исходный код на GitHub. Доступен по лицензии MIT.



Riot.im


Riot.im стал необыкновенно популярен с момента публикации статьи и заслужил в ней своё место. В нем имеется набор веб и мобильных инструментов, которые могут использоваться для подключения к Matrix, открытой сети для безопасной децентрализованной связи.
Riot, вероятно, наиболее легкий для использования из всего списка. Вы также можете проверить весь исходный код всех проектов на GitHub под лицензией Apache 2.0.

Благодаря интеграции, вы также можете использовать Riot для соединения с сервисами, использующими IRC, Slack или Gitter, что потенциально позволяет вам заменить несколько клиентов одним вариантом с открытым исходным кодом.
Чтобы узнать больше, ознакомьтесь с нашим полным введением в проект.



IRC


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

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



Другие альтернативы


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


image


Узнайте подробности, как получить востребованную профессию с нуля или Level Up по навыкам и зарплате, пройдя платные онлайн-курсы SkillFactory:



Полезное


Подробнее..

Опенсорс-бот для трекинга SLA в хелпдеске

10.03.2021 12:22:46 | Автор: admin

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

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

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

Итак, мы можем подключить бота к каналу в Slack, сделать простой интерфейс со списками и лить данные в таблицу. Можно в свою базу данных, можно в Google Таблицы. Выбирая решение для прототипа, я остановился на последних: с ними умеют работать все, особенно менеджеры (мой основной заказчик), и у продукта есть готовое API.


Так за 4 часа ожидания пересадки в аэропорту Хельсинки появился MVP бота.

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

Благодаря таблицам мы стали быстро собирать фидбэк от заказчика, и вскоре бот оброс новыми фичами:

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

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

Как бот стал популярным и как я упрощал его поддержку


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

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

  • Простая функциональность для раскатки на другие команды: каждый новый Slack-канал подключается простым добавлением пары ID (к какому каналу подключиться и куда отправлять данные) на GitHub. Завести бота для нового заказчика внутри компании буквально дописать 6 строк в один из файликов.
  • Добавление категорий без участия разработки: в табличке у каждого заказчика есть лист, на котором можно дописывать классы обращений, не привлекая нас. А оно передается в бот.
  • Поддержка Slack Workflows. Что любят люди, помимо таблиц? Формы. Поэтому Slack сделал нативное решение, которое их заменяет. Но т.к. формы реализованы на базе бота, а наш бот обучен не учитывать сообщения от других ботов и не отвечать на них (были забавные инциденты), пришлось чуть повозиться с тонкими настройками фильтра не реагируй на других ботов.

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

И решил, что пора его опенсорсить


Теперь вы можете поднять своего Лебовски на своем сервере (или в Heroku) и пользоваться им в своем рабочем Slack. Инструкции о том, как его развернуть, а также сами исходники бота лежат на нашем GitHub здесь.

Сейчас Лебовски работает так:

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



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

С одной стороны может показаться, что этот бот делает не так уж и много, но в повседневной жизни этого достаточно, чтобы посчитать много полезных метрик, например AFRT (Average First Response Time) и ACRT (Average Case Resolution Time).

Но лучше 1 раз попробовать, чем 100 раз прочитать.

Что еще почитать про боты в Slack: 7 cмертных грехов Slack в большой компании (и как победить их автоматизацией).
Подробнее..

Перевод Самый ужасный день в компании Slack

08.07.2020 18:05:40 | Автор: admin


Эта статья описывает технические детали проблем, из-за которых Slack упал 12 мая 2020 года. Больше о процессе реагирования на тот инцидент см. хронологию Райана Каткова Обе руки на пульте.

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

Пользователи заметили даунтайм в 16:45 по тихоокеанскому времени, но на самом деле история началась около 8:30 утра. Команда разработки по надёжности БД (Database Reliability Engineering Team) получила предупреждение о значительном увеличении нагрузки на часть инфраструктуры. В то же время команда по трафику (Traffic Team) получила предупреждения, что мы не выполняем некоторые запросы API.

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

Одним из последствий инцидента стало значительное расширение нашего основного уровня веб-приложений. Наш генеральный директор Стюарт Баттерфилд писал о некотором влиянии карантина и самоизоляции на использование Slack. В результате пандемии мы запустили значительно больше инстансов на уровне веб-приложений, чем в далёком феврале этого года. Мы быстро масштабируемся, когда воркеры нагружаются, как это произошло здесь но воркеры гораздо дольше ждали завершения некоторых запросов к БД, что вызвало более высокую загрузку. Во время инцидента мы увеличили количество инстансов на 75%, что привело к максимальному количеству хостов веб-приложений, которые мы когда-либо запускали до сегодняшнего дня.

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

За балансировщиком нагрузки на 4-м уровне стоит набор инстансов HAProxy для распределения запросов на уровень веб-приложений. Мы используем Consul для обнаружения служб и consul-template для рендеринга списков здоровых бэкендов веб-приложений, к которым HAProxy должен направлять запросы.


Рис. 1. Высокоуровневое представление архитектуры балансировки нагрузки Slack

Однако мы не рендерим список хостов веб-приложений прямо из конфигурационного файла HAProxy, потому что обновление списка в таком случае требует перезагрузки HAProxy. Процесс перезагрузки HAProxy включает в себя создание совершенно нового процесса, сохраняя при этом старый, пока он не закончит работу с текущими запросами. Очень частые перезагрузки могут привести к слишком большому количеству запущенных процессов HAProxy и низкой производительности. Это ограничение находится в противоречии с целью автоматического масштабирования уровня веб-приложений, которая заключается в том, чтобы как можно быстрее вводить в эксплуатацию новые инстансы. Поэтому мы используем HAProxy Runtime API для управления состоянием сервера HAProxy без перезагрузки каждый раз, когда сервер веб-уровня входит в эксплуатацию или выходит из неё. Стоит отметить, что HAProxy может интегрироваться с DNS-интерфейсом Consul, но это добавляет лаг из-за TTL DNS, ограничивает возможность использования тегов Consul, а управление очень большими ответами DNS часто приводит к болезненным пограничным ситуациям и ошибкам.


Рис. 2. Как набор бэкендов веб-приложений управляется на одном сервере Slack HAProxy

В нашем состоянии HAProxy мы определяем шаблоны серверов HAProxy. Фактически, это слоты, которые могут занимать бэкенды веб-приложений. Когда выкатывается инстанс нового веб-приложения или старый начинает отказывать, обновляется каталог сервисов Consul. Consul-template выводит новую версию списка хостов, а отдельная программа haproxy-server-state-management, разработанная в Slack, считывает этот список хостов и использует HAProxy Runtime API для обновления состояния HAProxy.

Мы запускаем M параллельных пулов инстансов HAProxy и веб-приложений, каждый пул в отдельной зоне доступности AWS. HAProxy сконфигурирован с N слотами для бэкендов веб-приложений в каждой зоне доступности (AZ), что даёт в общей сложности N*M бэкендов, которые могут быть направлены на все AZ. Несколько месяцев назад это количество было более чем достаточным мы никогда не запускали ничего даже близко к такому количеству инстансов нашего уровня веб-приложений. Однако после утреннего инцидента с базой данных мы запустили чуть больше, чем N*M инстансов веб-приложений. Если представить слоты HAProxy как гигантскую игру в стулья, то некоторые из этих инстансов webapp остались без места. Это не было проблемой у нас более чем достаточно возможностей для обслуживания.

Рис. 3. Слоты в процессе HAProxy с некоторыми избыточными экземплярами веб-приложений, которые не получают трафик

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

В 16:45 большинство инстансов HAProxy были способны отправлять запросы только к набору бэкендов, доступных утром, и этот набор старых бэкендов webapp теперь составлял меньшинство. Мы регулярно предоставляем новые инстансы HAProxy, так что оставалось несколько свежих с правильной конфигурацией, но большинство из них оказались старше восьми часов и поэтому застряли с полным и устаревшим состоянием бэкенда. В конечном итоге, произошёл сбой сервиса. Это случилось в конце рабочего дня в США, потому что именно тогда мы начинаем масштабировать уровень веб-приложений по мере снижения трафика. Автомасштабирование в первую очередь завершает работу старых инстансов webapp, и это означало, что в серверном состоянии HAProxy их осталось недостаточно для обслуживания спроса.

Рис. 4. Состояние HAProxy изменялось с течением времени и слоты начали ссылаться в основном на удалённые хосты

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

Мы особо не переделывали этот стек HAProxy, потому что всю балансировку нагрузки постепенно переводим на Envoy (недавно мы перенесли на него трафик веб-сокетов). HAProxy хорошо и надёжно служил в течение многих лет, но у него есть некоторые операционные проблемы, как в этом инциденте. Сложный конвейер для управления состоянием сервера HAProxy мы заменим собственной интеграцией Envoy с плоскостью управления xDS для обнаружения конечных точек. Самые последние версии HAProxy (начиная с версии 2.0) тоже решают многие из этих операционных проблем. Тем не менее, мы уже некоторое время доверяем Envoy внутреннюю сервисную сетку, поэтому стремимся и балансировку нагрузки тоже перевести на него. Наше первоначальное тестирование Envoy+ xDS в масштабе выглядит многообещающе, и в будущем эта миграция должна улучшить как производительность, так и доступность. Новая архитектура балансировки нагрузки и обнаружения служб не восприимчива к проблеме, вызвавшей этот сбой.

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

История презентаций современные сервисы (часть 3)

04.12.2020 10:17:32 | Автор: admin
image

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



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

Создавать презентации можно онлайн


В 2007 году конкуренцию двум уже знакомым нам программам составил сервис от Google. В отличие от PowerPoint и Keynote, Google Slides это изначально онлайн-редактор. Устанавливать никаких программ на компьютер пользователям не приходилось, достаточно было зайти на сайт. Редактор слайдов входил в пакет программ Google Apps, который задумывался как полноценный конкурент Microsoft Office.

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

image
Alt: Интерфейс Google Slides очень похож на современный PowerPoint


Любопытно, что программы Google это не уникальные разработки компании. В частности, редактор слайдов был создан компанией Tonic Systems, которую купил Google.

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

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

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

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


Alt: Prezi удобен для демонстрации схем и инфографики, которая вмещает много информации

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

На сегодняшний день рынок поделен между четырьмя крупными игроками: PowerPoint, Keynote, Google Slides и Prezi именно эти программы пользуются популярностью среди людей, выступающих регулярно. В России стоит говорить даже о трех сервисах, потому что в Prezi ограничены возможности установки кириллических шрифтов: преимущественно этот редактор выбирают для англоязычных презентаций.

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

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

image
Alt: Так выглядит интерфейс Canva здесь есть огромное количество готовых шаблонов на все случаи жизни

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

Работать в команде теперь удобнее


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

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

Аналогичным образом организована командная работа в остальных сервисах. В Keynote совместное редактирование возможно при сохранении презентации в iCloud, в Google Slides при сохранении в Google Drive, в Prezi через библиотеку файлов на сайте. Совместная работа над слайдами необходимость, поэтому во всех крупных сервисах эта опция реализована на хорошем уровне.

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

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

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

image
Alt: В чате мессенджера участники получают уведомления от Prezi

Презентации становятся интерактивными


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

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

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

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

image
Alt: С помощью смартфонов в опросах может принимать участие неограниченное количество человек

Есть и другие аналогичные сервисы например, Slido или AhaSlides. Они работают по тому же принципу, и тоже интегрируются с PowerPoint.

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


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

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

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

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

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

Выводы


  • Сервисы для создания презентаций больше не ограничиваются установленными на компьютер программами: они переходят в онлайн. Так проще хранить файлы, получать к ним доступ с любого устройства или работать над презентацией совместно.
  • Для организации командной работы часто используются корпоративные мессенджеры: некоторые приложения для презентаций уже интегрируются с ними для удобства совместного просмотра и редактирования.
  • Интерактивы это важная часть современных презентаций. Технологии позволяют добавлять в слайды опросы и тесты, чтобы легко взаимодействовать с аудиторией.
  • Оформление презентаций перестало быть непосильной задачей для пользователей, далеких от дизайна: во всех сервисов доступны шаблоны на любой случай.
  • Вероятно, в будущем процесс создания слайдов удастся сделать еще проще благодаря развитию нейросетей. У PowerPoint уже доступны технологии искусственного интеллекта, которые помогают оформлять слайды в зависимости от их контента. Хотите использовать все возможности PowerPoint по максимуму? Записывайтесь на наш курс!
Подробнее..

Бархатная перчатка Microsoft

17.07.2020 20:07:39 | Автор: admin
Культурный контекст
Персонажи Люси и Чарли Браун это отсылка к очень популярному на западе, в частности Америке, комиксу Peanuts (оттуда же известен белый пес Snoopy). Люси на протяжении многих лет психологически издевалась над Чарли: призывая его с разбегу пнуть мяч, каждый раз давая иллюзию, но она, в самый последний момент, этот мяч от него забирала.
Всю свою жизнь, Чарли Браун, всю свою жизнь.
обобщительная статья (англ.) об этой шутке из комикса

Это мой первый крупный перевод текста, старался удержать плавный слог на русском языке. Будут пожелания знаете куда писать. Поехали!

Подход бархатной перчатки срабатывал у Microsoft каждый раз. Уже более 30 лет Microsoft-Люси утягивает мяч от разработчика Чарли Брауна. И контроль над GitHub мне абсолютно ясен. Но использовать Ната [Nat Friedman, GH CEO] в роли приманки по-моему чересчур.[1]

За Microsoft стоит удачная история овладевания и контроля над компьютерной платформой с целью контроля над сферой разработки ПО. Та поддержка, которую они оказывали разработчикам на заре Windows, была легендарна. И реальна. Дело в том, что пока люди скучковывались в сообщество разработчиков Windows, изобретая попутно совсем новые категории программ, они и понятия не имели, что все их перспективы и с ними связанные мечты на самом деле принадлежали Microsoft. Конечно, поддержка и маркетинговая помощь от Microsoft были потрясающими. Но оглядываясь назад, мы были идиотами. Точнее, я идиот 0055.
Люси оттягивает мяч перед Чарли, тот промахивается и со всей скорости падает на землю

Стоило лишь определенной категории программ вырасти до прибыльности, а разработчикам и инвесторам начать пускать слюнки, что их день вот, наконец, настал, как вмешивалась Microsoft и отбирала себе целую категорию этого рынка. Иногда это происходило за счет одного только объявления, что Microsoft планировала какую-то программу. Как это случилось с категорией менеджера связей и объявлением Outlook. Инвестиции прекратились. Продажи встали. Должны были пройти три года, прежде чем первая сносная версия Outlook увидела свет. А независимым разработчикам досталось. Те перспективы, о которых они мечтали, всегда были за Microsoft и всегда ей принадлежали.

Вот она сила контроля над платформой.

Платформа контролирует сферу ПО. А программы контролируют цифровую информацию, которую они собирают и производят. Новым разработчикам ПО нужен доступ к тоннам этой программозависимой информации. Но эта информации всё больше и больше принадлежит вычислительным платформам Microsoft Azure и облаку Office 365. Хочешь доступа? Регистрируйся на GitHub. Поцелуй бархатную перчатку. И потом жди, пока Люси не заберет мяч ото всех твоих перспектив, которые ты держал за свои.

Припоминаю я свою первую конференцию JavaONE. Первая продажа акций Netscape в августе 1995-го взбудоражила мир до лихорадки из-за сети Интернет. Java была представлена как способ разработки программ для сети Интернет. Удивительно как много было разработчиков Windows на первой и второй конференциях JavaONE. Выглядело это скорее как конференция по Windows-OS/2 минувших дней. Казалось, что я там всех знаю. Чувствовал себя как дома. На самом деле, я смекал из-за чего мы были там. Интернет был платформой используемой всеми, не принадлежащей никому. Он также стал приютом для сообществ открытого ПО (OSS).

Напряженность между OSS разработчиками и Sun заметно ощущалась. Мы виндузятники смотрели на это с изумлением. Сообщества открытого ПО боялись, что Java была троянским конём, придуманным Sun, дабы завладеть сферой ПО для Интернет. Это как раз то, что случилось с разработчиками Windows и объясняло, с чего мы собрались на JavaONE. Целыми толпами.

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

Все мы знаем те последующие годы, когда бизнес и OSS бились за Интернет. Бархатная перчатка, ту что носит Нат, ни что иное как ещё один вызов, и как всегда: опять всё на кону. Первое Правило Кремниевой Долины: Владелец платформы заправляет её перспективами.

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

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

В 1995-ом я работал с системой видеоконференций Intel ProShare Video Conferencing System, разрабатывая программы для интеграции средств коммуникаций с вычислительной информацией и совместной работой. В ретроспективе это кажется безупречным проектом, на который способен был лишь Интернет. Но тогда не было ни Netscape, ни Java, ни единого устоявшегося взгляда на то, чем Интернет позже станет. Торговля акциями Netscape началась в августе 95-го, скачок Java пришёлся на следующий год. В тот же год случился феномен под названием Windows. К тому времени, Люси уже забрала мяч и сообщество разработчиков под Windows прекрасно понимали, что Microsoft сама заведует всеми перспективами платформы Windows. Теми перспективами, что они могли отобрать в любой момент.

У Intel было много толковых ребят. И я подчеркиваю: очень умных. Та идея, связать коммуникации с вычислительной мощностью, всё это в обертке платформы для совместной работы она была у всех на уму. Вывели и маркетинговую формулу, которая всё это объясняла. Эта формула и объясняет почему бархатная перчатка и в какую сторону устремилась Microsoft.

Она была названа Уравнение Продуктивности Красиво. И звучит примерно так: продуктивность равна интеграции вычислений с коммуникациями, вычислительной информацией (данные, документы, сообщения) и сотрудническим обменом.

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

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

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

Часть вторая


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

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

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

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

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

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

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

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

ИМХО, весь тот прогресс в ИИ, который проделали Google, SalesForce и IBM, каким бы потрясающим он не был, даже близко не стоял с Microsoft. В конце концов вся красота переписки в Slack падет, как только предстанет перед, еле того достойной, версией MS Teams, но которая будет снабжена документами.

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

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

Смотрите сами: феномен облачных вычислений, как программная платформа, начал набирать обороты с выходом Google gMail и SalesForce.com. Подумать только, какая у них была фора, сколько времени! И когда iPhone вышел в свет в 2007г., началась гонка в облака. AWS появляется из ниоткуда и предоставляет отдельным разработчикам огромную вычислительную мощь в облаке.

В отчете за 2014г. Gartner Magic Quadrant записали Apple как облачного провайдера 1. Номером два был DropBox, за ним следовали Google, SalesForce, и Box. Баллмер [Steve Ballmer] уходит в феврале 2014-го, после ужасного многомиллиардного приобретения: коммуникационного гиганта Nokia. Тут бархатная перчатка перенимает и Office 365 становится доступным внутри империи iPhone. С Apple всё выглядит хорошо. А Windows не очень. DropBox, SalesForce и Box помчались за лицензией Office 365. Они жаждали сыграть с Люси в её рулетку [русскую рулетку]. И бархатная перчатка приняла их с распростертыми объятиями.

Далее проиcходило невероятное. С выпуском Office 365, вся монополия Microsoft начинает одно из величайших преобразований в компьютерной истории; переход их информации и программ с платформы Windows в Azure облако Office 365. Первым телодвижением бархатная перчатка устанавливает за собой владение над программозависимыми документами, чтобы перевести все столпы монополии с её системами в бизнесах и корпорациях.

2018-й год Magic Quadrant почти полностью закрепляет за двумя компаниями: Amazon и Microsoft. Четыре коротких года и развязка этого действа уже в поле зрения.[2] Google бьется с их классными и многообещающими ИИ, автоматизацией сообщений и бизнеса всё дальше об стену. Но дело не сдвигается с мертвой точки. Они остаются висеть на ниточке облачных вычислений. Остальные же лидеры облачных вычислений без конца изобретают вдохновляющие и фантастические программные возможности и перспективы, между тем Microsoft так же держит железный кулак над теми документами и информацией, которая нужна их конкурентам.

Часть третья


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

В 2018-м рост облака Microsoft оценивали в 18 млрд. долларов в год. Год спустя, оценка роста подписок в год исходила от ошеломляющих 33 млрд. долларов. Всё это и дальше экспоненциально устремляется ввысь.

Понимание сей новости[0] заключается в том, что разработчики c GitHub получат доступ к массивам вычислительной информации из облака Microsoft. А без этого никак, согласен. Но пока не перепрыгул, запомни: Люси тебе не друг. Бархатная перчатка отлично сидит на железной руке. И все перспективы, о которых мечтаешь, в которые вливаешься всеми силами и душою, твоими навыками и деньгами? Что ж, они принадлежат Microsoft. Опять.

Тебя отблагодарят.

~ge~

Конец

Сноски
[0] Microsoft? Oh it's just another partnership, insists GitHub CEO The Register, 24.05.2019

[1] В оригинале: But using Nat as bitch bait is a bit much. Нат, как парень, кто до непристойности легко, втирается в доверие девушкам. В данном случае эту черту эксплуатирует. наверх

[2] and the end game is in sight. End game это устоявшийся сленговое выражение, которое не столько буквальное финал и заключение, а как последняя фаза действия, цель, со стороны возможно непонятной, тактической игры. Перевод названия фильма Avengers: Endgame не воспроизводит всю эту глубину термина. наверх

Исторический контекст и примечание

Об авторе и истории:


Эта статья была оставлена год назад в виде комментария под новостной статьей о том, что несмотря на приобретение GitHub, Microsoft для этой компании останется как и прежде партнером.[0] Но грех бы было пройти мимо этого комментария, для меня он стал тем hidden gem, который случайно находишь в глубинах Интернета.

Интересно не оставляет обычный человек из ниоткуда вот такое вот эссе. Углубившись в поиски перед переводом я выяснил, что Гэри некоторое время состоял в Техническом Коммитете OpenDocument OASIS (как минимум 2002-2005гг.), где ковались в т.ч. форматы используемые OpenOffice в противовес всему Microsoft Office, и был волонтёром OpenOffice.org, далее, как понимаю, у него продолжилась карьера в ИТ-маркетинге (данные из презентации, стр. 3).
В 2005 году разыгралась драма нешуточных масштабов, когда американский штат Массачусеттс выдвинули идею стандартизировать OpenDocument для своего бюрократического аппарата. Разумеется, Microsoft боролась против этой затеи, что, видно, оставило след на памяти всех, кто был за открытый стандарт документов.
Officials in the state have proposed a new policy that mandates that every state technology system use only applications designed around OpenDocument file formats
Fox News, September 2005

В следствие этого конфликта, Microsoft устроили кампанию против этого стандарта/OOo, с тезисом, что формат может быть проприетарным из-за наличия каких-то лицензий или патентов от Sun, сообщение из рассылки (26.09.2005):
ODF Reciprocal License Allegation
Hi Eduardo,

Thanks for responding. Your explanation makes sense, but the shills and lackeys are off and running wild with this new discovery
that Sun has secret patents on ODF. Yes, they went full throttle, zero to sixty in under four seconds.

By next week this latest conspiracy theory will likely go the way of other myths that got some noise, and then into a vast echo
chamber that otherwise intelligent people reference in shamelessly self serving ways to justify the next conspiracy theory. I can
hear the deafening refrain now, There were so many reports that Sun had patents on ODF and that it's not really open, that you have
to stop and think.

I wrote a response to Brian Jones, and sent it to PJ for review. But the truth is, today is the first time i ever had to think
through the licensing issues. The interesting thing is that it's easy to circle false arguments, and set them spinning, even without
having a clue as to what i'm talking about :) At the end of the day they will become the fateful victims of their own wishful and
self serving exuberance. Such is life when you have no sense of integrity, trust and truth. And don't understand that when push
comes to shove, trust and truth are the only things that matter. Push came to shove in Massachusetts, and everyone got to see, up
close and personal, who they really are. Not a pretty sight. +1 Open Standards. +1 Open Source. Transparency rules.

Your arguments though have the truth of being there. Would you mind if PJ published your comments? I know that's asking a lot,
especially since there's far more at stake than needing to respond to the lies and deceits of the MS Office 12 gang. But your
response is clean, clear, and to the point. Groklaw does have one loud and booming voice. And PJ is the kind of do gooder who
doesn't like FUD. She usually does an excellent job of exposing and slamming away lies, deceits and distortions.

There is the distinct probability that things will get worse. I for one am quite surprised by the heavy handed, uncompromising take
no prisoners ferocity Microsoft has shown regarding the Massachusetts decision. ODF though is a silver bullet, and the shot Eric
Kriss and Peter Quinn took at all proprietary, platform and application bound file formats found it's mark. Finally.

The day before the final decision was made, i had a chance to speak at length with Peter Quinn. They were hoping against hope that
Microsoft would respect their decision and make the necessary accommodations to provide OpenDocument files. Sadly it was not to be,
but for sure Microsoft was given every consideration. Deserved or not.

Peter did ask if i would participate in his panel discussion session at the upcoming NACIO conference in San Diego. They expect
excellent attendance from every state. He's trying to get someone from Microsoft, but so far they are refusing to participate. So i
asked him if it would be okay if showed up with a few hundred OpenOffice.org CD's to pass out. He told me i would need more than that
:) Apparently the line behind Massachusetts is both long and ready.

I also asked if he and Eric would kindly autograph my copy of the OASIS OpenDocument v 1.0 specification. He said of course, but then
asked if i could get him a copy autographed by all the engineers and TC members who worked on OpenDocument. That would be a very nice
thing to do Eduardo, but could Sun help me put something like that together?

Thanks for setting things straight,

~ge~


С ссылкой на вот это очень длинное чтиво: Comments on Microsofts Letter to Massachusetts
by David A. Wheeler, October 29, 2005


Ещё есть интервью с GE, приуроченное к выходу OOo 2.0 (ноябрь, 2005): Gary Edwards: OpenOffice.org 2.0 leaping over legacy lockdown with clean XML

На письмо на эл. почту 2002 года (на yahoo) он не ответил :) Другие домены истекли, LinkedIn не пробовал, хотя он там наверняка есть.
Подробнее..

Перевод Использование Slack для отслеживания очереди недоставленных сообщений SQS

28.12.2020 18:17:25 | Автор: admin
AWS SQS играет значительную роль в современной архитектуре приложений, особенно в бессерверной среде. При работе с SQS часто можно увидеть, что сообщения не были прочитаны; причиной могут быть ошибка в вашем коде, временное ограничение ресурсов, превышение бюджета API или зависимости в сообщениях, которые должны быть обработаны. В большинстве случаев вы хотели бы знать, что это за сообщения, если они много раз терпят неудачу, а затем узнать, почему, и устранить проблемы. Именно здесь в игру вступает очередь недоставленных сообщений SQS.





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

  1. Нет деталей о недоставленных сообщениях. CloudWatch только показывает, что есть сообщения в очереди недоставленных сообщений, не сообщая деталей, чтобы найти более подробную информацию, DevOps часто приходится использовать другие инструменты, например AWS CLI.
  2. Нет возможности воспроизвести недоставленные сообщения, то есть система не в состоянии вернуть недоставленное сообщение в SQS, по крайней мере это не так легко. Можно использовать AWS CLI, чтобы вернуть их обратно, но опять же это делает устранение уже неприятных неполадок еще более неприятным.

Вышеперечисленные проблемы могут быть решены с помощью Slack и Lambda, как показано ниже.

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

Часть 1. Slack


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


Перейдите на api.slack.com, чтобы создать приложение, если у вас его ещё нет.

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



Создаём интерактивность, URL запроса это место, куда Slack посылает действия пользователя, конечная точка шлюза API функции Lambda.



Часть 2. Lambda


Я использую бессерверный фреймворк для управления лямбда-функциями. У нас будет две лямбда-функции:

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

Файл Serverless.yml показывает, как настраиваются эти две функции
service: slack-sqs-monitorframeworkVersion: "2.9.0"provider:  name: aws  versionFunctions: false  runtime: nodejs12.x  region: ap-southeast-2  iamRoleStatements:    - Effect: "Allow"      Action:        # You should only give least permissions to your functions.        - "sqs:*"      Resource:        - arn:aws:sqs:ap-southeast-2:xxxxxxxx:sqs.fifo # The original SQS arn        - arn:aws:sqs:ap-southeast-2:xxxxxxxx:deadletter.fifo # The dead letter queue arnplugins:  - serverless-webpack  - serverless-domain-managercustom:  customDomain:    rest:      domainName: labs.mianio.com      basePath: sqs-command      createRoute53Record: true      securityPolicy: tls_1_2  webpack:    webpackConfig: "webpack.config.js"    packager: "yarn"functions:  monitor:    handler: functions/monitor.handler    desciption: The function has the dead letter queue as the event, and forward the event to Slack    tags:      name: Monitor    environment:#     This is the webhook URL from the previous step      SLACK_ENDPOINT: https://hooks.slack.com/services/XXXXXXX/XXXXXX/XXXXXXXXX    events:      - sqs:#       Dead letter queue ARN          arn: arn:aws:sqs:ap-southeast-2:xxxxxxxx:deadletter.fifo  command:    handler: functions/command.handler    tags:      name: Command      desciption: The function handles Slack action and place the message back to the queue    environment:    # Credentials should be retrieved from Parameter Store       SLACK_SIGNING_SECRET: ${ssm:/deadletter/slack/signing-secret~true}      SLACK_OAUTH_TOKEN: ${ssm:/deadletter/slack/oauth-token~true}    events:      - http:          path: slack          method: post          cors: true


  • В функция мониторинга есть SLACK_ENDPOINT в качестве переменной окружения, которая будет использоваться для публикации в Slack.
  • Функция command находится за шлюзом API, конечная точка URL запроса для интерактивности Slack.

Функция декомпозирует события из очереди недоставленных сообщений и создаёт полезную нагрузку Slack для отправки. Смотрите api.slack.com/block-kit, чтобы узнать подробности о блоках для разработки в Slack.

Функция мониторинга
import middy from "@middy/core";import axios from "axios";import doNotWaitForEmptyEventLoop from "@middy/do-not-wait-for-empty-event-loop";export const monitor = async (event: any): Promise<any> => {  const records = event.Records;  await Promise.all(    records.map((record: any) => {      const messageGroupId = record?.attributes?.MessageGroupId;      const messageDeduplicationId = record?.attributes?.MessageDeduplicationId;      const approximateReceiveCount =        record?.attributes?.ApproximateReceiveCount;      return axios({        method: "post",        url: process.env.SLACK_ENDPOINT,        data: {          blocks: [            {              type: "section",              text: {                type: "mrkdwn",                text: `*Messsge ID*: ${record.messageId}`,              },            },            {              type: "section",              text: {                type: "mrkdwn",                text: `*Message Group Id*: ${messageGroupId}`,              },            },            {              type: "section",              text: {                type: "mrkdwn",                text: `*Message Deduplication Id*: ${messageDeduplicationId}`,              },            },            {              type: "section",              text: {                type: "mrkdwn",                text: `*Approximate Receive Count*: ${approximateReceiveCount}`,              },            },            {              type: "section",              text: {                type: "mrkdwn",                text: record.body,              },            },            {              type: "actions",              elements: [                {                  type: "button",                  style: "primary",                  text: {                    type: "plain_text",                    text: "Send back",                  },                  action_id: "sendback",                  value: record.body,                },              ],            },          ],        },        headers: {          "Content-type": "application/json; charset=utf-8",        },      });    })  );  return;};export const salesforceDeadLetterMonitor = middy(monitor).use(  doNotWaitForEmptyEventLoop());


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



Часть 3. Отправка обратно


Захватывающая деталь проекта это возможность отправить недоставленное сообщение обратно в SQS для переработки. При нажатии зеленой кнопки Send back Slack запускает POST-запрос на определённый ранее URL-адрес действия, то есть конечную точку шлюза API.

Функция command, которая отправляет недоставленное сообщение обратно в SQS
import { APIGatewayEvent } from "aws-lambda";import AWS from "aws-sdk";import qs from "qs";import axios from "axios";import middy from "@middy/core";import doNotWaitForEmptyEventLoop from "@middy/do-not-wait-for-empty-event-loop";import httpHeaderNormalizer from "@middy/http-header-normalizer";import httpEventNormalizer from "@middy/http-event-normalizer";import httpErrorHandler from "@middy/http-error-handler";import { slackVerifier } from "../../middlewares/slack/verify";const sqs = new AWS.SQS({ region: "ap-southeast-2" });const command = async (event: APIGatewayEvent) => {  if (!event.body) return { statusCode: 200 };  const requestBody: any = qs.parse(event.body);  const payload: any = JSON.parse(requestBody.payload);  let response;  const action = payload.actions[0];  if (action.action_id === "sendback") {    try {      const sqsPayload = payload.message.blocks.find(        (block: any) => block.block_id === "payload"      );      if (sqsPayload?.text?.text && action?.value) {        const payload = JSON.parse(sqsPayload.text.text);        await putBack(payload.jobName, payload.jobData, action.value);        response = {          payload: {            attachments: [              {                color: "good",                text: "Job was sent back",              },            ],            response_type: "in_channel",          },        };      }    } catch (error) {      console.error(error);    }  }  if (payload.response_url) {    await axios({      method: "post",      url: payload.response_url,      data: response.payload,      headers: {        "Content-type": "application/json; charset=utf-8",        Authorization: `Bearer ${process.env.SLACK_OAUTH_TOKEN}`,      },    });  } else if (response && !payload.response_url && response.payload) {    return {      body: JSON.stringify(response.payload),      statusCode: 200,    };  } else {    return {      statusCode: 200,    };  }};const putBack = async (name: string, data: any, workerUrl: string) => {  const params: any = {    MessageBody: JSON.stringify({ jobName: name, jobData: data }),    QueueUrl: workerUrl,  };  return new Promise((resolve: Function, reject: Function): any => {    sqs.sendMessage(params, (err: any, data: any): any => {      if (err) {        reject(err);      } else {        resolve(data);      }    });  });};export const handler = middy(command)  .use(doNotWaitForEmptyEventLoop())  .use(httpEventNormalizer())  .use(httpHeaderNormalizer())  .use(slackVerifier())  .use(httpErrorHandler());


Эта функция довольно проста:

  • Функция slackVerifier. Она проверяет, что POST-запрос направлен от Slack.

verifier.ts
import crypto from 'crypto';import qs from 'qs';export const slackVerifier = () => {  return {    before: async (handler: any) => {      const slackSignature =        handler.event.headers && handler.event.headers['x-slack-signature'];      const timestamp =        handler.event.headers &&        handler.event.headers['x-slack-request-timestamp'];      const time = Math.floor(new Date().getTime() / 1000);      if (Math.abs(time - timestamp) > 300) {        //  The request timestamp is more than five minutes from local time.        // It could be a replay attack, so let's ignore it.        return {          statusCode: 401,          body: JSON.stringify('Too old'),        };      }      const body = handler.event.body;      const sigBasestring = `v0:${timestamp}:${body}`;      const hash = crypto        .createHmac('sha256', process.env.SLACK_SIGNING_SECRET)        .update(sigBasestring, 'utf8')        .digest('hex');      const mySignature = `v0=${hash}`;      if (        !crypto.timingSafeEqual(          Buffer.from(mySignature, 'utf8'),          Buffer.from(slackSignature, 'utf8')        )      ) {        return {          statusCode: 401,          body: JSON.stringify('Invalid Signature'),        };      }      return;    },    onError: (handler: any) => {      return handler.callback(null, handler.error);    },  };};view raw

Переменная среды SLACKSIGNINGSECRET это переменная со страницы конфигурации Slack, которая вводится из определений бессерверной среды Serverless.yml.



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

SLACK_SIGNING_SECRET: ${ssm:/deadletter/slack/signing-secret~true}

  • Функция sendBack. Она получает полезную нагрузку от POST-запроса Slack и отправляет ее обратно в SQS.

Полезная нагрузка, которую Slack отправляет в command-функцию, выглядит так.



Она содержит response_url, который должен использоваться для отправки ответа обратно в Slack, чтобы подтвердить действие.



Код отправляет [полезную нагрузку ответа] обратно в Slack с помощью Bearer-токена.

await axios({    method: "post",    url: payload.response_url,    data: response.payload,    headers: {       "Content-type": "application/json; charset=utf-8",       Authorization: `Bearer ${process.env.SLACK_OAUTH_TOKEN}`    }});

SLACK_OAUTH_TOKEN вводится из переменных среды во время развёртывания. Вы можете получить её значение на странице конфигурирования Slack:



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

Задумали с нового года начать новую жизнь и подучиться? До конца этого года ещё можно ухватить курс с хорошей скидкой. А если использовать промокод HABR к скидке на баннере можно прибавлять еще 10%.

image



Подробнее..

Новости стартапов и венчура меньшинства для NASDAQ, Моргенштерн и Альфа-банк

08.12.2020 12:05:45 | Автор: admin

Привет Хабр! Раз в неделю я рассказываю о крупнейших событиях в отрасли в России и мире.Оригинальный роликна YouTube, ниже расшифровка.

Меньшинства для NASDAQ

Самое громкое событие, которое случилось это новая идея NASDAQ. Американская биржа отправила в американское правительство предложение о том, что теперь в каждой публичной компании, в совете директоров должна быть одна женщина и один представитель меньшинств. Это пока только предложение. Это пока не указ, не закон, не правило. Но тренды такие, что скорее всего в похожей форме оно будет принято. Если не сейчас, так через год. Тренды.

Если считать что примут именно то, что написали, то правила такие: в совете директоров должна быть одна женщина, не менее одной женщины, и в совете директоров должен быть один представитель меньшинства. Либо сексуального (любого), либо расового по американским понятием (либо латинос, либо афро-американец, либо индеец, либо гаваец, азиат и еще несколько групп). В принципе, эти меньшинства это уже 40% населения США, а если еще и сексуальные меньшинства, то одного человека из них достойного найти не является проблемой. Квота, но довольно мягкая квота.

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

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

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

Salesforce покупает Slack

Salesforce объявил о том, что покупает Slack. Сумма сделки примерно 28 млрд. долларов. Это не рекорд, но очень много. На бирже акции Salesforce немедленно упали. В капитализации компания потеряла примерно те же 28 млрд. долларов. То есть, биржа посчитала, что деньги не потрачены на приобретение крутой компании, а просто выброшены в пропасть. Было на 28 млрд. долларов больше, стало на 28 млрд. долларов меньше.

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

Вторая причина, она философская, а не арифметическая. Многим кажется, что две совсем разные компании, на совсем разных стадиях развития в сумме будут управляться хуже, чем одна. Когда вместо гениального основателя Slack появится безымянный менеджер из Salesforce, он все это изгадит, он компанию испортит, то есть зря покупаем, зря тратим деньги. Вот и надо продавать, вот и надо выходить. Ну и на все это истерика. Salesforce начал падать, поэтому надо продавать, поэтому падает еще сильнее, поэтому надо продавать. Вот такой тяжелый M&A.

Эволюция Альфа-Потока

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

Сбер выкупает Сбермаркет

Сбер объявил о том, что покупает мажоритарную долю Сбермаркета. Это сервис доставки продуктов из магазинов, он когда-то был независим и назвался Инстамарт, потом Сбер в него инвестировал и свою долю положил в совместное предприятие Mail.ru Group. А теперь он его выкупает из этого совместного предприятия и будет развивать в одиночку. Сумма, которая прозвучала, это 12 млрд. рублей. Часть была потрачена на выкуп, часть будет положена внутрь компании на будущее развитие. Стоимость компании не очень понятна: она и не звучала, и не вычисляется.

Что интересно, на той же конференции был озвучен оборот Сбермаркета это 3 млрд. рублей в месяц, то есть 36 в год, если просто умножить. В 2019 году 35 млрд. рублей это был весь рынок продажи онлайн продуктов питания. То есть, сейчас один сервис больше, чем весь рынок в прошлом году. Ну, спасибо, COVID.

Альфа-банк плюс Моргенштерн

Альфа-банк снял рекламу с Моргенштерн. Моргенштерн, если вы не знаете, это очень популярный среди молодежи российский рэпер. Ролик называется Клип за 10 лямов, типа Альфа-банк за него заплатил 10 млн. Может быть, кстати, и больше. И разыгрывает лотерею среди новых клиентов, каждый из них может выиграть 100 тыс. рублей. Длится ролик 4 минуты. Очень советую посмотреть, получите представление о современном искусстве. Ну это полезно. Ролик очень успешный, 8,5 млн. просмотров, дикое количество обсуждения везде, включая меня. Очень много критики о том, что вот, Альфа-банк был серьезным бизнесом для серьезных людей, а тут какой-то Моргенштерн советует подросткам попросить папу зарегистрировать кредитку. Ну позор, я с такими не буду на одной поляне. Но пришло такое время, что ютьюбер имеет охват, как Первый канал. И этим охватом надо пользоваться. Вот Альфа-банк и воспользовался. 8,5 млн. просмотром пришли сами по себе и, наверняка, огромное количество кредиток зарегистрировано благодаря этому. Успех. Не Альфа-банк виноват, мир виноват. Мир теперь такой.

Продолжение через неделю.

Подробнее..

Боль разработчика Никогда не давайте пользователям бесплатный тариф

15.03.2021 12:08:42 | Автор: admin


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

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

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

Британский разработчик Гвидо Зюйдхоф (Guido Zuidhof) настолько устал от таких пользователей, что запустил специальный сайт No Free Plan, на котором излил всю свою боль по этому поводу.

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

Цена бесплатного плана


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

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

Мы уже упомянули, что люди на самых дешёвых тарифах самые назойливые. Как думаете, от кого придёт больше запросов в службу поддержки: от десяти крупных корпоративных клиентов с подпиской за $100 в месяц или от тысячи пользователей, каждый из которых сидит на тарифе за $1 (включая школьников, студентов)? Очевидно, что в первой группе более высококвалифицированные специалисты и профессиональные IT-департаменты, которые задают меньше глупых вопросов.

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

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

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

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

В итоге бесплатные тарифы слишком дорого обходятся.

Ничего бесплатного не бывает


Понятно, откуда идёт заблуждение об эффективности бесплатных планов. Мы видим, что крупнейшие интернет-компании Google и Facebook не берут никакой платы за основной набор услуг. Хотя у Google есть много платных сервисов, но для широкой публики всё основное бесплатно. И на Google Drive, и на Gmail есть бесплатный лимит, который устраивает почти всех.

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

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


Сервис слежки за сотрудниками Hubstaff почти обанкротился с таким бесплатным тарифом

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

Какие альтернативы?


Пробный период


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

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

Пробный период эффективен даже без тёмных паттернов. Лимит можно выставить по времени (30 суток) или по объёму услуг (в гигабайтах трафика, минутах просмотра и т. д.).

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

Фильтрация пользователей


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

Вы выбираете действительно достойных, кто вам нравится.

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

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

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

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

Единовременный платёж


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

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

Это не полноценная подписка на сервис, а некий промежуточный вариант между вечным пробным периодом и нормальной платной подпиской. По аналогии с фримиум (freemium) его можно назвать чипиум (cheapium).

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

Автор концепции No Free Plan говорит, что на создание сайта его побудило чтение многочисленных дискуссий в сообществе Indie Hackers, где общаются основатели мелких онлайн-компаний и независимых бизнесов. Обычно это разработчики-одиночки, которые запускают платный сервис, SaaS, утилиту, игру или мобильное приложение или несколько таких сервисов, зарабатывая доход в качестве индивидуального предпринимателя.

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

Проблема опенсорсных мейнтейнеров


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

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

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

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

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

Преимущества бесплатных тарифов


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

В то же время мы видим, что многие компании предлагают данный вариант. Бесплатные вечные тарифы есть у Trello, Jira, Slack, Asana, Dropbox. Миллионы профессионалов используют эти сервисы бесплатно, а потом рекомендуют их в компаниях, куда приходят работать и те покупают платную подписку. Ссылки на Dropbox разлетаются по интернету как вирусный маркетинг. Таким образом, здесь расходы на бесплатные тарифы многократно окупаются.

Вопрос только в том, это стандарт индустрии или исключение из правил?

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

Это модель будущих единорогов.

Но правда и в том, что бесплатный план не всегда конвертируется в продажи. Если по неосторожности выложить в бесплатный тариф что-то полезное, то вы рискуете попасть на деньги, когда налетят миллионы юзеров из Юго-Восточной Азии. Тогда 95% вашего трафика будут генерировать бесплатные пользователи, которые никогда не заплатят ни цента, а скорее найдут другую бесплатную альтернативу.

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

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



На правах рекламы


Наша компания предлагает аренду VPS для совершенно любых проектов. Создайте собственный тарифный план в пару кликов, максимальная конфигурация позволит разместить практически любой проект 128 ядер CPU, 512 ГБ RAM, 4000 ГБ NVMe!

Подробнее..

Из песочницы Расчет временных ограничений для ПЛИС простым языком

16.07.2020 14:23:13 | Автор: admin
Здравствуйте. Эта статья написана для самых-самых новичков в мире ПЛИС. В ней я попытаюсь максимально просто и понятно рассказать что такое временные ограничения (timing constraints), накладываемые на проекты под ПЛИС.

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

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

Введение


Проведу короткое введение на языке простых понятий.

Для того, чтобы в ПЛИС что-то работало, в нее нужно загрузить (залить, зашить) файл прошивки, с помощью программатора и утилиты прошивания. Файл прошивки является продуктом компиляции САПРом некоторого проекта папки с файлами, каждый из которых описывает какую-либо сторону проекта. В простых случаях пользователь описывает сам лишь файлы с исходным кодом, файл с распиновкой и файл с временными ограничениями. Из этой триады только файл временных ограничений является формально необязательной частью проекта. Собственно, если ваш проект не содержит частот выше 30-50 МГц, то вполне вероятно, что этот файл и не пригодится. Однако, если проект содержит высокие тактовые частоты и не укомплектован файлом временных ограничений, то вы не узнаете о том, что ваш проект не будет успевать обрабатывать данные.

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

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

Таким образом, синхронные схемы состоят из межрегистровых передач данных (RTL, register transfer logic, r2r transfer). И ключевой аспект временного анализа состоит в измерении слэка (slack). Это слово буквально переводится как провисание, запас по времени, но в русскоязычной среде чаще употребляют кальку с английского слэк. В рамках межрегистровой передачи речь идет о слэках предустановки (Setup) и слэках удержания (Hold).

Межрегистровая передача


Межрегистровая передача (рис. 1) рассматривается как система из двух последовательно включенных регистров, которые работают на синхронных в общем случае клоках. В простом случае на одном клоке. Один регистр является источником (source), а другой является получателем данных (destination). Между ними на пути данных находится некая произвольно определенная пользователем комбинационная логика. Она несинхронная так как не имеет в себе элементов памяти с синхронизирующим сигналом, наподобие регистров. Эта логика и есть то поведение, те логические операции, которые пользователь описывает своим кодом. Регистры это те однобитные переменные, которым пользователь дает имена в коде и оперирует по отдельности, либо объединяя в вектора и массивы.

image
Рис. 1. Схема передачи данных от регистра к регистру

Существует два понятия, связанные с приемом данных регистром-получателем: Setup time и Hold time. Они очерчивают собой диапазон времени, в течение которого сигнал на входе получателя должен быть стабильным и актуальным. Стабильным по существу означает, что его напряжение должно быть очень близко к одному из двух логических состояний 0 или 1, а не болтаться между ними с вероятностью перепутывания. Актуальным означает, что этот бит информации должен по смыслу относиться к этому такту клока, который его захватит, а не запоздавший бит от предыдущего такта.

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

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

То есть данные на входе получателя должны быть стабильными и актуальными не только в момент прихода фронта клока, но и на протяжении некоторого защитного интервала времени вокруг него (рис.2), длительностью не менее (Setup_time + Hold_time).

image
Рис. 2. Смысл Setup Time и Hold time как защитного интервала

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

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

Слэков, соответственно, тоже два Setup Slack и Hold Slack (рис.3).

Setup Slack характеризует собой запас по времени, который имеют данные от момента стабилизации до начала интервала Setup time.

Hold Slack характеризует собой запас по времени, который имеют данные от окончания интервала Hold time до потери данными стабильности.

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

image
Рис. 3. Положение слэков во времени

Расчет слэков


Теперь перейдем к тому, как эти слэки рассчитываются. Начнем с Setup Slack.
Рассмотрим схему передачи данных на рис. 4.

image
Рис. 4. Схема передачи данных

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

Фронт запуска (Launch Edge) это фронт клока, пришедший на вход регистра-источника и запустивший процесс передачи данных.

Фронт захвата (Latch Edge) это фронт клока, следующий после Launch Edge через один период клока, который приходит на регистр-получатель и заставляет его захватить данные на входе.

Момент прибытия данных (Data Arrival Time) определяется как время фактического прибытия данных на регистр-получатель.

Момент ожидания данных (Data Required Time) определяется как время, за которое данные должны дойти до получателя до наступления времени предустановки на регистре-получателе.

Момент прибытия клока (Clock Arrival Time) определяется как время прохождения фронта захвата от тактового входа всей схемы к тактовому входу получателя.
Под тактовым входом всей системы обычно понимается выход глобального клокового буфера или выход PLL, то есть единая точка, из которой тактовый сигнал расходится до всех регистров своего тактового домена. В самом примитивном случае это ножка ПЛИС, к которой подведен тактовый генератор.

Как соотносится момент прибытия данных с фронтом запуска?

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

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

$Data Arrival Time = Launch Edge + \max t_{CLK} + t_{CO} + \max t_D $


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

Слагаемое $\max t_{CLK}$ это максимальное время, за которое фронт запуска дойдет до тактового входа источника. Анализатор как правило не оценивает точно это время, он просто берет диапазон времени от точно не менее чем до точно не более чем и в данную формулу подставляет верхнюю границу точно не более чем. Эта величина не зависит от пользователя. Компилятор сам решает где расположить регистр на кристалле и сам учитывает время прохождения клока до него. Сеть соединений, по которым тактовый сигнал расходится к регистрам, спроектирована таким образом, чтобы тактовый сигнал доходил до любого регистра практически за одинаковое время. Поэтому на самом деле разница между $\max t_{CLK}$ и $\min t_{CLK}$ крайне мала, но все же учитывается.

Слагаемое $t_{CO}$ это время срабатывания регистра (clock-to-output time), которое регистр тратит на то, чтобы увидев фронт на тактовом входе поменять данные на своем выходе. Анализатор считает эту величину равной для всех регистров на кристалле. Эта величина не зависит от пользователя.

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

Момент прибытия клока на получатель рассчитывается проще:

$Clock Arrival Time = Latch Edge + \min t_{CLK}' $


Это момент, в который фронт захвата дойдет до тактового входа регистра-получателя.
Слагаемое $\min t_{CLK}'$ это минимальное время, за которое фронт захвата дойдет до тактового входа получателя, то есть по аналогии с предыдущей формулой это время точно не менее чем. Черточка в данном случае означает что речь идет о тактовом входе получателя, а не источника.

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

$Data Required Time = Clock Arrival Time t_{SU} CSU$


Слагаемое $t_{SU}$ это уже известное нам Setup time, которое считается одинаковым для каждого регистра на кристалле. Это время не зависит от пользователя.

Слагаемое $CSU$ это Clock Setup Uncertainty, неопределенность времени предустановки. Как и любая другая неопределенность во временном анализе CSU не является физическим процессом, а является способом отразить в анализе влияние джиттера или просто способом ввести в анализ защитный интервал времени на всякий случай. Простыми словами, это запас времени на учтение сложноописываемых процессов.

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

$Clock Setup Slack = Data Required Time Data Arrival Time $


Теперь раскроем эти слагаемые и немного переставим местами:

$Clock Setup Slack = Latch Edge + \min t_{CLK}' t_{SU} CSU -$

$ (Launch Edge + \max t_{CLK} + t_{CO} + \max t_D) $

$Clock Setup Slack = Latch Edge-Launch Edge-\max t_D $

$-CSU+( \min t_{CLK}'-\max t_{CLK}) t_{SU} t_{CO}$

$=Period-\max t_D CSU+ \min t_{CS} t_{SU} t_{CO}$


Здесь появились новые слагаемые.

Про период понятно, это период тактовой частоты, т.е. время между Launch Edge и Latch Edge.
Слагаемое $\min t_{CS}$ это растекание клока (clock skew) минимальная величина разброса времени прихода одного фронта клока от тактового входа системы до разные синхронные регистры. Минимальное растекание клока определяется как разница между наименьшей задержкой клока к получателю и наибольшей задержкой клока к источнику $\min t_{CS} = \min t_{CLK}' - \max t_{CLK}$. Анализатор не делает разницы в оценке этого времени для разных регистров на кристалле.

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

На рисунке 5 показано как формулу слэка можно представить графически:

image
Рис. 5. Графическое представление выражения Setup Slack

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

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

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

$Clock Hold Slack = Data Arrival Time Data Required Time$


Вот только эти слагаемые теперь рассматриваются с другой стороны.

$Data Arrival Time = Launch Edge + \min t_{CLK} + t_{CO} + \min t_D $


Теперь здесь рассматривается самый быстрый вариант прохода данных и там, где было max стало min.

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

$Clock Arrival Time = Latch Edge + \max t_{CLK}' $


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

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

$Data Required Time = Clock Arrival Time + t_H + CHU$


Слагаемое $t_H$ это уже известное нам Hold time, время удержания. Оно считается одинаковым для каждого регистра на кристалле и не зависит от пользователя.
Слагаемое $CHU$ это Clock Hold Uncertainty, неопределенность времени удержания. Оно несет в общем тот же смысл, что и CSU, да и как правило берётся равным ему.

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

$Clock Hold Slack = \min t_D-\max t_{CS} + t_{CO} -t_H - CHU$

$\max t_{CS} = \max t_{CLK}'-\min t_{CLK}$


Ура, мы узнали как рассчитываются слэки. Как использовать эти знания?
Давайте посмотрим на выражения слэков еще раз:

$Clock Setup Slack=Period-\max t_D CSU+ \min t_{CS} t_{SU} t_{CO}$

$Clock Hold Slack = \min t_D -\max t_{CS} + t_{CO} -t_H - CHU$


Если какие-то слэки проекта стали отрицательными, то поменять их мы можем поменяв их слагаемые.

Мы видим слагаемые, которые не зависят от пользователя, а зависят только от технологии кристалла. Это $t_{SU}, t_H, t_{CS}, t_{CO}$.
Мы видим слагаемые CSU и CHU, которые анализатор как правило берет равными параметру CU Clock Uncertainty, нестабильность тактовой частоты. Этот параметр вообще говоря невелик, десятки пикосекунд. Он указывается пользователем в файле ограничений. А пользователь его в свою очередь берет из спецификации на тактовый генератор. Считается что клоковый буфер или внутренняя PLL ПЛИС, которые принимают внешний клок от генератора и преобразуют во внутренний клок на тактовый вход системы, сохраняют величину CU той же, что получена от генератора. Если CU не указать, то анализатор выставит ей некоторое значение по умолчанию, например Quartus ставит 20 пс. В общем случае это слагаемое говорит нам о том, что лучше использовать для тактирования высокостабильные генераторы с величиной нестабильности порядка 20-60 пс.

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

И, наконец, слагаемое $t_D$ характеризует по сути эффективность написанного кода. Отсюда следует и основной способ решения проблем со слэками переписать как следует. Большое время $t_D$ появляется у слишком сложных аппаратных конструкции, требующих слишком много комбинационной логики. Если такие сложные конструкции появились у вас в проекте, то классический способ решения проблемы разбить одну сложную r2r передачу на несколько простых, вставив еще 1-2 регистра в последовательность операций. При этом вырастет задержка в тактах на выполнение операции, но зато увеличится быстродействие операции. Например, сложение за один такт нескольких векторов это не очень хорошая идея. Складывать несколько векторов лучше по очереди, с промежуточными суммами. Некоторые сложные конструкции бывает невозможно разбить на конвейер из нескольких простых тогда такую логику нужно переписывать как-нибудь принципиально иначе.

Дополнение


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

Анализатор группирует слагаемые иначе, исходя из своих машинных резонов.
Он оперирует понятиями Clock Setup Relationship (SR) и
Clock Hold Relationship (HR) которые можно перевести как соотношение времени между инициирующим фронтами для предустановки и удержания соответственно.

$SR = Setup Latch Edge - Setup Launch Edge-CSU$

$HR = Hold Latch Edge - Hold Launch Edge-CHU$


На рисунке 6 можно увидеть о каких фронтах идет речь:

image
Рис. 6. Фронты, используемые в расчетах слэков.

Можно сразу преобразовать полученные выражения в более понятный вид:

$SR = Period-CSU$

$HR = CHU$


Наибольшее межрегистровое время (Largest r2r Required) это максимальное время, имеющееся для того, чтобы данные дошли к получателю до начала интервала предустановки:

$Largest\ r2r\ Required = SR + \min t_{CS} t_{CO} t_{SU}$


Самая длинная межрегистровая задержка (longest r2r Delay) это время, необходимое для передачи данных из исходного регистра в регистр назначения по самому длинному пути:

$Longest\ r2r\ Delay = \max t_D$


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

$ClockSetupSlack = Largest\ r2r\ Required Longest\ r2r\ Delay$


Раскрытие слагаемых этой формулы даст нам уже знакомое представление слэка предустановки:

$Clock Setup Slack=Period-\max t_D CSU+ \min t_{CS} t_{SU} t_{CO}$


Теперь про слэк удержания. Наименьшее межрегистровое требование (smallest r2r Requirement) это время, необходимое для удержания данных на входе регистра назначения:

$Smallest\ r2r\ Required = HR + \max t_{CS} \min t_{CO} + t_H$


Кратчайшая межрегистровая задержка:

$Shortest\ r2r\ Delay = t_D$


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

$ClockHoldSlack = Shortest\ r2r\ Delay Smallest\ r2r\ Required$


При раскрытии слагаемых выражение также принимает уже знакомый вид:

$Clock Hold Slack = \min t_D -\max t_{CS} + t_{CO} -t_H - CHU$


Теперь вы знаете что такое слэк, а значит можете самостоятельно изучать отчеты анализатора временных ограничений и отлаживать быстродействие своего проекта.
Подробнее..
Категории: Fpga , Slack , Constraints , Altera , Rtl

Использование Slack в обучении

10.01.2021 12:14:24 | Автор: admin

Не так давно я написал несколько статей с примерами про использование GitHub в обучении (часть 0, часть I, часть II, часть III) - теперь же хочу рассказать про использовании Slack в обучении.

Если кто вдруг не знает, то Slack - это корпоративный мессенджер с поддержкой каналов, видеовстреч, интеграций с другими сервисами. Подробнее можно почитать в Wikipedia - https://ru.wikipedia.org/wiki/Slack или на оф. сайте - https://slack.com/intl/en-ru/

Но прежде хочу рассказать о минусах.

Чем мне не нравится Slack

  • Платная подписка. По началу думал что бесплатного тарифа хватит (подробнее о тарифах - https://app.slack.com/plans/).

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

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


      Знаю, что есть тариф для учебных заведений, но я так и не смог разобраться что нужно, чтобы его оформить. Подробнее о тарифе для учебных заведений - https://slack.com/intl/en-ru/help/articles/206646877-Apply-for-the-Slack-for-Education-discount

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

Почему я стал использовать Slack в обучении?

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

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

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

  • канал#generalнеобходим для сообщений учебной части и руководства

  • канал#randomдля болтания

  • в канале#educationбудут ссылки на разные конференции, вебинары и т.п. вещи

  • в канале#edu-materials-progбудут учебные материалы

  • каналы #team-<номер группы> - это закрытые каналы групп студентов

Прекрасно понимаю, что не самая лучшая система каналов, но пока остановился на такой.

  • Личные сообщения никто не отменял.

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

  • Можно фиксировать/закреплять необходимые сообщения. На скриншоте к предыдущему пункту справа видны как раз закреплённые сообщения.

  • Удобно вести беседы на определённые темы. Есть система комментариев к сообщениям.

  • Можно оставлять не только комментарии к сообщениям, но и своё отношение в виде эмодзи/смайликов

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

Как всегда жду критики и советов.

Подробнее..

Категории

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

  • Имя: Макс
    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