2017-03-18

О кубиках

Лего бывает разное. Дупло (Duplo, полагаю, с ударением на первый слог) — для самых маленьких. Просто Лего, в виде кучи самых разнообразных серий и наборов, — для детей всех прочих возрастов. Lego Technic — для взрослых.
We love Lego
Год назад мне подарили здоровенный 42025 Cargo Plane. Боже, как быстро они обновляют ассортимент. Самолёт уже не продаётся. Кажется я понимаю маньяков, которые коллекционируют Лего. Ведь большинство моделей продаются лишь год-два. А потом становятся археологией и мечтой коллекционеров.
Собирать самолёт было интересно. Кажется, я потратил почти две недели, вечерами. Офигенская маленькая пластмассовая раздаточная коробка с почти настоящими муфтами включения. Куча рычажков, которые что-то включают, выключают, двигают.
Но играбельность у самолёта оказалась почти никакой. Большой. Тяжёлый. Несколько хрупкий. Непонятно, за что его держать, чтобы палец не попал в шестерёнку, и чтобы что-нибудь не отломить. Нельзя просто так включить пропеллеры и "полетать" по квартире. Игрушкоподъемность и кукловместимость тоже никакие.
Cargo Plane
Вспоминая детство и металлический конструктор (который иностранцы называют Meccano), пытался сделать машинку с поворотными колёсами. Не слишком успешно.
В железном конструкторе всё просто. Вот планка с дырками. Вот болт и гайка. Вот уголок, если надо соединить планки под углом. Для длинных жёстких конструкций есть длинные металлические профили.
Всё более-менее просто в корейском My Robot Time. Там пластмассовые детали без проблем соединяются под прямым углом. Наоборот, чтобы соединить встык или внахлёст, нужны дополнительные детальки.
А вот в Lego Technic всё очень сложно. Это не обычные кирпичики Lego, которые просто ставишь один на другой. Это тоже планки с дырками. И пластмассовые шканты для их скрепления (порылся в Википедии в поисках правильного термина). Только вот пластмассовые планки не условно нулевой толщины, как в металлических конструкторах, а вполне себе единичной толщины, квадратные в поперечном сечении.
Возникает страшная проблема при переходе с одного направления дырок в планках (и направления протяжённости самих планок) на другое. Простых уголков почти нет. Нужны специальные детальки. Которые тоже занимают несколько единиц объема. И соединяемые детали получаются смещены на единицу. Это вам не торжество кубизма, как в Minecraft или классическом Lego.
Моё пространственное воображение пока только-только подстраивается под это безобразие. Удивительно, но помогают последние страницы инструкций, где нарисованы все детали, имеющиеся в наличии, и их количество.
Steering machine
А вот сейчас подарили SBrick. Эти ребята собрали деньги на Kickstarter в 2014. Успешно сделали и продают вот что...
У Lego есть моторчики, есть лампочки, есть инфракрасный приёмник и пультик дистанционного управления. Правда, в настоящее время с пультиком продаётся только странный гусеничный вездеход. Вся эта электрика подаётся под маркой Power Functions.
Но штатные способы управления этой электрикой: механические выключатели, полтора странных инфракрасных пультика, одна модель (но в двух не очень совместимых версиях) инфракрасного приёмника. Пультик и приёмник имеют только два канала управления. И те дискретные. Для машинки, чтобы рулить влево-вправо, да ехать вперёд-назад, может, и достаточно. Но такую машинку, с пультиком, нужно ещё найти и купить.
А вот SBrick — это четыре канала пропорционального управления. Через Bluetooth. С телефона или планшета, через соответствующее приложение. Можно рулить сразу несколькими SBrick. Рай для любителей электрификации Lego моделек.
SBrick and motors
А в конце 2016 те же ребята завершили ещё одну кампанию на Kickstarter. Теперь для SBrick Plus. Его уже можно купить. Он совместим не только с Power Functions, но и с образовательным набором от Lego под названием WeDo. А вскоре, видимо, через переходник, обещают поддержку и Mindstorms.
И SBrick, и SBrick Plus можно программировать как минимум через Scratch. Впрочем, я пока до этого не дошёл.
Имея два моторчика и один SBrick, я пока что взялся за, как внезапно оказалось, мечту всего детства — радиоуправляемую машинку. С одной стороны у нас есть SBrick и его четыре порта управления (задействуем только два). С другой стороны — приложение под Android и то, что называется Profile Designer.
Legokhod
Ребята развернули целую инфраструктуру. Чтобы управлять моделькой, в приложении нужно загрузить профиль. Либо один из нескольких доступных публично. Либо нарисовать свой профиль, в онлайн редакторе, привязанный к вашему аккаунту SBrick. К этому же аккаунту привязаны и ваши модельки.
Профиль — это картинка, которая будет видна на экране телефона/планшета при управлении моделькой. Рычажки, переключатели, фоновая картинка. Когда профиль подключается к модели, каждые рычажки и переключатели привязываются к каналам SBrick. Есть рычажки с одной осью. Есть джойстики с двумя осями. Есть кнопки вкл/выкл. Есть кнопки, которые при одном нажатии передают какую-то последовательность вкл/выкл. Можно задействовать акселерометры телефона, и, например, рулить моделькой, наклоняя телефон, как в автогонках. Можно применять к положению рычажков какие-то простейшие преобразования. Но нельзя смешивать несколько каналов, для этого придётся попрограммировать. В общем, получается уникальная панель управления для вашей уникальной модели.
Profile example
Имеем вполне успешный кикстартовый проект, который может хорошо порадовать родителей и детей, владельцев Lego Technic моделей.
У меня пока что получился лишь уродливый и неуклюжий легоход. Буду улучшать :)

2017-03-05

О ClickHouse

