Стендап Сьогодні

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

Підписатись на RSS · 📢 Канал в Telegram @stendap_sogodni

05.05.2024

IAM в AWS та Google: різні речі

Нещодавно зробив коротке занурення в Google Cloud (бо я резидент AWS.) Коли працюєш з Terraform, може створитися уявлення, що всі хмари однакові, тільки ресурси мають різні назви. Виявилося навпаки, що система зі знайомою назвою IAM (“Керування особами та доступом”) працює фундаментально інакше.

В AWS IAM я звик, що права складаються з політик, а в політиці міститься перелік ресурсів та дозволених операцій. Мені завжди це здавалося логічним; хіба що є проблема адресації ресурсів. Проте документація по IAM просто чудова та з нею, крок за кроком, можна все зібрати.

…А в Google IAM я до кінця не зрозумів, але ніби права мають модель графа авторизації. По-перше, ролей тут немає, тільки користувачі або “сервісні” користувачі. А по-друге, дозволи призначаються звʼязкою користувача та ресурсу. Звʼязка ця містить “роль”, тобто визначає дозволені операції.

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

Таке я не люблю. Політика в AWS виражає намір, який інформує її зміст. В Google по переліку звʼязків можна побачити, що існую, але не зрозуміти, чому. На щастя, якщо є Terraform, то намір можна викласти там.


04.05.2024

Будні проєкту на Ruby та Go

В екосистемі Ruby мова Go має конкретне місце: на ній можна написати найбільш навантажену частину проєкту. Я давно вже рекомендую цей підхід та вважаю, що саме мова Go є найкращою “швидкою” мовою для рубістів, в першу чергу через свою простоту.

Але “переписування на Go” безповоротно змінює проєкт. Тепер для кожної нової фічі в нас є вибір: писати на Ruby, або на Go. Чим більше працюєш з Go, тим далі критерій навантаженості зсувається від, умовно, найвибагливішого запита до API, до будь-чого що має потенціал стати “гарячим шляхом” в проєкті. Чим більше коду Go написано, тим більше нового коду буде зручно написати на Go.

Чи я хочу сказати, що доля такого проєкту — бути переписаним на Go повністю, то це зовсім не так. Бо в Ruby та Rails є купа того, чого на Go немає — починаючи, наприклад, з інтеграційного тестування. Виходить, доведеться завжди балансувати.

Мені, особисто, дуже подобається писати все на Go. Тому я компенсую це пошуком актуальних переваг з боку Ruby. Наприклад, потрібно було написати задачу, що повторюється за графіком. Саму задачу мені дуже легко написати на Go. Але це була б AWS Lambda. А графік запуску довелося б робити на AWS EventBridge. Та ще все це конфігурувати. Та підтримувати.

А в Ruby вже є готова система задач - Sidekiq. Для неї достатньо додати рядок конфігурації на кшталт cron та графік готовий. Чи значить це що всі заплановані задачі краще робити на Ruby? Знову ні — якби задача була частішою, або обробляла багато даних, то зручність вже не в пріоритеті.

Одним словом, нема такого, що все чітко: тут Ruby, тут Go, нічого не перетинається. Вибір мови стає повсякденним питанням з вагомими наслідками.


03.05.2024

Винятки в мовах програмування

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

В Ruby винятки — це механізм обробки помилок будь-якого рівня — від SyntaxError до специфічної помилки бізнес-логіки. Ну, тобто, за домовленістю на “однозначні” помилки можна просто повернути nil, а все, що не вкладається в nil, утворює виняток. Та, в Ruby дійсно можна спіймати навіть помилку синтаксису або вихід з програми. Зазвичай так роблять не навмисно, а тому, що ловлять пращур всіх помилок: Exception - про це в мене є дуже стара стаття.

В JavaScript якийсь час механізм винятків якось ігнорувався, тобто їх обробку рідко можна було побачити. Не в останню чергу тому, що вони не були сумісними з Promise та рівночасним кодом. Втім, з впровадженням синтаксису async/await блоком обробки винятку try ... catch можна охопити скільки завгодно асинхронної логіки, тому винятки знову повертаються в моду. Втім, в JavaScript немає чітко визначеної класифікації винятків. Навіть в TypeScript неможливо зазначити, якого типу ти очікуєш помилку.

