Євгеній Гизила
504 subscribers
47 photos
1 file
98 links
Автор @hyzyla

Пишу про різне зі світу веб розробки: власний досвід, Python, React, TypeScript, стартапи і власні думки
Download Telegram
Маю ідеальний план вічного заробітку для ІТ-аутсорсу: держава створює тендер на розробку нової системи, бізнес подає заявку з мінімальною ціною для виграшу в тендері (навіть, якщо ця сума не покриє половини витрат). Після перемоги в тендері, компанія розробляє систему згідно з правилами. Але робить API для цієї системи настільки незручним, заплутаним і жахливим, що пару програмістів по ходу розробки звільняються з цієї компанії. Звісно ж не забути написати документацію без прикладів і пояснень.

Далі сісти і чекати, поки до тебе прийдуть компанії за платною консультацією, з приводу інтеграції до цієї системи.

Якщо ви не вмієте писати жахливий API, то можете звернутися до мене за платною консультацією, я покажу приклади в реальному житті і ще підкину пару хитрих ідей 🙃
На днях стало цікаво як на практиці реалізовують безшовний вхід у декілька сервісів за допомогою одного логіна. В теорії я знав, що круті компанії, по типу Google чи Facebook, для цього використовують стандарт OAuth 2.0. Але проблема в тому, що стандарт містить багато деталей, щоб писати реалізацію самостійно з нуля. Тому пішов шукати готові рішення, щоб нічого не писати, але так щоб ні за що й не платити.

Для Python знайшов бібліотеку authlib, в якій є все що потрібно, щоб зробити власні OAuth 1.0/2.0 сервери (зробити вхід під одним логіном у декілька сервісів) або ж підключатися до сторонніх OAuth серверів (вхід за допомогою Facebook чи Twitter). А ще в цій бібліотеці є інтеграція з популярними веб фреймворками Flask, Django, Starlette і FastAPI. (aiohttp немає 😢). Хоча би за це варто зайти до них на github і вліпити їм зірочку.

Також знайшов компанію, яка розробляє вже все готове, для ідентифікації користувача: OAuth2 сервер hydra, сервер авторизації keto і ще щось там. І до цього всього все це опенсорне, сама ж компанія заробляє на консультаціях. Виглядає все круто і є велика підтримка спільноти у цих сервісів.

В цілому хочеться спробувати на практиці, запустити OAuth2 сервер. набити пару шишок, що зрозуміти чи легше писати кожного разу свою автентифікацію чи запустити якийсь опенсорсний сервіс чи взагалі може легше і дешевше заплатити якомусь хмарному провайдеру.
Вже майже рік чекаю поки Safari добавить пітримку gap для flexbox, бо хочеться в одному місці написати відступ і навіть не чіпати стилі дочірніх елементів:
.flex-container {
display: flex;
gap: 10px;
}

замість славнозівсного:
.flex-container {
display: flex;
margin: -5px;
}

.flex-wrapper {
margin: 5px;
}


Зарза за даними з caniuse.com лише 70% користувачів мають браузер з підтримкою gap 😔 і поки основний блокер це Safari в яких ще близько 16% ринку бразуерів.
Для мене кожен раз є відкриттям, що в python тип Decimal опрацьовує float і str по різному
Недавно дізнався, що в AWS S3 є безкоштовний і простий спосіб зекономити гроші. Для зберігання даних пропонується 4 різні класи сховищ, кожен з яких повільніший для зчитування даних і відповідно дешевший за попередній клас. Для прикладу на стандартному класі треба заплатити $0.0245 за гігабайт, той же час наступний клас сховища пропонує $0.0135$ за гігабайт і затримка на доступ до файлу в межах 1мс.

Як бонус цьому всьому можна налаштувати автоматичне перенесення з одного класу в інший, якщо файл не використовувався довгий час і при цьому файл залишиться за старим ключем, тому нічого нічого не прийдеться міняти у своєму коді.
Foreign data wrapper