А у нас на продакшине ClickHouse. А у вас? Вот так вот получилось. Ну и продолжается, само собой.
ClickHouse logo
Задача была такая. Есть юзеры. Они генерируют события. Не буду распространяться, какие именно события, дабы не нарушать NDA. По каждому событию сейчас у нас получается семнадцать параметров. Причём два из них — массивы строковых значений. Событий генерируется довольно много: от половины до даже пяти миллионов за сутки. В пиковые моменты случается до ста пятидесяти событий в секунду. И эти числа ещё собираются расти по мере появления новых пользователей.
По этим событиям нужно строить, казалось бы, обычные аналитические штуки. Самые популярные (часто встречающиеся) значения параметров (в том числе из массивов) за последние двадцать четыре часа, семь и тридцать дней. Графики изменения параметров за те же периоды. Просто счётчики событий. Всё это, выбранное и сгруппированное по значениям других параметров. Что-то порядка десятка подобных отчётиков, чтобы ещё обновлялись по возможности в реальном времени. И безо всякой гарантии, что новые отчёты внезапно не понадобятся.
Но кроме этого, все эти события, все семнадцать параметров, надо изображать как они есть, в виде журнала, и выгружать в CSV. Заостряю на этом внимание, потому что это требование несколько противоречит предыдущим. Как оказалось, одно дело — это составлять агрегированные метрики по исходным данным на лету. А совсем другое дело — хранить исходные логи, как они есть.
Подход номер раз. Я сказал: это ж timeseries данные. Давайте возьмём InfluxDB и будем складывать всё туда. Тем более, что Инфлюксина вроде стала более-менее стабильной (только только вышла версия 1.0), и обросла солидной инфраструктурой.
Впрочем, инфраструктурой мы так и не воспользовались. А в InfluxDB совершенно разочаровались. Да, она может хранить всё, что нам нужно. Да, она может делать нужные нам запросы. Теоретически. Но на практике не всё так просто.
InfluxDB не умеет делать достаточно сложные аналитические запросы. Напрямую, одним запросом. Нужно собирать промежуточные агрегации и строить результат в несколько шагов. Либо во временных таблицах, что медленно и требует памяти и диска. Либо формировать агрегации на лету с помощью так называемых Continuous Queries, или даже с помощью сложного и мощного Kapacitor, что требует очень хорошего знания самой InfluxDB и её языка запросов, хорошего понимания того, что нужно получить и посредством каких преобразований, ну и снова требует памяти, диска и CPU. Может, был бы у нас аналитик данных, он бы побаловался. Но мы, простые смертные программисты, задачу многошагового агрегирования не осилили.
А ещё оказалось, что InfluxDB имеет своё понимание временных промежутков, от которого никуда не деться. За текущие и прошлые сутки, недели, месяцы проагрегировать — пожалуйста. За последние (от текущего момента, а не от полуночи) двадцать четыре часа — никак.
InfluxDB не даёт никакой гарантии касательно того, когда только что вставленные данные появятся в результатах запросов. Eventual consistency во всей красе. Жутко неприятно только, что даже безо всякой нагрузки этот интервал может составлять минуты.
Ну а добила нас InfluxDB непомерными и непредсказуемыми требованиями к оперативной памяти. Даже на небольших данных, в самом начале проекта, сложные аналитические запросы приводили к выеданию гигабайт ОЗУ. Выглядело это так. Всё работет, данные вставляются. Открываем страничку, где рисуются наши отчёты. И парочка самых тяжёлых отчётов зависает на минуты, а потом валится с ошибкой. InfluxDB в этот момент съедает всю ОЗУ, потом весь свап, и потом запрос таки падает от нехватки памяти. Спасибо, что вся Инфлюксина не падает, а только поток тяжёлого запроса. Мы подумали, что платить за сервера с тридцатью двумя или шестидесятью четырьмя гигабайтами ОЗУ для обработки жалкого миллиона (на тот момент) записей — это слишком жирно. Тем более, что непонятно, хватит ли и этих гигабайт.
InfluxDB logo
Подход номер два. PostgreSQL. Попробовали, проверили, потестировали. Оказалось, что старый добрый реляционный SQL прекрасно умеет все те аналитические запросы, которые оказались не по зубам InfluxDB. Прямо поверх исходной таблицы с журналом событий. SUM, GROUP BY, ORDER BY, LIMIT. Джойны таблицы с самой собой, точнее по массивам внутри данных. Чтобы не париться со схемой, юзали jsonb. Индексы правильные построили. Всё прекрасно работало и память не жрало. Postgres — прекрасен.
Так всё жило несколько месяцев. А пользователей становилось всё больше. И запросы стали выполняться всё медленнее. Уже минутами. Окончательный ахтунг случился, когда запросы стали падать из-за нехватки места на диске. Оказывается, Postgres создаёт временные файлы, что вполне ожидаемо. Но что эти файлы вполне могут быть размером больше десяти гигабайт — внезапно.
На популярных VDS дисковое пространство денег стоит. А сто сорок миллионов записей стали занимать сто двадцать гигабайт диска. Многовато. Как освободить место? Ну давайте сдампим старые данные в файлики, и удалим их из Postgres. Ок. Как положено, сделаем vacuum analyze. Ах, обычный вакум не возвращает дисковое пространство операционной системе, он просто помечает место в уже используемых страницах как свободное. Вставлять ещё без дополнительного пожирания диска можно, но места не добавилось. Может, надо сделать vacuum full? Чорт, ему нужно свободное место, чтобы полностью перекопировать таблицу. А места уже нет. Пат.
Почему запросы стали медленными? Потому что транзации. По идее, даже банальному count(*) должно быть достаточно лишь индекса, чтобы дать ответ. Но у нас ещё и интенсивная вставка происходит. А значит, помимо индекса, надо сверяться с visibility map для каждой строки, чтобы убедиться, что данная строка действительно видна в данной транзации. Получается слишком много IO для выполнения запроса. Получается слишком медленно.
PostgreSQL logo
Проблемы с Postgres, спасибо ему, начались не внезапно. Мы начали смотреть по сторонам, чтобы понять, что делать. Думали о Cassandra. И тут случился ClickHouse.
У ClickHouse замечательная документация. Там всё написано, как начать и продолжить.
Идея такая, в каком-то смысле противоположная текущему подходу к timeseries, что применяется в том же InfluxDB. Во-первых, мы не знаем, какого рода отчёты нам понадобятся. Во-вторых, если отчётов потребуется слишком много, придётся хранить много агрегированных данных, которые многократно дублируют исходные данные. Их объём может быть значительным. Так почему бы не вспомнить тот же SQL и старый добрый OLAP? Пусть у нас будут только исходные данные, но в виде, удобном для разных хитрых манипуляций. А если мы сможем хранить данные достаточно компактно, чтобы уменьшить IO, то сложные запросы ещё и выполняться будут достаточно быстро.
Вот такой он, ClickHouse. Это колоночная база данных. Колонки данных физически хранятся в разных файлах. Для аналитики это важно, так как в одном запросе, как правило, используются далеко не все колонки. Вот которые не используются, можно не читать. К тому же колонку можно очень хорошо сжать, если там будут повторяющиеся значения. И Кликхауз жмёт.
В самом популярном движке таблиц Кликхауса MergeTree данные упорядочены по дате и по первичному ключу. И объединяются в кусочки по ключу. Каждый кусочек представлен на диске каталогом, в котором лежат файлики. По паре файликов (данные и индекс?) на каждую колонку. Когда мы вставляем, создаются новые кусочки. А потом они в фоне объединяются.
Эти файлы на диске очень не похожи на то, что обычно бывает в базах данных. Какой-то блокнот с зипом. Надеюсь, эта конструкция неприхотлива к потере отдельных файлов.
Сжимает Кликхаус потрясающе. То, что в Постгресе занимало сто двадцать гигабайт, в виде загзипованного csv дампа занимало пять гигабайт, будучи вставлено в Кликхаус, заняло два(!) гигабайта на диске.
Зато нет транзакций, невозможны апдейты, а удалять данные можно только партициями, т.е. кусками за целый месяц. Ну и фиг со всем этим, не для того держим.
Запросы, которые в Постгресе затягивались на минуты, в Кликхаусе выполняются за пару секунд. На том же объеме данных. Он ещё гордо показывает какое чудовищное количество гигабайт он читает при выполнении запроса. Эквивалетный объем несжатых данных, конечно. Маркетинг.
Да, на простеньких запросах Постгрес быстрее, он может ответить за миллисекунды. Кликхаус редко отвечает быстрее сотен миллисекунд.
Есть репликация. Для этого нужен ZooKeeper. Раз уж разводим зоопарк разных БД, нужно за ним присматривать. Для репликации имеется отдельное семейство движков БД, например, ReplicatedMergeTree. Получается, что реплицируются отдельные таблицы. Движок через ZooKeeper координирует куски данных, имеющиеся в каждой реплике. И реплики обмениваются пропущенными кусками. Соответственно, получается мультимастер. Писать можно в любую реплику, данные появятся в обеих. Получается, что тут на репликах и запись масштабируется.
Есть шардинг. Тут тоже нужен ZooKeeper. Для шардинга есть отдельный движок таблиц под названием Distributed. Он сам не хранит данные, а берёт их из других таблиц-шардов, доступ к которым осуществляется через этот Distributed. Это могут быть любые таблицы, в том числе и реплицируемые. С ключом шардинга ClickHouse обращается весьма вольно. Для чтения он вообще не нужен, потому что запрос всегда будет направлен во все шарды, а финальный результат будет собран на хосте, где живёт Distributed таблица. Для записи ключ нужен только чтобы обеспечить равномерное распределение данных между шардами. Или не обязательно равномерное, можно указать разные веса разным шардам. А можно вообще шардить вручную, напрямую записывая в скрытые под Distributed таблицы. Кликхаус всё стерпит.
Мы до репликации и шардинга ещё не дошли, то технически к этому готовы. Хотя, такими темпами до ста двадцати гигабайт данных в Кликхаусе ещё не скоро доберёмся.
Подключаться к Кликхаусу можно как минимум двумя способами. Родной консольный клиент clickhouse-client использует некий бинарный протокол. Зато, похоже, только он умеет показывать симпатичный прогресс бар во время выполнения запроса. Кстати, этот консольный клиент — самая весёлая консоль, что я видел, порадует вас смайликами. Ещё есть HTTP протокол, можно просто курлить. И есть JDBC драйвер, который, впрочем, почему-то ходит на сервер тоже через HTTP.
ClickHouse client
Случилось с ClickHouse и штуки три (пока три) неприятных момента. Документацию читать внимательно надо.
Во-первых, у JDBC драйвера стоит очень маленький таймаут на подключение к серверу. Пятьдесят миллисекунд. Этого, конечно, хватит для локальной сети или даже одного датацентра. Но подключиться к серверу на другом берегу Атлантического океана уже не получится. Лечится указанием property при создании DataSource или Connection по имени connection_timeout с более вменяемым значением.
Во-вторых, Кликхаус очень не любит одиночные инсерты. Ему подавай батчи. Официальная документация даже говорит, что одиночные инсерты, пожалуйста, не чаще одного в секунду на одном сервере. Оно понятно, почему. MergeTree нужны достаточно большие куски, чтобы заняться их слиянием. Постоянно сливать отдельные строки выходит слишком дорого. Мы сдуру попробовали вставлять наши события по одному. Кликхаус нещадно насиловал диск на запись, и упорно задерживал инсерты. Т.е. в логе писал соответствующее сообщение. И вставляемые данные появлялись в запросах с дикой задержкой аж в часы. В отличие от InfluxDB эта задержка прямо коррелирует с нагрузкой на сервер.
Пришлось расчехлить знание многопоточного программирования на Java, и собирать батчи на нашем клиенте. Асинхронно, по тысяче строк за раз, но не реже раза в тридцать секунд, и не забыть записать всё накопленное при шатдауне системы. Почему эта буферизация не сделана в JDBC драйвере? Или даже на сервере? Та же Кассандра спокойно пишет данные в журнал, и лишь потом с ними разбирается. Похоже, надо смириться с тем, что при вставке в Кликхаус данные могут быть потеряны. Ну а пока с батчами всё ок. Один сервер держит нашу нагрузку.
В-третьих, Кликхаус не очень приятно работает с датами. Слишком легко облажаться. Дату-время он хранит в виде юниксового таймстампа. Прощай миллисекунды. Здравствуй, проблема 2038 года. Но также ClickHouse готов принимать в качестве даты и времени строки в формате ISO 8601. При этом никаких таймзон и даже смещений от UTC не предусмотрено. А для конвертации он использует в лучшем случае таймзону, прописанную в конфиге, а в худшем случае, системную таймзону, подхваченную при старте сервера.
В консольке Кликхауса это не так страшно, ибо Кликхаус очень строго относится к типам данных (даже строже, чем Постгрес), поэтому, чтобы использовать строку как дату, нужно явно использовать функции toDate() и toDateTime() (и даже Date автоматически из DateTime не выводится). И вроде как консолька по умолчанию использует таймзону сервера. А вот JDBC драйвер спокойно съедает String там, где ожидается DateTime. И поди пойми, в какой момент происходит конвертация, и в каком часовом поясе, клиента или сервера. Надо переходить на long.
При переходе из Postgres пришлось распотрошить jsonb в отдельные колонки. Но у нас же колоночная база. Если потом понадобится добавить ещё одну колонку, это же будет очень дёшево, правда?