В Go виняток, тобто паніка — це, за посібником, реакція на помилку розробника. Тобто таку, яку виправити може тільки зміна в програмі, а не в даних чи обставинах. Решта помилок повертається з функцій напряму. Фактично в більшості випадків ми просто емулюємо механізм винятків вручну та передаємо помилку назовні.

У Swift функція мусить явно оголосити, що вона створює винятки, модифікатором throws. Відповідно, функції, що її викликають, або обробляють винятки, або успадковують модифікатор. З типом Optional на винятки залишається не так багато потреб — зазвичай це помилки вводу/виводу. Але деякі помилки на кшталт “індекс за межами масиву” вважаються “помилками розробника” та їх спіймати взагалі неможливо - “сам винний!”. Тож паніки в Go це ще дружня поведінка!


02.05.2024

Kafka як барʼєр від стресу

Я вже писав як Kafka розʼєднує сервіси за навантаженням: тобто звільняє джерело подій від турбот про навантаження на споживача. Проте є й інший аспект турбот: надійність. З ненадійними споживачами доводиться перестраховуватись, щоб уникнути каскадних збоїв.

Та ось в чому річ. За останній рік Kafka була, без перебільшень, найбільш стабільним компонентом з тих, з якими мені доводиться працювати. Краще ніж “хмарні” амазонівські сервіси на кшталт SQS та Firehose. Тому Kafka виступає надійним буфером між сервісами, який завжди готовий прийняти події… та притримати їх стільки, скільки це потрібно.

Є декілька обʼєктивних причин такої надійності. В першу чергу, це простота - Kafka нічого не робить з даними. В ній немає схеми — а значить, схему не можна порушити. Скільки разів було таке, що міграції в базі розійшлися з клієнтом, та це зупинило запис? З Кафкою такого ніколи не буде.

Також в Кафки гарний принцип надмірності. Кафка зазвичай побудована кластером. Та клієнти знають не одну вхідну адресу сервера, а багато. При збої одного з серверів клієнти просто переходять на інший. Це стає до нагоди й при оновленнях ПЗ.

Якщо ви стикнулися з ситуацією, де отримувач даних ненадійний, та доводиться вводити повторні спроби та інші механізми захисту — подивіться на Кафку.


01.05.2024

Optional у Swift

Продовжуємо рубрику “аспекти дизайну мов програмування”. Сьогодні не було настрою робити щось по трекеру, тому просто виправляв скарги лінтера. Одна з частих скарг — на так званий “force unwrapping”. Про неї й хочу поговорити.

Відсутність значення у Swift реалізована механізмом Optional. Optional приймає або значення загорнутого типу, або nil. Це єдиний випадок, коли звернення до значення неприпустиме, ніякого більше “порожнього посилання” або “непризначеної змінної”. Та головне, що тип Optional обовʼязково потрібно “розгорнути”. Або безпечною перевіркою, або примусово — що може призвести до фатальної помилки.

Мене здивувало у Swift те, скільки методів в стандартній бібліотеці повертають Optional. Наприклад: методи Array.first, .last, .min, .max. Бо дійсно, якщо масив порожній, то жодних елементів в ньому не буде. (На жаль, операції з індексами позбавлені такої безпеки та можуть призвести до помилки.)

З наївним написанням умов це трохи дратує: я ж перевірив, що масив не порожній!

if !items.isEmpty {
  maxItem = items.max! // Примусово розгортаємо!
  // ... щось зробити з maxItem
}

В багатьох випадках таку непряму та тому небезпечну перевірку можна замінити на явну, безпечну:

if let maxItem = items.max {
  // ... щось зробити з maxItem
}

Можна помітити зворотний порядок гілок: я звик перевіряти if item == nil як особливий випадок, а потім вже працювати з ним; у Swift для того є альтернативна конструкція guard, яка розгортає тип без вкладення:

guard let maxItem = items.max else { return }
// ... щось зробити з maxItem