Недавно читав книгу з кабанчиком про бази даних і там пислаи про таку штуку як foreign data wrapper. Якщо коротко, то це можливість підключити будь-яке сховище даних до бази даних і його аналізувати через SQL запити. Для PostgreSQL є обгортки для ексель файлів, sqlite, csv, kafka, mongo і кучі всього іншого. Весь список можна знайти тут. Поки не придумав де це може бути корисно, та і маю сумніви, що на практиці хтось аткивно користується, але сама можливість так робити це вже цікаво.
Розширення для браузера

Недавно робив друге в своєму житті розширення для браузера і, оскільки стек для розширень звичайний як для веб (JS, HTML і CSS), то хочу поділитися власним досвідом публікації розширень. Мої розширення не збирають ніяких даних і лише автоматизовують, ті дії, що і так вже є на сайті, тому до мене було менше питань зі сторони модераторів розширень, ніж могло б бути. Якщо зберігаєте хоч якісь дані користувача, то будьте готові надати докази «браузерній поліції» для чого вам ці дані потрібні.

Майже всі браузер з недавніх пір почали підтримувати стандарт для розширень, тому можна один раз написати, упакувати і відправляти один і той самий код в каталоги розширень без змін (окрім Safari звісно ж).

Найпростіше публікувати для Firefox: заповнюєш невелику форму, додаєш опис, завантажуєш збірку розширення, один-два дні його вручну перевіряють модератори і публікують, якщо не знайдуть ніяких порушень. Наступні публікації відбувають в автоматичному режимі і публікація нової версії займає менше хвилини. Просять також додати код розширення, якщо збірка містить JS код, який автоматично був згенерований. За весь час модератори Firefox мені ще жодного разу не відмовляли в публікації, тому посилаю мої промені любві до них❤️. Також здається для Firefox розширення можна не завантажувати в магазин, а поширювати на своєму сайті самостійно, але я цей варіант не пробував.

Для публікації в Chrome Web Store потрібно пройти майже ті самі кроки, що й для Firefox, хоча з деякими нюансами. В першу чергу треба додатково написати модераторам мотивацію чому ті чи інші дозволи тобі потрібні. Якщо розширення має працювати для певного хоста, то треба аргументувати, яке ти маєш відношення до цього хоста. Кожна нова версія розширення модеруютьється вручну, запит на модерацію займає 3-4 дні і легко отримати відмову. Для прикладу, причини чому мені відмовляли тричі: не зрозуміло, що робить розширення, немає privacy policy і недостатня аргументація чому розширення повинне мати доступ до хоста. В цілому публікувати в Chrome Web Store втричі важче і повільніше, ніж в каталог Firefox, тому ставлю трійку з п‘яти для Google.

Щоб опублікувати розширення для Safari треба мати MacOS, Xcode і приєднатися до Apple Developer Program за 99$ в рік :classic. Тому для Safari я не осилив публікацію розширення.

Про саме розширення яке робив розкажу трохи згодом окремим дописом.
This media is not supported in your browser
VIEW IN TELEGRAM
Kibana Clicker

Нарешті Google опублікував розширення для браузера, про яке обіцяв розповісти в минулому дописі. Вже встиг про нього розказати на роботі, тому розкажу ще й тут. Це розширення браузера має одну мету — пришвидшити пошук логів в Kibana. Для прикладу, якщо є лог з полем user_id, що має значення 123456789, то розширення вбудовує замість цього значення посилання на сторінкувже з увімкненими фільтром “user_id: 12345678”. Теж саме відбувається для інших значень лога. Раніше, щоб знайти всі логи користувача треба було скопіювати значення user_id, відкрити нову сторінку і сформувати запит руцями. З розширенням процес має скоротитися до одного кроку — перейти по посиланню з вже сформованим запитом.

github, firefox, chrome

Якщо будуть питання, зауваження чи ідеї, то пишіть і коментарях
Hot restarts for everything

Коли щось розробляю, то майже завжди надаю перевагу інструментами, що дозволяють відразу бачити результат, після того, як я щось змінив в коді. Така особливість називається hot reload або hot restart. Для прикладу, коли міняю код мобільного додатку на Flutter, то Flutter автоматично підтягує ці зміни в мобільний додаток і створюється відчуття, що ти модифікуєш додаток на льоту, а не його код.