2017-02-19

Об UI

Забыли про механические компьютеры. Они могли только числа молотить. Поэтому весь интерфейс у них — это ввести числа колёсиками, да покрутить ручку, чтобы результат получить. Весьма неплохой интерфейс для этой задачи, кстати.
Арифмометр
У первых электрических (но ещё не электронных) компьютеров интерфейс был прост. Надо было нужные кабели повтыкать в нужные дырочки. Ну примерно как на первых телефонных станциях. «Барышня, соедините меня...» Барышня и соединяет. Буквально, проводом.
Коммутатор
Перетыкание проводов быстренько заменили переключателями: тумблерами или поворотными. Ну и лампочки, конечно, как устроство вывода.
Потом наступила эпоха перфокарт и перфолент. Машина их ела и мигала лампочками в ответ. Сразу стало понятно, что дырявить эти дырочки вручную — гиблое дело (впрочем, наверняка всякое бывало). А печатные машинки, да и телеграф, уже тогда были. Ну и были телетайпы. Эти вполне себе клавиатуры тут же приспособили сначала для пробивания перфолент/карт, а потом и для непосредственного ввода данных уже в электронные компьютеры.
Те же телетайпы были ещё и принтерами. И компьютеры научились на них печатать. Вводим буковки, получаем буковки. Уже хорошо.
Телетайп
Что ещё осталось? Да, чтобы не тратить тонны бумаги, придумали подключать к компьютеру ЭЛТ экраны. Забавно, что в некоторых древних компьютерах ЭЛТ использовались как память (люминофор продолжает какое-то время светиться после облучения электронами). И уже тогда энтузиасты начали писать компьютерные игрушки, рисующие что-нибудь на этих ЭЛТ.
Клавиатура есть. Экран есть. Рисовать компьютеры пока могут только буковки. Но и этого достаточно. А ещё компьютеры были довольно дорогие и большие. Поэтому пользовались ими сразу несколько человек. Часто даже удалённо, через те же телетайпы. Это даже называлось многопользовательскими многотерминальными системами. Но у каждого человека была своя оболочка, принимающая его команды. Это был Unix. И это был интерфейс командной строки (короче говоря, CLI).
CLI on Apple
CLI, как вы знаете, до сих пор жив и процветает. Потому что ничто не может превзойти его по эффективности использования канала связи с удалённой машиной. Поэтому удалённое администрирование — это только CLI. За редким исключением дурацких Windows серверов.
CLI хорош, но требует серьёзной предварительной подготовки пользователя. Нужно знать, какие команды есть, что они делают, какие аргументы и в какой форме принимают. Прежде чем начать пользоваться, нужно это всё изучить. На уровне концепций и способов манипуляций с ними. Не зная, что такое файловая система и каталоги, невозможно понять, что делает команда cd.
Хотелось чего-то более очевидного, наглядного и интуитивно понятного. Поэтому понятен бешенный успех того же Norton Commander. Технически это всё тот же текстовый интерфейс. Но визуально это что-то совсем другое. Это что-то, что наглядно представляет предметную область, в данном случае файлы.
Norton Commander
Это что-то называется графическим (в случае Нортона — псевдографическим) пользовательским интерфейсом (он же GUI).
Графика, т.е. возможность отображать компьютером не только буковки, но и любое изображение, была очень важна. Те же ребята, что на малюсеньких круглых ЭЛТ первых компьютеров делали игрушки про полёты в космос, делали для этих же компьютеров и программы проектирования инженерных сооружений и расчёта их прочности. То, что позднее превратилось в целый класс ПО — CAD. Тут без графики ну просто совсем никак.
Sketchpad
А ребята из Xerox в 70-е годы прошлого века баловства ради решили, что нарисовать можно не только балки и мосты, но обычные вещи, вроде рабочего стола (буквально, стола), папочек, листиков с документами и всё такое. На их беду к ним в гости зашёл некто Стив Джобс. И компьютеры фирмы Apple чуть ли не с самого начала имели GUI в качестве основного интерфейса взаимодействия с пользователем. PC с MS-DOS на этом фоне смотрелся бледновато. Microsoft со своими Windows подтянулись заметно позднее.
Xerox Alto
Что нам даёт GUI? Наглядность. Понятность. Возможность протыкать всё, что есть (меню), не запоминая заранее. Для простого смертного пользователя требуется гораздо меньше предварительной подготовки. Конечно, появились свои примитивы, которым всё же нужно учить. Окна, поля ввода, кнопки, меню. Надо же понимать, что в эти нарисованные прямоугольники можно тыкать.
Кстати, тыкать. GUI подарил нам указательное устройство. Как ни удивительно, вначале это было световое перо (или пистолет). Такая штуковина, которую нужно было наводить на экран. Но прижилась всё же мышка. Хоть она и привязана к конкретным координатам экрана только посредством указателя, и сам по себе манипулятор никаких координат не выдаёт.
Световое перо
Экран, клавиатура, мышка. Окна, меню, кнопки. Что дальше? Дальше случился Web (он же WWW).
До Веба всё взаимодействие в Интернете было исключительно текстовым. Та же электропочта, Gopher... Веб тоже задумывался текстовым, но почти сразу же для него появился графический браузер — Mosaic. Ну и быстренько научились в HTML документы вставлять картинки.
NCSA Mosaic
Никто тогда не думал, что под Веб можно создавать приложения. Ну да, появились формы. Да, появились какие-то интерфейсы. Но строились они исключительно по принципу запрос-ответ. Просим пользователя заполнить форму, отображаем результат. Никакого интерактива и мгновенных изменений, никакой анимации, в конце концов. Как оно уже всё было в десктопных приложениях, в Вебе ничего такого не было.
А потом появился JavaScript. Сначала просто анимация, изменение страницы без перезагрузки. Потом докатились до Single Page Application. Браузеры посолидней стали. Железо десктопное подтянулось. И оказалось, что Web UI вполне может тягаться с десктопным GUI. И даже имеет некоторые преимущества. Браузер уже есть в каждой приличной системе, и приложение не нужно устанавливать. И обновляться оно будет само. И к облакам-серверам будет обращаться вполне естественным способом, через тот же HTTP.
Дошло до того, что появилась даже Chrome OS — операционная система, в которой есть только браузер и веб приложения.
Chrome OS
Сейчас веб UI почти не уступает дестопному UI. Анимация, свестелки, перделки, даже 3D, всё есть. Десктоп ещё не умер окончательно, пожалуй, только по двум причинам. Веб приложениям всё ещё нужен Интернет, чтобы полноценно работать. Веб приложения потребляют значительно больше ресурсов, чем аналогичные по функциональности десктопные приложения.
Дошло уже до того, что стали лениться делать нормальные десктопные приложения, а стали просто делать запускалку с WebView. А внутри этого WebView запускать те же самые веб приложения. JavaScript повсюду, JavaScript везде, даже на серверах. Ужас какой.
Electron
Однако, с веб приложениями началась интересная тенденция. Упрощения. Десктопные приложения можно было делать узкоспециализированными, продавать конкретной аудитории, с которой можно брать деньги за обучение. В вебе аудитория потенциально не ограничена. Прийти и запустить ваше приложение может кто угодно. И надо бы, чтобы этот кто угодно не развернулся в ужасе, а проникся любовью и уважением к вашему продукту. Заработал маркетинг, начали всерьёз интересоваться пользователем. И оказалось, что кучи настроек и возможностей не нужны всем. Всем нужно, чтобы было просто и красиво. И интерфейсы стали проще и понятнее. А приложения часто стали менее функциональными.
Your company app
Однако, действительно сложные приложения, с действительно сложным и нагруженным интерфейсом, всё ещё проще делать для десктопов. Поэтому серьёзные IDE пока существуют только для десктопа. А ещё IDE требуют заметных ресурсов, и дополнительные накладные расходы веба тут совсем не к месту. Так что пока что десктоп жив.
Jobs and iPhone
Благодаря тому же Стиву Джобсу у нас сейчас появился ещё один тип пользовательского интерфейса. Мобильный.
Вроде тот же GUI, но не совсем. Во-первых, очень очень очень маленький экран. Не по пикселям, а по физическим размерам. Во-вторых, экран сенсорный. Вместо, как внезапно оказалось, очень точной мыши, у нас толстый пользовательский палец.
Маленький экран очень сильно ограничивает сложность интерфейса. На него просто не помещается много элементов управления. Нужно ещё сильнее упрощать. Управление пальцами добавляет некоторые неожиданные возможности, вроде свайпов-пролистывания в разные стороны. Но и делает невозможными некоторые привычные для десктопа или веба вещи, вроде всплывающей подсказки при наведении мыши. Ведь никакого указателя мыши тут нет.
Я честно думал, что эти сенсорные штучки не привносят ничего нового. Но потом я стал наблюдать, как моя доча, совершенно не умея читать, обращается с планшетом на Android. Её никто не учил им пользоваться. Но она сама ставит интересные ей игры, играет в них, и смотрит мультики. Да ещё и учит пользоваться планшетом бабушку. Сама. Значит, та самая интуитивная понятность интерфейса всё же достигла уровня, когда с интерфейсом могут справиться четырёхлетние дети.
Mobile UI
Мобильный интерфейс должен быть ещё проще, ещё понятнее, ещё красивее, ещё удобнее чем десктопный или веб. Смартфоны теперь в каждом кармане. Потенциальная аудитория ещё больше, чем у веба. И если вы сможете дать пользователю возможность сделать что-то на два тыка быстрее, чем все конкуренты, вы сможете выиграть. Если, конечно, это будет красиво :) И если вы сможете сделать красивее и удобнее, но абсолютно то же самое, что у всех конкурентов, вы тоже сможете выиграть.
Удобство — это, не в последнюю очередь, отзывчивость интерфейса. А отзывчивый интерфейс можно построить только экономя на ресурсах. Хоть ядер и памяти в современных смартфонах побольше, чем на иных ноутбуках, они вовсе не все всегда задействованы. У нас ведь ещё и очень маленькая батарейка, чей заряд нужно беречь. Поэтому веб приложения, хоть и очень очень стараются стать ещё и мобильными, через те же WebView, всё ещё не так продвинулись в мобилках, как уже отвоевали себе пространство на десктопах. Может, это и хорошо. Может, это и плохо.
Я уже много лет жду, когда, наконец, появится технология, которая позволит делать одинаково удобные, отзывчивые и нативные приложения под десктоп, веб и мобилки. Но пока подобного решения не предвидится. Всё же размер экрана имеет значение, и интерфейс мобилки нужно строить по совсем другим принципам, отличным от десктопного.
UI toolkits
Кстати, заметили, происходит «мобилизация» веба. Так как мобильных пользователей больше, сайты стали упрощать интерфейс, укрупнять элементы управления, внедрять адаптивную вёрстку. Чтобы на экране телефона смотрелось прилично. А на десктопе как-нибудь перебьёмся.
Что дальше? На подходе у нас виртуальная реальность (она же VR), где пользователь полностью погружается в виртуальный мир. (Привет, «Газонокосильщик».) И так же на подходе дополненная реальность (она же AR), где пользователь видит виртуальные предметы или метки поверх своего обычного восприятия реальности. По мне, так существенная разница лишь в том, какая часть реальной реальности замещается виртуальной.
Для этих реальностей нужны специальные устройства вывода: шлемы, очки и всё такое. Нужны и специальные устройства ввода, нужен указатель в трёхмерном пространстве: перчатки или что-то подобное. И нужны какие-то новые способы организации интерфейса пользователя. Какие именно, пока, похоже, не очень понятно.
Microsoft HoloLens
А вот пример Google Glass показал, что интерфейс может быть голосовым. А успех(?) таких устройств, как Amazon Echo и Google Home показывает, что сам по себе голосовой интерфейс, без других способов взаимодействия, тоже имеет ценность.
Amazon Echo
Каким должен быть голосовой интерфейс? Кто знает? Очевидно только, что никто не сможет надиктовать, скажем, программу на C. Так что ввод текста и чтение текста никуда не денутся.