Взагалі мені дуже подобається такий підхід до відсутності значень, бо він виділяє рівно один механізм та не дозволяє просто його ігнорувати. Проте, якщо в Go надійна програма завжди здобрена перевірками if err != nil, то у Swift так само багато перевірок guard.


30.04.2024

ID в базах Apple

…А наробило мені проблем вчора те, що чомусь в обʼєктів в базах CoreData / SwiftData немає нормальних ID. Тобто, ідентифікатор є — має клас PersistentModelIdentifier. Але він не кодується ані в число, а ні в рядок. Спроба кодувати його в JSON видає ось таке:

{
  "implementation": {
    "isTemporary": false,
    "storeIdentifier": "345F4CA3-CCAE-49D8-8434-EFECA9056B68",
    "primaryKey": "p2640",
    "uriRepresentation": "x-coredata://345F4CA3-CCAE-49D8-8434-EFECA9056B68/Sample/p2640",
    "entityName": "Sample"
  }
}

Так, все це з одного ID. Бачите, теоретично, наприклад, поле urlRepresentation могло б використовуватись як рядкове кодування. Але його не дають повернути назад у PersistentModelIdentifier (або я не знайшов, як.) Ба більше, навіть отримати це значення я не можу — тільки через JSON. Звісно, весь цей JSON разом можна декодувати в ID, але він абсурдно надлишковий.

… В базах Apple ID не дуже потрібний, оскільки вони мають обʼєктну природу. Замість ID ми зберігаємо просто посилання на інші обʼєкти. Та це дійсно дуже круто — бо дозволяє не думати про завантаження асоціацій.

Але — деколи ID все ж потрібні (наприклад, для побудови власних URL чи для експорту.) Що я роблю: генерую для кожного обʼєкта UUID, який зберігається окремим полем. Використовується він тільки для експорту. Чому UUID, які я так не люблю? Бо є ймовірність збігів (якщо застосунок встановлений на двох пристроях); плюс для генерації UUID є стандартний клас. А швидкодія мене мало турбує, бо ці UUID не є первинним ключем.

(PS: якщо придивитись, то ID CoreData теж містить в себе UUID. Але, наскільки я розумію, це UUID пристрою, а локально обʼєкти мають чисельні ідентифікатори.)


29.04.2024

Експорт: JSON або CSV?

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

Чому CSV? Насправді перший варіант використовував JSON, але він вийшов заскладний. Тоді я замість того, щоб спростити, звернувся до CSV. Цей формат приваблює своєю максимальною простотою: як для генерації, так і “на вигляд”. От тільки для машинного читання CSV неприємний. В ньому майже немає перевірки на помилки — рідкий документ не є коректним CSV. Немає й типів. Також, якщо дані містять хоч якусь вкладеність, то простота зникає.

Одним словом, я б в CSV відвантажував тільки дані суворо табличного формату там, де їх будуть завантажувати в табличний редактор. Журнал транзакцій — так. Книгу контактів — можливо. Але в загальному випадку JSON простіше для сприйняття навіть користувачами. Якщо вже це такі користувачі, що будуть копирсатися в експортах. До речі, з JSON завжди можна зробити CSV - навіть в утиліти jq для цього є оператор.


28.04.2024

Синхронізація бази через iCloud: магії тут занадто

Повернувся до розробки таймтрекера. Взагалі я сьогодні нарешті доробив надійний експорт та імпорт, бо в застосунку сидять мої реальні дані, та страшно було робити структурні зміни. Проте там нічого цікавого, тому вирішив також налаштувати синхронізацію бази, що також розблокує версію для macOS (а версію для macOS хотілося б мати хоча б для того, щоб там зʼявлялися пінги, коли сидиш за компʼютером.) Взагалі, синхронізація бази через iCloud називається CloudKit та є потужною перевагою платформи Apple.

Я вам запропоную дві інструкції. Перша — від Apple - розповідає про якісь дикі речі, які не стикуються з кодом ініціалізації, який генерується із застосунком. Друга — від популярного блогу — каже що “it just works”, та коду взагалі не потрібно.