Одне “але” з цією особливістю, що не всі інструменти підтримують таку можливість. Саме для таких випадків є класна утиліта watchexec, яка вміє слідувати за файлами і перезапускати команду, якщо щось змінилося в тих файлах. Для прикладу, команда, що буде перезапускати python скрипт, якщо відбулися зміни в .py файлах буде виглядати так:

watchexec —exts py -- python main.py

Якщо дехто з вас вже знає про цю утиліту, то, я впевнений, мало хто знає про recamshak/watchexec Docker образ, який місить в собі цю утиліту скомпоновано статично. Тобто, щоб у ваш Docker контейнер додати цю утиліту достатньо буде скопіювати її собі з цього образу і використовувати як я описував раніше.

COPY --from=recamshak/watchexec /watchexec/watchexec /bin/
How to feel rich for free

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


Use CapsLock to switch keyboard layout {CURRENT_OS}


Підсадив дівчину, підсадився я сам, тому рекомендую і вам. Після першого тижня будете дивуватися чому не зробили це раніше.
.git-blame-ignore-revs

Коли проєкти ростуть, то буває ситуація, що треба масово відформатувати багато файлів: додали новий форматер чи старий змінив форматування, або ж команда вирішила перейти з tab на пробіли для відступів у коді. Проблема таких змін, що git тебе запише, як того хто останній раз змінював код у файлі. Все б то нічого, але команда git blame для такого файлу переважно буде містити одну людину, хоча може бути, що ця людина тільки прийшла на проєкт і їй поставили задачу автоматично відформатувати код.

Тому, щоб git blame містив дійсно тих людей, які міняли код руцями, недавно в git додали параметр --ignore-revs-file. На вхід цей параметр приймає назву файлу зі списком хешів комітів, які треба пропустити і не показувати в історії змін файлу. Щоб кожен раз не вводити цей параметр, його можна визначити в персональних налаштуваннях git для проєкту git config blame.ignoreRevsFile <filename>. На жаль, цей параметр не можна задати на рівні репозиторію, тому кожен, хто буде працювати над кодом і захоче пропустити такі механічні зміни, має виконати цю команду в себе локально.

З іншої сторони, більшість розробників дивним чином домовилися про те, що файли зі списком комітів для ігнорування будуть називатися .git-blame-ignore-revs (приклад Chrominum). Тому можна хоч сьогодні задати глобально ці налаштування на власному компі і додати всіх з кого знімаються звинувачення в погано написаному коді 😬:

git config --global blame.ignoreRevsFile .git-blame-ignore-revs
Telegram боти на Python

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

Я відгукнувся написати цього бота і мені скинули іншого схожого телеграм бота, щоб я міг взяти його за основу. Той код здався мені сильно громіздким, бо тіло запитів до Telegram API формувалося вручну, стан розмови визначався за якимсь неявними ознаками і тд. Тому вирішив бота для ТРо писати з нуля, взявши python-telegram-bot. Це найпопулярний фреймворк на Python для створення Telegram ботів, має типизовані функції, низькорівневі штуки приховані від користувача і багато прикладів.

Цей вибір виявився на всі 100% виправданий, бо за два дні бот був готовий, складався з одного файлу і лише двохстарядків рядків коду. Він вмів збергіати фото, відео, голосові і відеоповідомлення, мав стейт машину розмови і вмів зберігати стан розмови після перезавантаження. Я навіть отримав задоволення від написання, бо всі ідеї які в мене виникали, з цим фрейморком було легко і швидко реалізувати.

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

P.S: цей бот використали один раз, тому тут успішною виявилася лише історія з тим, що на нього витратив хоча б небагато часу.
FastAPI template

Останні пів року з’явилася нагода попрацювати з веб фрейморком FastAPI. Бекенд розробники на python гарантовано чули про цей фреймворк, бо він зараз шаленими темпами набирає популярність. З часом FastAPI не розчарував, на ньому дійсно зручно і швидко писати бекенд, тому особисто я нові проєкти починав би писати на ньому.