2017-02-05

Об OAuth

Веб — странное место. Нормального входа по логину-паролю всем скоро стало не хватать. Сервисов слишком много, нормальный человек столько паролей не придумает. Поэтому появился OpenID.
OpenID — это распределённая система аутентификации в вебе. Пользователю не обязательно заводить отдельный идентификатор (и пароль) для каждого ресурса, достаточно зарегистрироваться на одном (любом) провайдере OpenID. (И эти провайдеры вполне себе функционируют и ныне, хотя потихонечку отмирают.) А затем использовать этот идентификатор OpenID (который представляет собой просто URL) для аутентификации на любом ресурсе, который эту самую аутентификацию через OpenID поддерживает.
Но мы же движемся к семантическому вебу, к интернету для роботов, сами того не осознавая. И логиниться под одним паролем всюду оказалось мало. Оказалось, нужно, чтобы один веб сервис (т.е. робот) мог обращаться к другому веб сервису (т.е. его API) от имени пользователя. Ну, чтобы какой-нибудь бот мог читать ваши твиты, не давать же ему пароль от Твиттера? Для решения этой задачи придумали OAuth.
OAuth называют протоколом авторизации. Это не проверка идентичности пользователя, т.е. аутентификация. Это предоставление доступа (от имени пользователя), т.е. авторизация.
На самом деле OAuth — целых две штуки. Версия 1.0 оказалась настолько сложной, что буквально единицы сервисов (например, Twitter) его успели внедрить до появления OAuth 2.0. Версию 2.0 специально упростили. Но в результате не специфицировали многие моменты поведения. Пытались это дело устаканить, махнули на всё рукой, и «протокол» переименовали во «фреймворк».
OAuth 2.0 logo
Более менее формальное описание OAuth 2.0 изложено в RFC 6749 (2012 год, за авторством Майкрософта). Это документик о двенадцати главах, трёх приложениях и семидесяти шести страницах. Но его прочтение (по слухам) не поможет вам создать своего OAuth провайдера. А передо мной возникла такая задача. Но одни правильные ребята написали правильную книжку, где всего на сорока пяти веб страничках изложены все основы OAuth 2.0, с объяснениями и примерами кода. Для создания OAuth клиента для любого OAuth провайдера достаточно будет прочитать первые пять страниц.
Итак. У нас есть юзер, т.е. человек, который знает пароль и владеет некоторыми ресурсами на некотором сервисе. У нас есть этот сервис, где есть твиты, картинки, документы и прочие ресурсы, принадлежащие этому человеку. У нас есть другой сервис, клиент OAuth, который хочет получить доступ к этим самым ресурсам этого человека. И у нас есть сервер авторизации OAuth, который может дать права клиенту на ресурсы. Сервер авторизации и сервер ресурсов, понятное дело, должны как-то взаимодействовать (как именно, OAuth не волнует), поэтому часто это вообще один сервер.
OAuth 2.0 roles
Допустим, мы создали новую, милую и хорошую веб службу. И этой службе нужно получить доступ, например, к профилю пользователя на Google. Собственно, получение доступа к профилю пользователя на каком-то солидном и популярном ресурсе или социальной сети, чтобы узнать его имя, и, допустим, email, вполне достаточно для аутентификации пользователя на вашем маленьком сервисе. Это и называется «войти через Google/Facebook/VKontakte/etc».
Для начала нам нужно нашу службу зарегистрировать на сервере авторизации. Везде это делается по-разному. Чаще всего это всякие девелоперские настройки вашего профиля, список авторизованных OAuth приложений, и тому подобное. Вы должны указать название и описание вашего сервиса, вероятно, некоторую иконку, а также очень важный параметр: Redirect URI. Это URI/URL на вашем сервисе, куда, в виде колбэка, будет приходить важная информация от сервера авторизации. Таких URI, как правило, можно указать несколько.
Взамен вам, для вашего сервиса, выдадут Client ID и Client Secret. Это просто две длинные строки, которые нужно будет вписать куда-то в настройки вашего приложения. Client Secret — это аналог пароля, поэтому хранить его нужно соответствующе, по возможности шифруя.
От сервера авторизации вам нужно знать ещё два URL, два метода его API: URL авторизации и URL получения токена. Они обычно заканчиваются на что-нибудь вроде oauth/authorize и oauth/token.
Итак, где-то у вас на сайте есть кнопочки вроде «Log in with Google» или «Connect to Google Drive». Когда пользователь их нажимает, начинается процедура авторизации. Ваш сервис перенаправляет браузер пользователя на oauth/authorize сервера авторизации. При этом в URL добавляются параметры.
  • client_id — тот самый идентификатор клиента, что вам выдали при регистрации вашего сервиса.
  • response_type — ожидаемый тип ответа от сервера авторизации, для данного случая должен быть «code». Хотя некоторые серверы авторизации не требуют этого параметра, ибо поддерживают только один вариант авторизации.
  • redirect_uri — один из тех адресов возврата, что вы указывали при регистрации.
  • scope — описание того, какие права вы хотите получить для доступа к ресурсам. Только читать твиты или ещё их и постить? Формат этого параметра определяется исключительно сервером авторизации (и сервером ресурсов). Обычно это какой-то набор строк (или URL), разделённых пробелами, по отдельной специальной строке для каждого разрешения. Гранулярность разрешений тоже зависит от сервера ресурсов. Например, у Google получить просто имя пользователя — это «profile», а узнать его емейлы — это дополнительно «email».
  • state — некоторое «состояние» вашего сервиса, эта строка просто вернётся вам без изменения в конце, можно использовать, чтобы отличить один ответ от сервера авторизации от другого. Точнее даже нужно использовать, для защиты от CSRF атак.