Де ж правда? Правда в тому, що замість синхронізації в логах була помилка Invalid bundle ID for container; питань на форумах про це вистачає, а розвʼязки є варіантами тропу “спробуйте видалити та створити заново”.

Тут випливає, певно, найнеприємніший аспект розробки для Apple: надто багато аспектів приховані або абстраговані. Наприклад, оця помилка ніби свідчить про відсутність доступів у застосунку до бази; проте доступи зазвичай повинен видати сам XCode. А сидять доступи в сервісі “Certificates, Identifiers & Profiles” у розробницькій панелі керування. Причому ззовні все всюди написано правильно. А всередині щось розʼїхалось, та щоб це виправити немає краще шляху, ніж щось (ID застосунку, профіль розгортування, ID контейнер тощо) видалити та спробувати створити наново.

Зате зрештою все розвʼязалося, синхронізація працює, дані є. Дійсно не треба було ані дописувати код, ані налаштовувати базу — все автоматично-магічно. До речі, архітектура iCloud дуже цікава, ось стаття.


27.04.2024

Використання Jupyter для Javascript

Не все тут так просто. Як я швидко зрозумів, Jupyter має фундаментально клієнт-серверну модель: код виконується на сервері та віддає клієнтові тільки результати виконання. Для Python саме це й потрібно; до того ж як я нарешті зрозумів, так зручно працювати з великими даними, бо в браузер з них потрапляє тільки обмежений розріз.

А я хотів долучити Jupyter до подальшої розробки візуалізації даних з реєстратора. Технічно, є купа “ядер” для підключення Javascript; проте всі вони обовʼязково працюватимуть на сервері. З того, що я бачив, найкращий варіант — це ядро від deno, бо воно має офіційну підтримку; решта проєктів або не закінчена, або не підтримується.

Проте все одно, ми будемо працювати суто в серверному оточенні, та, скажімо, створити canvas просто так не вийде. Є там якісь емульовані бібліотеки, але це зовсім не те саме, що малювати в браузері. Плюс deno має езотеричні правила імпортів тощо та зовсім не хочеться вивчати їх тільки заради Jupyter.

Трохи подивився на інші проєкти та знайшов, наприклад, Starboard. Тут весь код виконується в браузері, тож можна робити практично будь-що. Але немає підтримки Typescript або JSX.

Також, помітив, що все ж візуалізації даних — це найпотужніша функція Jupyter порівняно з іншими середовищами. Бо так, я можу написати код просто в редакторі та навіть перезапустити його без втрати стану за допомогою Hot Reload, проте щоб побачити зміст змінної (та ще й у вигляді графіку чи таблички) доведеться зробити купу рухів.


26.04.2024

Риштування в Rails - забута перевага

Коли я тільки починав працювати з Ruby on Rails, тобто десь у 2008 році, в Rails була одна перевага, про яку я абсолютно забув та згадав тільки тому, що у SwiftUI нічого схожого немає.

Йдеться про риштування, тобто scaffolding. Це генерація коду для нової моделі: від інтеграції з базою даних до CSS. Так, зокрема хочу виділити саме генерацію фронтенду: однією командою можна було утворити набір шаблонів для переліку, перегляду та редагування цієї модельки.

Причому шаблони відразу були частиною застосунку, а не чимось окремим як, наприклад phpMyAdmin. В багатьох випадках залишалось тільки дописати власну логіку — та MVP готовий.

Технічно, риштування й досі працює, проте все так же ж генерує статичний код HTML. А я вже забув, коли останній раз робив застосунок без “активного” фронтенду. Тож виходить, що й риштування у своїй стандартній формі більш не корисне.

Одним словом. Як і Jupyter, риштування допомагало отримати доступ до даних та почати щось з ними робити. Якщо повернутись до SwiftData/SwiftUI - як типового сучасного середовища — то щоб відредагувати будь-що в базі даних, доведеться написати все це риштування вручну. Зазвичай це стільки зусиль, що ніхто так не робить, а обмежується необхідною функціональністю.

Цікаво — чи залишилася сама можливість блогу за 15 хвилин у світлому та простому минулому, або все ж можна відтворити її з сучасними технологіями?