Одна з проблем, яку фрейморк не вирішує — це як правильно організувати структуру проєкту. Тому для себе зробив суб’єктивний шаблон невеликого сервісу, в якому вже структурував код і налаштував типові інсутрменти для роботи з кодом. Проєкт виклав на github fastapi-template і запрошую звернути увагу на мій шаблон, якщо плануєте робити новий проєкт на FastAPI.
class Status(str, Enum): ...

В Python є один лайфхак, яким я часто користуюся на роботі. Коли створюю новий enum, то відразу ж успадковую клас не тільки від типу Enum, а також і від типу str. Ця невелика деталь робить роботу з enum простіше і дозволяє порівнювати значення enum з рядком без перетворення у Enum тип.

Для прикладу: на картинці вище клас Status це звичайний enum. Щоб порівняти значення цього enum з будь-яким рядком, то цей рядок спочатку треба перетворити у значення enum Status("success") і лише потім порівнювати. На цій же картниці є також клас SuperStatus — це наш розумний enum, значення якого будуть мати одночасно тип SuperStatus і str , і тому без пробелем буде працювати порівнння цього значення з іншим рядком.

До речі, в python 11-ої версії з’явиться тип StrEnum, який буде мати ті самі властивості, що й наш SuperStatus
UUID

На роботі декілька раз помічав як колеги беруть рядок UUID, змінюють декілька символів і пробують використати цей рядок десь у системі для тестування. Інколи такий UUID підходить і працює без проблем, а інколи валідатори видають помилку, що цей рядок не є валідним UUID.

В чому ж проблема? UUID з першого погляду виглядає як рядок з випадкових літер і цифр розділених дефісами. Але якщо почитати визначення у Wikipedia, то стає зрозуміло, що UUID це 32 цифри у шістнадцятковій системі числення: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E та F. Для прикладу F у шістнадцятковій системі числення дорівнює 15 у десятковій. Саме тому будь-які інші символи, такі як R, T, чи S це все невалідні числа у шістнадцятковій системі числення і тому UUID, що містять такі символи не будуть проходити перевірки.

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

9453d6a2-40e5-40c8-ba0b-ca072844c121
7a452126-0a76-432b-a61a-57feb0c93bc4
8330cb79-287c-478c-a2f3-9b9c908638a2
488d549c-2a90-4839-a345-ab100f14005f
aa2e7eb0-f23a-44dc-ba93-431d34299c49
Де мій лист?

У веб-розробці типова ситуація, коли треба щось записати у базу даних, а потім відправити листа чи кудись надіслати сповіщення. Для прикладу, є інтернет-магазин і потрібно додати метод “Підтвердити замовлення”, який продавець з продажів буде запускати, після того, як огляне замовлення. За умовою задачі має оновитися статус замовлення і відправитися лист клієнту, про те, що його замовлення було підтверджено.

Можна реалізувати цю задачу в лоб: спочатку оновлюємо статус у базі, а потім робимо запит до поштового сервісу, щоб він надіслав листа. Інколи оптимізовують надсилання листа, створюючи задачу у celery, rq чи sidekiq, або ж публікуючи повідомлення у rabbitmq, kafka чи redis. Але рішення в лоб, як і з оптимізацією, так і без, має проблему, що ви не можете гарантувати, що лист буде успішно відправлено. В будь-який момент, після того, як ви записали в базу може трапитися мільйон ситуацій, щоб друга частина функції ніколи не виконалася: операційна система вб’є процес, бо він забагато зжер оперативки, вимкнулося світло на сервері, сервер не може під'єднатися до redis, kafka чи rabbitmq, космічна радіація змусила ваш сервер померти і т.д.

І найцікавіше у цьому всьому, що, за моїми спостереженнями, найпопулярніше рішення, яке обирають розробники (і я теж ) — просто ігнорувати проблему. Мовляв, ну загубиться лист, ну нічого страшного не станеться, клієнт не помітить, а як і помітить, то він вже звик до того, що в цьому комп'ютерному світі завжди щось губиться і щось ламається.
Євгеній Гизила
Де мій лист? У веб-розробці типова ситуація, коли треба щось записати у базу даних, а потім відправити листа чи кудись надіслати сповіщення. Для прикладу, є інтернет-магазин і потрібно додати метод “Підтвердити замовлення”, який продавець з продажів буде…
Загублене повідомлення