Таким образом, в браузере пользователя открывается некоторая страница сервера авторизации. Что там происходит, определяется исключительно этим самым сервером. Если пользователь не залогинен на этом сервере, его попросят ввести логин и пароль. Если залогинен, сразу будет показана следующая страница. На этой следующей странице пользователь должен явно разрешить доступ вашему приложению. Тут будет отображаться то самое имя, описание и иконка вашего приложения, что вы указали при регистрации. Так что давайте внятное описание, чтобы пользователь не испугался.
Кроме того, будут явно показаны те разрешения (scope), что запросило ваше приложение. На особо продвинутых серверах авторизации пользователь даже может снять некоторые галочки, и в результате разрешить несколько меньше, чем ваше приложение запрашивает.
Пользователь подумает-подумает, да и нажмёт на кнопку «Allow». Это — успех. Сервер авторизации, запомнив решение пользователя, редиректнет браузер на тот самый redirect URI. Здесь придут два параметра.
  • code — временный код для получения токена доступа. Некая произвольная строка. Срок действия этого кода — не больше нескольких минут.
  • state — тот state, который вы посылали ранее, без изменений.
Ну вы же запрашивали «response_type=code», и получили «code».
Если что-то пошло не так, то либо ошибка будет показана где-то на страницах сервера авторизации, и никакого редиректа не будет. Либо в вашем редиректе появятся параметры error, error_description или даже error_uri, показывающие код, описание или, соответственно, адрес страницы с описанием ошибки.
Мы получили code. Ваш сервер не должен этот код нигде хранить, а должен немедленно обменять его на токен. Для этого нужно сделать запрос на второй URL сервера авторизации. Нужно сделать POST на oauth/token. Сделать не из браузера, а из бэкенда вашего приложения. И передать другие параметры в теле запроса, в виде x-www-form-urlencoded данных.
  • grant_type — способ получения токена, в данном случае по коду, поэтому значение этого параметра должно быть «authorization_code». Некоторые серверы авторизации не используют этот параметр, потому что поддерживают только один вариант авторизации.
  • code — тот самый код, который вы получили от сервера авторизации шагом ранее.
  • redirect_uri — снова redirect uri, чтобы лишний раз подтвердить, что вы — это вы. Нужен ли он, зависит от сервера авторизации.
Клиент, т.е. ваш сервер, должен аутентифицировать себя для получения токена. Чаще всего для этого к запросу добавляются ещё два параметра.
  • client_id — снова ваш идентификатор клиента.
  • client_secret — тот самый секрет, который вы получили при регистрации, наконец-то он пригодился.
Или же идентификатор клиента и секрет выступают в роли имени пользователя и пароля и передаются в HTTP заголовке «Authorization», согласно Basic Auth. Или же достаточно обычного GET запроса, а все параметры передаются в виде обычного query. Тут все нюансы снова определяются сервером авторизации.
В ответ за запрос токена сервер авторизации возвращает нам (наконец-то) обыкновенный JSON. Этот JSON не содержит вложенных объектов, а только свойства в объекте верхнего уровня. Эти свойства нам и нужны.
  • access_token — тот самый токен доступа, ради которого всё это затевалось.
  • token_type — некая подсказка, как этим токеном пользоваться. Часто просто строка «bearer».
  • expires_in — сервер авторизации может указать время жизни выданного токена.
  • refresh_token — сервер авторизации может выдать дополнительный токен, который может использоваться для обновления основного токена без прохождения всей этой длинной процедуры авторизации. Это имеет смысл из соображений безопасности, чтобы основной токен менялся почаще, но при этом доступ был подольше.
  • scope — если пользователь снимал галочки и урезал доступ вашему приложению, тут может вернуться актуальный scope, который отличается от того, что вы запрашивали.
  • state — state может вернуться и здесь.
Если что-то пошло не так, то в JSONе ответа снова будут поля error, error_description или даже error_uri.
Ну вот почти и всё. Теперь у вас есть accesstoken. Он даёт право вашему приложению делать какие-то действия, ограниченные scope, от имени данного авторизованного пользователя на сервере ресурсов. Нужно только в каждом запросе на API сервера включать этот токен. В простейшем случае токен добавляется к запросу как параметр с именем «accesstoken» или «token». Или же нужно посылать заголовок «Authorization» типа «Bearer».
Authorization: Bearer <access_token_here>
Тут нюансы снова зависят от сервера авторизации и конкретного сервера ресурсов. Потому OAuth и назвали «фреймворком», на «протокол» не тянет, ибо слишком многие детали отданы на откуп конкретным реализациям. А от этих деталей очень сильно может зависеть секурность протокола вообще, именно поэтому OAuth 2.0 справедливо критикуют за дырявость.
Даже в рамках одного OAuth параметры авторизации могут передаваться в query части URL, в теле запроса, включая x-www-form-urlencoded и JSON (хорошо, что без XML обошлись), или в заголовках. Но, поверьте мне, OAuth всё же не выиграет приз в номинации «Наибольшее разнообразие способов передачи параметров в HTTP в рамках одной спецификации».
OAuth with auth code workflow
В рассмотренном выше наш сервер должен держать где-нибудь в конфигах два параметра: Client ID и Client Secret. Иногда это считается небезопасным, когда код не является доверенным, потому что пользователь (потенциально) имеет к нему (коду) полный доступ и может (потенциально) модифицировать его по своему желанию. Это касается приложений, выполняющихся в браузере (т.е. на JavaScript), и мобильных приложений (т.е. под iOS и Android, например).
Для такого случая Client Secret не выдаётся (не нужен), а получение токена происходит посредством так называемого implicit grant.
В первом запросе, на oauth/authorize, в параметре response_type указывается другое значение: «token». Т.е. мы говорим серверу авторизации, что не хотим никаких промежуточных кодов, а хотим сразу токен.
И сервер авторизации редиректит назад с двумя параметрами: token и state. Т.е. мы сразу получаем наш вожделенный токен. Но вот передаётся этот токен не в query, а во fragment.
https://my.cool.app/oauth/callback#token=Yzk5ZDczMzRlNDEwY&state=TY2OTZhZGFk
Фрагмент не передаётся на сервер, но доступен из JavaScript. А мы вроде как и не хотели его никому, кроме как коду на JavaScript, давать.
С мобильными приложениями всё ещё интереснее. Страницы сервера авторизации, с логином/паролем и кнопкой «Allow» отображаются в системном браузере или WebView. Куда происходит редирект? Вообще-то, куда угодно.
Сервера у нас нет, поэтому редиректить надо на приложение. В Android приложение можно навесить на открытие любого URL. В iOS нужно регистрировать для приложения свою схему URI, и редирект получается на нечто вроде «mycoolapp://oauth/callback#token=xxx&state=yyy».
В принципе, если мобильное приложение контролирует WebView, можно вообще никуда не редиректить, а просто отловить ответ 302 Found от сервера авторизации и взять заголовок «Location», т.е. адрес, куда должен произойти редирект.
В любом случае мобильное приложение получает доступ к URI редиректа, и получает оттуда токен. Ну а потом использует этот токен как обычно, добавляя в запросы к API сервера ресурсов.
Получается, что Redirect URI — это действительно URI, а не просто URL. Сервер авторизации особо к его содержимому не придирается. Главное, чтобы этот URI, полученный от клиента, в точности совпадал с URI, указанным клиентом при регистрации.
Поэтому, кстати, вполне можно тестировать OAuth локально, указав в Redirect URI что-нибудь вроде «http://localhost:12345/test/callback».
OAuth implicit grant workflow
Если вы успешно переварили всё то, что тут было написано, вы сможете разобраться с этим скриптом. И поймёте, зачем там запускать браузер. Это вполне реальный пример мелкой автоматизации манипуляций через Slack API с использованием OAuth 2.0. Удачи.
Ещё раз, хотите сделать свой OAuth 2.0 руками — сначала прочитайте OAuth.com.

