• Еще про API

    Вдогонку про рестовые апишки. С первого дня, что я работаю с ними, меня бесит одна вещь, которую никто не замечает. Она состоит в следующем: данные размазаны по всему HTTP-запросу.

    Часть параметров нужно взять из урла, например айдишки сущностей. Другие данные – из заголовков. Если это GET/HEAD, взять параметры query string. Если POST – читать multipart или парсить json. В итоге первые 5-10 строк кода уходят на то, чтобы выковырять данные из разных мест.

    Всегда поражало: почему никто не видит проблемы? Почему не заслать все в джейсоне через POST? Ради чего размазывать поля по разным местам?

    Я хочу, чтобы апишка была описана данными, например словариком с полем action и params. Все в одном месте, и дальше этот словарик разруливается по полю action. Под каждый словарик пишется схема и документашка. Что еще нужно?

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

    • не нужно парсить урлы и query-параметры. Парсинг урлов, вычленение айдишек с приведением к числу – это сложно;

    • не нужно клеить урлы на клиенте. Это лютейшая боль: зачем вам /api/v1/users/123/orders/456 вместо {:user_id 123 :order_id 456}?

    • сообщенька отделяется от транспорта; становится неважно, как ее передали: по HTTP или иначе.

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

    • следствие 2: очереди. Тяжелые сообщения можно складывать в кафки-реббиты.

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

    • пакетный режим: тот же JSON RPC позволяет заслать несколько сообщений за раз. Их можно обработать параллельно.

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

    Замечу, что под сообщением я имею в виду не очереди задач, а нечто другое. Сообщение – это набор данных, которые описывают действие. Удобно, когда все это хранится рядом.

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

    Сообщения удобней для понимания. Что легче читается: POST /api/v1/orders или CreateOrder? Другой пример: PATCH /api/v1/orders/123456/cancel или CancelOrder? То-то же.

    Сообщения используются не только в вебе. Например, любое обращение к Postgres – это обмен сообщеньками. У них общий контейнер: метка, длина и произвольное тело, которое парсится в зависимости от метки. Как только написал парсер сообщений, считай, клиент почти готов.

    В Кассандре, Кафке то же самое: заголовок/cooбщенька, заголовок/cooбщенька.

    Поэтому я уверен: нужно думать над сообщениями, а транспорт всегда найдется. Это вторичная вещь. Жаль, этого не понимают REST-маньяки со своими сваггерами, постманами, бест-практис и так далее.

  • Европейский закон о зарплате

    Пишут, что в 2026 году в Европе введут новые правила о зарплате. Вот неполный их список:

    • зарплата должна быть указана в любом объявлении о найме;

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

    • разглашение зп не может быть поводом для увольнения или других воздействий;

    • сотрудникам можно запрашивать информацию о зарплатах других сотрудников;

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

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

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

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

    Кстати, в этом коренная проблема Европы – тотальное регулирование. Именно поэтому Европа смотрит американский Нетфликс, ищет в американском Гугле, читает американский Твиттер, чатится в американском Вацапе, вызывает американский Убер и так далее.

    Где хостится европейский стартап? В американских AWS, Google Cloud, Azure. Куда едут условные финн Торвальдс и голландец Ван Россум? Где зарегистрированы фонды Линкуса и Питона? В Америке.

    Чтобы создать сервис, который захватит мир, нужно нарушить чьи-то права. В Америке обе стороны (фирма и работник) идут на это добровольно, и в результате американские сервисы повсюду. Европейские – нет.

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

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

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

  • Полоса прокрутки (2)

    Небольшое продолжение про Ютуб — и больше поднимать эту тему не буду.

    Всякими махинациями я добился того, чтобы полоса была под видео, а не на нем. Для этого классу ytp-chrome-bottom добавляется свойство bottom: -60px или около того. Но проблема пришла откуда не ждали, и даже не одна.

    Дело в том, что тулбар появляется только когда наводишь курсор на плеер. Так вот: если тулбар вне плеера, то наведение мыши на него ничего не дает — он остается невидимым. Простыми словами, перестает работать.

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

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

    Ну и в целом сложность: штук двадцать вложенных дивов yt-playeryt-main-containeryt-main-inneryt-video-containeryt-video-node и так далее. Каждый что-то перехватывает и наследует. Дебажить этот цирк — то еще удовольствие.

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

  • Полоса прокрутки

    Некоторое время назад я жаловался на плеер Ютуба, мол, полоса прокрутки и кнопки залезают на видео. На это можно повлиять при помощи своего css-файла. Для Фаерфокса и его форков инструкция такая:

    • Определить папку с профилем. Для этого нажать Help -> More Troubleshooting Information -> Profile Folder -> Show in Finder

    • Создать в ней папку chrome

    • В ней создать файл userContent.css с таким содержанием:

    @-moz-document domain(youtube.com) {
     .ytp-chrome-bottom {
         position: relative !important;
         bottom: -15px !important;
     }
    }
    
    • Зайти в about:config и задать свойсво toolkit.legacyUserProfileCustomizations.stylesheets в true

    • Перезапустить браузер.

    В результате тулбар окажется сверху с зазором в 15 пикселей. Картинку прилагаю:

    Мысли следующие: пока настраивал папки-файлы, поразился этому дурдому. Почему ради простой задачи нужно совершать пять шагов? Неужели нельзя сделать примитивный textarea, куда вводишь текст и он сохраняется в файл? Почему браузер нужно перезагружать? Почему папка называется “Хром”? При чем тут хром? Какая-то жесть.

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

    Впечатления от новой полосы интересные. Чувство такое, что вместо 90% контента теперь видишь все 100%. Внезапно, надписи внизу читаются; у людей есть колени и ноги; у ведущего в углу кадра есть лицо. Раньше ничего этого не было, а теперь — пожалуйста.

    Могу спокойно читать субчики в “Зеленом слонике”, а раньше не мог. То-то же.

    Для Хрома и форков инструкции нет, но полагаю, найдутся расширения.

    Ни в коем случае не продавливаю свое решение — по-хорошему его надо улучить. Но какую-то свежесть оно дает, попробуйте.

  • Оружия черепашек-нинздя

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

    Итак, первый уровень, изи: Леонардо, который в голубой повязке, владеет двумя мечами. Это всем понятно.

    Второй уровень, нормал: что у Микеланджело, который в оранжевой повязке? Это нунчаки: две палки на цепи, которыми проще убить себя, чем противника.

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

    Последний уровень, найтмеар: что у Рафаэля, черепашки в красной повязке? Когда говорят, что трезубец, хочется кататься по полу от отчаяния. Это саи. С-а-и! Один сай, два сая, многое саев. Гугл-док не понимает и предлагает замену, но вы-то понимаете! Такие кинжалы с двумя лепестками по краям. Идея в том, чтобы принять меч противника между зубцами и резко повернуть. Твой рычаг больше, чем у противника, и он теряет оружие.

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

  • Дизайн REST API

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

    Во-первых, версия v1 в урле. Она всегда меня забавляла. Я работал во многих стартапах, и ни один из них не сменил версию. Гораздо чаще стартапы закрываются и продаются. Я считаю, апишка должна быть одна, и со временем какие-то методы добавляются, какие-то снимаются с обслуживания. А в той нумерации, что на скриншоте, слишком много пафоса. Примерно как объявить счетчик с шестью нулями для значения, которое в лучшем случае достигнет двойки. Даже если предположить, что появится новая апишка, просто сделай другой урл: /api/grpc или /api/graphql.

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

    Еще одна забавная вещь: метод PUT не может покрыть все изменения, поэтому в ход идут костыли: частички :report, :reopen на конце урла. Это некрасиво, потому что по-хорошему операция определяется HTTP-методом, а тут урлы. Тот случай, когда шубу заправляют в трусы, а она не лезет.

    Ну и расцветка Сваггера как у цыган: зеленый, голубой, оранжевый, красный… все собрали.

    На мой взгляд, нормальная апишка должна быть по принципу RPC. Один урл с двумя параметрами: action и params. Метод один – POST. Внутри словарик с функциями и схемами, по которому раскидываются запросы. Все. Документашка со схемами строится одной командой.

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

  • Просто берите SQLite

    Тезис к размышлению: при помощи Postgres и SQLite можно решить (почти) все насущные задачи. Без распределенных кэшей, очередей, облачных паб-сабов и так далее.

    Насчет Постгреса все ясно: я уже писал о нем отдельно. А что SQLite?

    Замечаю одно и то же: иной раз требуется хранить в памяти какие-то данные. Сначала это словарь вида id -> сущность. Потом нужен поиск по полю сущности. Строится обратная мапа поле -> список сущностей. Потом нужная вторая сущность и перекрестные ссылки. Код превращается в ад.

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

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

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

    Если данные не помещаются в памяти, можно скинуть их на диск. Красота же?

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

    За счет SQLite можно выкинуть просто нелепое количество кода и самописных решений. Разумеется, не всегда – но по моему опыту, очень-очень часто.

  • О кложурных редьюсерах

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

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

    Как вы знаете, Кложа хороша своими коллекциями – это прямо алмаз, одно из немногих утешений в айти. Вместе с коллекциями прилагаются штук триста функций: всякие map, reduce и так далее, включая экзотику.

    Начиная с какой-то версии в Кложу завезли параллельные map и reduce. Пакет называется clоjure.core.reducers. Сигнатуры параллельных функций почти идентичны оригиналам. Идея в том, что ты такой хоп – заменил reduce на r/reduce, и вычисления раскидались по ядрам. Нашлись коллеги, которые, не читая документации, так и сделали – а я пожал их плоды.

    Есть огромных файл, где каждая строка – джейсончик. Нужно построить мапу {id -> entity}, чтобы быстро дергать из нее сущность по айдишке. Скажем, в Питоне это делается так:

    {entity["id"]: entity
      for entity in file.read_lines()
    }
    

    В Кложе это тоже решается тремя строчками. Но коллега использовал не простой reduce, а который параллельный. Логика такова: коллекция большая, пусть колбасится параллельно. И вот я вызываю функцию, которая строит эту мапу, и замечаю – в ней нет половины ключей. Пропали. Что такое?

    Дебажил я передебажил и выяснил вот что.

    В документаци clоjure.core.reducers написано, что коллекции не всегда обрабатываются параллельно. Критериев несколько, например коллекция мала и нет смысла ее делить. Но главный критерий таков: коллекция не должна быть ленивой. А те коллекции, что коллеги читают из файла, ленивы. Это нормально, потому что файл огромный и читать в память все разом нельзя. Но получается, что вся параллельность идет псу под хвост – параллельные r/map и r/reduce сводятся к последовательным аналогам.

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

    Почему же у меня возникла ошибка? Дело в том, что я передал вектор – не ленивую коллекцию. Она попадала под критерии параллельности, и произошло вот что. Пакет clоjure.core.reducers использует подход ForkJoin. На этапе Fork коллекция бьется на части, и каждая часть обрабатывается в своем потоке. Получаются, скажем, два словаря {id -> entity} для каждой части. Далее наступает фаза Join – их нужно объединить. Функция r/reduce принимает дополнительную функцию для сборки финального результата, но ее не передали. А если ее нет, вызывается редуцирующая функция. Она приняла два словаря и неправильно их обработала, в результате чего пропала половина данных. Нужно было передать туда функцию merge, но никто не знал, для чего это в принципе.

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

    {"a" [1 2 3], "b" [5 4 1]}
    

    Если дать эту задачу на параллельное вычисление, они могут вернуть что-то такое:

    {"a" [1 2], "b" [5]}
    {"a" [2 3], "c" [11]}
    

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

    {"a" [2 3], "b" [5], "c" [11]}
    

    , то есть из “a” пропадет 1. Ошибки не будет, все пройдет молча, и догнать причину будет сложно. Правильная функция объединения будет такой:

    (partial merge-with into)
    

    С ней получим результат {"a" [1 2 3], "b" [5], "c" [11]}.

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

    В сухом остатке:

    • человек подключил либу для параллельных вычислений

    • вычисления всегда протекали последовательно

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

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

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

  • Обновление Ютуба

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

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

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

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

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

    Я не понимаю, почему панель должна налезать на видео? Кто сказал, что видео — это область, где можно ставить кнопки, бегунки и прочее? Нельзя! Оставьте видео в покое! На нем ничего не должно быть.

    Опять же, не понимаю, почему нельзя сделать опцию “панель снизу”? Это буквально пара мест в CSS, чтобы сместить блок вниз.

    В общем, главная медиа-площадка планеты — отличный пример того, как делать не нужно.

  • Postgres в Телеграме

    Лишний раз убедился, что писать на чужих площадках — гиблое дело.

    Где-то три года назад я вступил в группу Postgres в Телеграме; там было 11 тысяч пользователей. Только начал писать — какие-то тролли наставили кучу дизлайков. Я даже не понял: это заговор или что? Вышел.

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

    Что ж, если не хотите меня, то так и быть. “Мэссадж понятен даже идиоту” (с). Вышел, и третьему разу уже не бывать.

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

Страница 3 из 104