Попередній допис розповідав, що губити листи у веб-розробці це взагалі норма. Але що робити якщо, кров з носа, потрібно доставити те кляте повідомлення?

Найпростіший варіант — це у методі “Підтвердити замовлення” не відправляти листа, а лише зробити відмітку у базі даних, що лист по цьому замовленню ще не було відправлено. Залишилося запустити паралельно з сервером якийсь процес, який буде потихеньку діставати з бази даних записи без ціє відмітки, відправляти листи й позначати такі записи як виконані.

Таке рішення все ще не гарантує, що лист дійде до користувача (банально забули заплатити за поштовий сервіс), але ви точно тепер не заручник долі, а повністю контролюєте цей процес. Для прикладу, можна додати, якусь додаткову логіку, якщо через декілька спроб не вдалося відправити: сповістити менеджера з продажів, послати СМС чи скасувати замовлення.
Євгеній Гизила
Загублене повідомлення Попередній допис розповідав, що губити листи у веб-розробці це взагалі норма. Але що робити якщо, кров з носа, потрібно доставити те кляте повідомлення? Найпростіший варіант — це у методі “Підтвердити замовлення” не відправляти листа…
Transactional outbox pattern

Якщо в передньому дописі замінити листи на щось серйозне, то кількість ситуацій, де можна загубити повідомлення, стає в рази менше. Для прикладу записати у базу даних і водночас оновити індекс в ElasticSearch, послати повідомлення у Kafka для реплікації даних між мікросервісами чи збільшити лічильник у Redis. Обмеження в таких випадках залишаються такі ж самі як і з листами — надійно ви можете записати лише в одну систему (нашому прикладі це база даних).

Щоб надійно записати у дві системи придумали патерн transactional outbox. Він узагальнює те рішення з попереднього допису — ми робимо зміни в потрібній нам табличці у базі даних і додатково створюємо запис у нашій новій outbox таблиці. ⚠️ Важливо, щоб ці два записи були об’єднані в транзакцію бази даних. В цю нову outbox табличку можна записати або ID нашого замовлення, або всі поля замовлення, або лише ту частину яка змінилася. Потім окремий процес (publisher) поступово дістає з цієї таблички дані, їх відправляє і помічає як оброблені або й взагалі видаляє (а чому б і ні?). Отже, база даних гарантує за допомогою транзакцій, що запис відбудеться у обидві таблиці або у жодну. А publisher гарантує, що запис буде видалено з бази лише після того як він був відправлений.

Тому, кожен раз коли публікуєте повідомлення у Kafka, робите запит в Elasticseach, створюєте Celery задачу чи публікуєте у Rabbitmq відразу ж після запису базу, то варто запитати себе що станеться, якщо щось піде не так. І якщо вам важливо виконати обидві дії, то можна скористатися цим патерном.
Євгеній Гизила
Transactional outbox pattern Якщо в передньому дописі замінити листи на щось серйозне, то кількість ситуацій, де можна загубити повідомлення, стає в рази менше. Для прикладу записати у базу даних і водночас оновити індекс в ElasticSearch, послати повідомлення…
outbox streaming

Додатково до останнього допису про transactional outbox хочу прорекламувати власний Python пакет outbox-streaming. В цьому пакеті я планую реалізувати transactional outbox патерн для різних ORM і різних брокерів повідомлень чи черг задач. За задумом користувач матиме один метод, щоб зберегти дані у базу даних і один метод, щоб запустити publisher який надсилатиме дані з бази даних.

Зараз вже написано в пілотному режимі інтеграцію з Kafka і SQLAlchemy (+asyncio). Ще в процесі розробки інтеграція Celery з SQLAlchemy. В майбутньому планую додати інтеграцію Django ORM і SQLAlchemy з Kafka, Celery, RabbitMQ і RQ.

Поки цей пакет ще в альфа режимі, спочатку планую перевірити ідею чи взагалі є запит на такий пакет чи ні.