2017-01-21

О деплое

Есть такая штука — плойка. Ею завивают волосы. Соответственно, где-то в природе существует штука — деплойка. Ею распрямляют волосы. Но айтишники для деплоя, т.е. deploy, т.е. развёртывания, явно пользуются чем-то другим.
Deploy!
Начиналось всё, как я уже писал, с простого FTP. По крайней мере для веба. Во времена PHP и статических сайтов всё, что нужно было сделать, это положить новые версии файлов в нужные места на сервере. FTP для этого вполне хватало.
Страшно представить, в те времена даже системами контроля версий мало кто пользовался. Хотя CVS и, прости Господи, Visual SourceSafe вполне уже были и работали. И даже модный Subversion уже подтягивался. А что, начал серьезные переделки, сохрани предыдущую версию в архив, на всякий случай. Вот такая деревенская система контроля версий.
Конечно, сейчас есть более интересные и секурные способы передачи файлов. SCP — просто копирование файлов, но через SSH. Значительно секурнее. А rsync — вообще пушка. Тоже работает через SSH, хотя имеет и своего демона. Жмёт файлы при передаче, что очень даже актуально для всяких скриптов, бинарников и разметок. Ну а самое главное, rsync — синхронизирует. Т.е. высчитывает контрольные суммы от содержимого файлов на удалённой стороне и передаёт только различающиеся файлы. Даже только различающиеся части файлов. Для инкрементного деплоя — ваще то, что надо.
А недавно я открыл для себя новые возможности ssh и rsync. Во-первых, ssh умеет ограничивать те команды, которые может запускать определённый даже не пользователь — ключ. Вы сталкивались с этим на примере ssh доступа к git репозиториям. Вроде все подключаются под одним юзером по имени git, но с разными ключами, и имеют доступ к разным репозиториям. В ~/.ssh/authorized_keys этого пользователя перед каждым ключём ещё указана директива command=.
Во-вторых, в rsync для Debian имеется команда rrsync. Технически это скрипт на Perl, работает не только в Debian но и во FreeBSD. Он позволяет для rsync через ssh ограничить доступ к определённому каталогу файловой системы. Ну примерно как в FTP, где каждый юзер может наложить файлы только в свой каталог. Только тут каждый ssh ключ можно снабдить ограничениями. Можно в authorized_keys прописать что-то вроде:
command="$HOME/bin/rrsync -ro ~/backups/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAA...vp Automated remote backup
И пожалуйста, по этому ssh ключу можно rsyncать только каталог backups, причем только читать из него.
Rsync minion
Ладно, с передачей файлов разобрались. Но это то, чем заканчивается деплой. В более приличных языках или более серьёзных проектах нужна еще сборка.
Систем сборки существует туева хуча. Замечу только, что уже окончательно произошёл переход от императивного описания сборки к декларативному. В императивном случае вы обязаны указать, что именно нужно сделать, какие шаги выполнить, какие команды и с какими параметрами запустить, чтобы получить очередной кусок проекта. В декларативном случае все шаги и команды уже подразумеваются неявно, и исходные файлы уже расположены в подразумевающихся по умолчанию местах. Вам лишь нужно задать некоторые параметры, которые не могут быть выяснены неявно. Как минимум это — имя проекта и его зависимости, т.е. другие проекты, которые он использует. Для явистов поясняю кратко: в начале был Ant, но теперь балом правит Maven, а также всякие штуки, использующие его инфраструктуру, вроде Gradle.
Ради сборки возникает CI — Continuous Integration — непрерывная интеграция. Идея такая. Какая бы ни была у нас система сборки, каждый разработчик всё равно собирает проект у себя локально. Как минимум, чтобы тесты погонять. Но откуда возьмётся релиз? Т.е. сборка продукта, которая пойдёт к пользователям. Либо в виде кода, залитого на сервер, либо в виде какого-то дистрибутива, доступного для скачивания и установки.
Либо нам нужно выделять отдельного человека для осуществления ответственной релизной сборки. Это — релиз-менеджер, была такая роль в проектах. Либо мы это дело автоматизируем. Ведь у нас уже есть система автоматизированной сборки. И мы умеем заливать файлы, куда надо.
Первым CI инструментом, с которым я столкнулся, был Hudson. Родился он в недрах Sun, которую, как вы знаете, со всеми потрохами купил Oracle. Ну и, как случилась и со многим другим наследием Sun, разработчики разругались с Oracle, хлопнули дверью и создали свой клон — Jenkins. А Hudson умер.
Сейчас набирает существенную популярность Teamcity от JetBrains. Он, конечно, стоит денег. Но для небольших команд хватит и бесплатной версии.
Jenkins и Teamcity работают очень похоже. Система CI мониторит систему контроля версий. Когда в релизной ветке появляются новые коммиты, запускается сборка. Сборка происходит на агентах, которыми могут быть даже Windows машины. Важно, что репозиторий, который мониторить, шаги сборки, которые запускать (ну то, какие параметры mvn передать), задаются в самом CI и хранятся в его БД. В особо запущенных случаях сборка проекта расползается на десятки шагов, с невнятными многостраничными shell скриптами на каждом шаге. Самое страшное в этом, что эти скрипты — за пределами контроля версий.
Тем приятнее свежий подход, применяемый в Travis, Bitbucket Pipelines и GitLab CI. Здесь шаги CI прописываются в специальном файле, размещённом прямо в репозитории с исходным кодом. Как правило, в публичном репозитории, соответственно, на GitHub для Travis, в Bitbucket для Pipelines, в GitLab для GitLab CI. Получается, что вся конфигурация CI тоже под контролем версий.
Но кроме того, теперь сборка осуществляется в Docker контейнерах. Ничто не мешает нарисовать свой нужный контейнер, с нужными библиотеками и компиляторами, для сборки данного конкретного проекта. Среда сборки получается более стабильной.
Заметьте, системы контроля версий слились с системами непрерывной интеграции. Если сюда ещё добавить среду для выполнения ваших программ, (чтобы не пришлось куда-то отдельно копировать файлы :) то получится, простите, Heroku.
DevOps loop
В некотором смысле эти наши хероки, это уже следующий уровень развития облаков. А до этого облака были просто виртуальными машинами, с голой ОС, на которые можно ставить всё, что угодно. Это ещё называется VPS. Ну и обычные железные сервера, либо где-то арендованные, либо стоящие рядом на полочке (чтобы теплее и уютнее), никто не отменял. И эти сервера, ещё до того, как на них деплоить ваши приложения, надо как-то настроить.
Конечно, когда серверов лишь несколько штук, настроить можно всё руками. В лучшем случае написав инструкцию по деплою. Но у нас же в моде распределённые системы, кластеры и прочие штуки, когда серверов набираются сотни, причём большинство из них нужно настроить одинаково.
Эту проблему назвали Configuration Management. И сначала подключились математики. Идея была такая. Правильно настроенный сервер находится в некотором состоянии. На нём установлены определённые пакеты, присутствуют определённые файлы конфигурации и запущены определённые сервисы. Задачей (математической) является доведение серверов до нужного состояния. А какое состояние должно быть у каждого сервера, пусть будет записано на некоем центральном сервере. Там же пусть хранятся и рецепты по переходу из состояния в состояние, т.е. по установке и настройке сервисов. А на самих серверах пусть будет запущен некий агент, который будет проверять, в каком состоянии находится этот сервер, сверяться с центральным сервером, и выполнять рецепты, если состояние нужно изменить.
Именно так работают CFEngine (ведущий родословную аж с 1993 года), Puppet и Chef. Существенная разница между ними, в общем-то, только в формате представления рецептов. Ну и в CFEngine больше всего математики. Puppet и Chef почему-то написаны на Ruby. Питонистам стало обидно и они родили SaltStack.
Но сейчас для задачи настройки кучи серверов всё чаще выбирают Ansible. Его делали инженеры. И им было лень заводить отдельный сервер и разворачивать целую инфраструктуру для такой скучной задачи, которую ещё и спокойно можно сделать руками. Поэтому Ansible — это, по большому счёту, лишь инструмент удалённого выполнения команд. Всё что ему нужно — доступ на удалённые сервера по ssh и наличие там Python 2. Команды, которые он может выполнять, чаще всего написаны на Python. И они, чаще всего, идемпотентны (ой, всё-таки математика). Команды, прежде чем что-то сделать, проверяют, а нужно ли это делать. Результат получается тем же самым. Если мы упорно будет пытаться поставить пакет на N серверов, он таки, рано или поздно, будет установлен на всех N серверах. Задача решена.
Справедливости ради стоит заметить, что серьёзные облачные провайдеры, вроде AWS, обзаводились собственными, ни с чем не совместимыми, инструментами конфигурирования и развёртывания ещё до того, как всякие Chef и Puppet становились популярными.
Lego VMs
И кстати, в облаках у нас живут исключительно виртуальные машины. А виртуальные машины возникают из образов. А в образ, помимо операционной системы, можно поместить и нужную среду выполнения. И даже поместить всё ваше (сетевое) приложение. И когда придёт время выпустить новый релиз, мы просто соберём новые образы и запустим новые виртуалки. А старые просто удалим. Это называется Immutable Infrastructure. И специализируется на этом подходе компания HashiCorp, которая подарила Миру такие замечательные инструменты как Vagrant и Packer.
А что, если пойти дальше? Пусть эти образы и виртуальные машины будут насколько лёгкими и простыми в использовании, что разработчики смогут запускать их на своих компьютерах. И тестировщики будут тестировать образы, созданные разработчиками. И те же самые образы будут деплоиться на реальных серверах. Это — идеология Docker. А Docker, как мы уже видели, может быть инфраструктурой не только самого приложения, но и частью инфраструктуры сборки приложения.
Всё становится довольно сложно. Нужно автоматически собирать. Нужно автоматически разворачивать. Нужно автоматически настраивать. Нужно ещё и мониторить. Для этой кучи задач придумали даже своё название: Continuous Delivery. (Забавно, что в русском «обеспечении» — тот же корень: «liver» — «печень».) А для людей, занимающихся этими задачами, тоже придумали название: DevOps.
Впрочем, тут справедливо заметили, да и Википедия пишет, что DevOps — вовсе не профессия, не должность, и даже не роль. DevOps — это набор практик, которые должен применять к своему проекту любой грамотный разработчик. Чтобы каждый пуш в репозиторий превращался в развёрнутый отчёт по тестированию, метрикам кода, и приводил к автоматическому появлению новой версии продукта на продакшене, на благо пользователям.

2017-01-08

О статике

Веб меняется.
Web
Начиналось всё с рисования HTML ручками. Потом добавился CSS, чтобы красивее было, тоже писался ручками. Потом добавился JS, но использовался он исключительно для свистелок и перделок. Помнится, даже специальные гуёвые инструменты были, чтобы сайты делать.
Со стороны сервера эти самые HTML, CSS и JS валялись файлами. Прямо руками создавались файлы. Прямо в HTML или CSS разметке. И заливались через FTP на сервер. Это была исключительно статика.
Но конечно же очень скоро захотелось динамики. У нас же есть формы. А формы надо куда-то сохранять. И какие-нибудь результаты или даже отчёты надо отображать.
Динамика появилась сначала в виде CGI. Довольно быстро основным языком для написания CGI-скриптов стал Perl. А вскоре и PHP подоспел.
Быстро оказалось, что CGI — это не очень эффективно — плодить по процессу на каждый запрос. И самый популярный тогда веб сервер Apache быстренько обзавёлся модулями для интерпретации и Perl, и PHP, чтобы выполнять скрипты прямо в процессе веб сервера. Именно тогда появилась аббревиатура LAMP.
LAMP
CGI-скрипты были именно скриптами. По одному скрипту на каждый запрос. И такая структура была унаследована и в PHP. Бэкенд становился всё сложнее, требовались какие-то средства организации кода. В те времена PHP4 каждый пэхэпэшник рисовал свой фрэймворк, организующий .php файлы и загружающий только нужные.
Несколько организованней всё происходило в мире Java. Там с самого начала были сервлеты — отдельные классы для обработки запросов. И механизмы маппинга запросов на эти самые сервлеты. Можно было прилично всё организовать: классы бизнес-логики — на старой доброй Java, и всё это для веба слегка прикрыто сервлетами.
Правда, испугавшись популярности PHP, ребята запилили JSP. Все тогда пилили свои Server Pages, на которых можно было говнякать, как на PHP, смешивая в одних файлах и обращение к базе данных (о, я помню mysql-escape-string() и mysql-real-escape-string()), и бизнес-логику, и формирование HTML.
Важно, что вся эта динамика возвращалась с сервера в виде HTML. А так как на солидных порталах без динамики не может обойтись ни одна страница (имя залогиненного юзера в меню показывать), весь HTML генерировался бэкендом. Из статики остались только CSS и JS, ну и картинки, конечно.
Понятное дело, чтобы как-то организовать код на бэкенде, придумали много разных вещей. Например, MVC — Model-View-Controller, чтобы отделить то, что зависит от предметной области — модель, от того, что является спецификой веба — контроллер, и от того, что является исключительно представлением — вьюхой. Но вьюха по-прежнему создаёт HTML разметку.
MVC
Кстати, создатели HTML допустили фатальную ошибку, когда решили использовать угловые скобки для выделения тегов. Они ведь ни на одной клавиатуре без шифта не набираются. Понятно, что сильно многим надоело топтаться на шифте, и придумали уйму языков-шаблонов.
Самый известный сейчас, пожалуй, — это Markdown. В нём можно писать так, что и текст в простом тексте (т.е. «исходники» маркдауна), и текст в «богатом» варианте (например, в HTML) выглядят одинаково читабельно и красиво. Вот только в Markdown можно представить только простую разметку, для текста, даже на таблицы стандарта нет.
А недавно я познакомился с Pug, он же PugJS, ранее известный как Jade. Дурацкое название, что новое, что старое, гуглить приходится «pug/jade». Вот это уже полноценный язык шаблонов для иерархических структур, типа HTML или XML. Вложенность тегов изображаем вложенностью элементов. Да, Pug — это ещё один язык (разметки), чувствительный к отступам. Сразу предупреждаю.
Потом появился Node.js. Внезапно оказалось, что те, кто писал свистелки и перделки на JavaScript, могут сами для себя и сервер написать. Но с его приходом пока что мало что поменялось. Express — это, в чистом виде, такая же низкоуровневая штука, как сервлеты. Вот вам запросы, обрабатывайте их как хотите. Можно и HTML по шаблонам генерить.
Серьёзное изменение произошло с появлением AJAX, задолго до всяких Ноджээс. Оказалось, что не обязательно серверу возвращать всю динамику в виде целой HTML страницы. Сначала оказалось, что кусочки этой страницы можно подгружать отдельными запросами. Потом оказалось, что не обязательно возвращать HTML. Пусть приходит какой-нибудь JSON, а мы уж тут, в JavaScript, прямо в браузере, разберёмся, куда эти данные запихнуть в DOM дерево.
AJAX
И если раньше веб-разработчик занимался всем: и схему БД делал, и бизнес-логику писал, и HTML верстал, и CSS стили натягивал, и JS рисовал (как попало, до появления jQuery). То теперь между фронтендом и бэкендом пролегла чёткая граница.
Бэкенду осталась персистентность, работа с БД, и API для доступа к данным. Фронтенду досталась вся красота, видимая пользователям. Веб стал веб-приложениями, запускаемыми в браузере и общающимися с API.
Примечательно, что это API может быть использовано не только веб-приложениями, но и мобильными приложениями, десктоп приложениями (которые тоже всё чаще становятся внутри веб), и даже сторонними сервисами.
Также примечательно, что подразумевается чаще всего RESTful API. Происходит это от сосредоточенности на данных, ведь REST — это доступ к данным. Причём данным предметной области. Этим RESTом всё настолько пропитано, что инструменты документирования API, вроде Swagger, даже не подозревают, что через HTTP можно просто пересылать JSON или XML документы, без привязки типа документа к пути в URL.
REST
На самом деле, для веба, мобилки и десктопа в общем случае нужны разные API. Как минимум потому, что на одном экране мобилки помещается меньше данных, чем на одной веб-странице. В идеале хочется, чтобы на одну страницу был бы один запрос на сервер, который бы вернул все данные, необходимые для отображения данной страницы. А раз данные разные для мобилки и веба, то и запросы, и API должны быть разными. И REST тут тоже будет только мешать, ведь мы хотим в один запрос вместить совершенно разнообразные данные. Это должна быть просто пересылка сообщений через HTTP.
Так вот, у нас получается один сервер исключительно для динамики, для API. Но веб-приложение — это HTML, CSS и JavaScript. Это — статика. В том смысле, что для браузера это статичные файлы, пусть и создающие динамическое веб-приложение. Нужен ещё один сервер для раздачи статики.
Ну и саму эту статику, фронтенд веб-приложения, никто уже не пишет на голом HTML, CSS и JS. Для HTML есть шаблоны, тот же Pug или JSX. Для CSS есть всяческие препроцессоры и постпроцессоры: SASS и LESS — по сути отдельные языки (более удобные?) для описания стилей. Для JS тоже есть альтернативные представления, вроде TypeScript или CoffeeScript. К тому же стало модно транслировать популярные языки программирования общего назначения в JS представление, чтобы кодить и под веб.
Само собой, появились и фреймворки. Весьма мощные. Вы их знаете: Angular и React (упомянутый выше JSX есть часть ReactJS).
Angular vs. React
Настолько всё изменилось в мире фронтенда, что это дело понадобилось собирать. Тут, внезапно, снова пригодился Node.js. Всякие прелести стали паковать в NPM модули, даже CSS. Появился свой собственный инструмент сборки, работающий в Node. Да сильно не один: Grunt, Gulp, Webpack... Тысячи их. Хипстотометр зашкаливает.
В общем, статика рулит вебом. Никому не интересны эти ваши бэкенды и API. API можно и заглушкой заделать, или в Firebase мышкой натыкать. А вот на чём мы будем фронт делать? И чем это дело будем собирать?
С другой стороны, есть у меня сайт на PHP. Где из всех конструкций PHP используется только include. Зачем ему быть динамичным, если это всё статичный HTML? Ведь можно взять все эти шаблоны, генераторы, билдеры и сгенерировать статику. Нефиг руки HTML тегами марать.
Есть ещё сайты, в которых нет ни одной формы, одно только содержание. Вот я и побаловался. Ради снижения уровня хипстоты нарисовал генераторы страничек на Python. Pug для шаблонов (как ни странно, через Mako). Markdown для контента. Получилось. Даёшь генерацию статичных сайтов!

2016-12-24

Об IFTTT

Недавно Telegram обзавёлся поддержкой IFTTT. Или IFTTT обзавёлся поддержкой Telegram. Это смотря с какой стороны смотреть. Самое время вспомнить, что такое IFTTT, и зачем оно нужно.
IFTTT — это IF This Then That. Если Это, То То. Если что-то где-то происходит, то сделать с этим то-то. Что-то и то-то — это интернет сервисы, а в последнее время и всякие штуки интернета вещей. Если что-то случается в одном интернет сервисе или умной домашней штуке, можно сделать что-то в другом интернет сервисе или другой умной домашней штуке.
IFTTT
Эти сервисы или штуки раньше назывались каналами (channel), а сейчас, называются именно сервисами. Просто сервисы, с которыми может работать IFTTT. У каждого такого сервиса есть две стороны. С одной стороны оно может стоят в условии if, т.е. быть источником каких-то событий. Называется это триггером (trigger). С другой стороны оно может стоять после then, т.е. совершать какие-то действия. Называется это экшеном (action).
Объединение условия и действия раньше называлось рецептом (recipe), а сейчас называется апплетом (applet). Задаёте себе апплеты и они работают. Где-то в недрах IFTTT, типа в облаке. Это тотальная автоматизация интернетов. И, в какой-то мере, умных домов.
IFTTT
Я постоянно пользуюсь такими рецептами. Feed в качестве триггера. Это полноценная читалка RSS и Atom лент. Соответственно, событие возникает, когда появляется новая запись. Ну ещё опциально можно отфильтровать по ключевым словам. И Pocket в качестве экшена. Это тот самый Pocket, который замечательное приложение для чтения потом. В него я засовываю всякоразные статьи, чтобы потом с комфортом читать с планшета. А с помощью IFTTT туда же сами попадают новые статьи из RSS. Таких рецептов, Feed + Pocket, у меня аж шестнадцать штук. Таким странным образом я подписываюсь на RSS.
Конечно же, можно подписаться на RSS через Feedly. Помните, он возник, когда Google похоронила свой Reader, объявив, что RSS умирает. Вот только Feedly предоставляет интеграцию с IFTTT только в Pro версии, которая стоит денег. А Feed в IFTTT — бесплатен.
Ещё один мой рецепт шлёт новости из Feed по электропочте. Сервис Email умеет слать симпатичные письма.
Есть интеграция и с Gmail. Но Gmail больше подходит для отправки писем, от вашего имени, чем для получения писем. Как-то письма с новостями от самого себя выглядят немного странно. А вот триггеры у Gmail весьма интересные. Можно мониторить входящие, по меткам или разным условиям поиска. А уж в then потом можете сделать что угодно.
У IFTTT есть приложение для Android. В нём можно рулить рецептами. Но и IFTTT получает возможность рулить вашим Андроидом. IFTTT может следить за состоянием батареи вашего телефона, есть соответствующие триггеры. Может следить (триггеры) за Bluetooth и Wi-Fi подключениями, и менять (экшены) рингтоны, обои и прочую мелочь. Может следить за вашим местоположением. Может следить, кто вам звонит и какие звонки вы пропустили. Узнаёт, когда вы сделали новую фоточку. Может следить на СМСками и даже отсылать их. А ещё может отсылать уведомления на умные часы с Android Wear.
Конечно, более специализированные штуки, работающие в рамках одного Android устройства, могут быть более точны и эффективны. Например, для отключения звонка, когда находишься на работе, лучше работает Locale или Llama. А Automate вообще творит чудеса. А фоточки в облако прекрасно заливает и сам Google Photos. Но когда надо связать разные сервисы с вашим телефоном, а прямой интеграции нет, то IFTTT остаётся чуть ли не единственным вариантом.
Кстати, вместо какого-то условного триггера, можно просто разместить кнопку на экране вашего телефона. Нажимаете кнопку, и некий экшен происходит.
IFTTT
IFTTT — это самый универсальный способ репоста между разными соцсеточками. Поддерживаются Twitter, Facebook, Foursquare, Instagram, LinkedIn и некоторые другие. А вот ни ВКонтакте, ни Гуглоплюса нет.
Гугль вообще как-то плохо дружит с IFTTT. Гуглокалендарь, например, есть. Но триггер там срабатывает только в момент наступления события в календаре, а вовсе не заранее. Почти бесполезно.
Поддерживаются чатики. Можно послать сообщение в Slack. А можно что-то сделать в Telegram.
Интеграция с Телеграмом получилась очень мощная. Со стороны Телеграма это выглядит как бот по имени @IFTTT. Его можно добавить в группы и каналы. И триггером в IFTTT будут определённые сообщения напрямую боту или в эти группы или каналы. А в качестве экшена IFTTT может посылать сообщения, фоточки, видосики, музычку прямо вам или снова в группы или каналы. Даже не выходя за пределы Телеграмма получается довольно мощный и универсальный бот. Но ведь триггерами в IFTTT может быть почти всё, что угодно, и экшенами тоже всё, что угодно. Можно настроить уведомление в Телеграм о всём, что угодно. Или наоборот, настроить управление из Телеграма всем, чем угодно.
Интернет вещей. Есть простые штуки, вроде тех самых лампочек, меняющих цвет, с неприличным названием Philips Hue, или того самого термостата Nest. А есть интеграция с сервисами, которые сами являются полноценными мозгами для умного дома, например, openHAB.
В общем, IFTTT — очень крутая штука. И часто — совершенно бесплатная. Довольно сложно сразу придумать, зачем он нужен. Но иногда он весьма элегантно решает насущные проблемы.
Сейчас IFTTT почему-то акцентирует внимание на готовых апплетах, хотя вся мощь IFTTT запрятана в сервисах. Начните с поиска сервисов. Посмотрите, что они умеют. Вдруг пригодятся.
IFTTT Infrastructure