ORM — це просто
Привіт друзі! Нещодавно жалівся в блозі, що не маю з ким поговорити про код, який я пишу, тож поділюсь із вами, бо нарешті маю трохи вільного часу 😃
В програмуванні є різні підходи до роботи з базами даних:
- можна написати код, який просто виконує SQL запити. Це просто і зрозуміло
- А можна використати ORM (Object-relational mapping) — технологію, що зв’язує кожен запис в таблиці БД з відповідним об’єктом. Як от код
class Person(Base): id = Column(Integer, primary_key=True) name = Column(String, nullable=False) age = Column(Integer, nullable=True)
буде відповідати запису в SQL таблиці з трьома колонками з відповідними іменами

Це зручно для розробника:
🟢створив об’єкт — додався запис в базу
🟢змінив — і зміни вже там
🟢видалив — ви зрозуміли
🟢а працювати з даними з пов’язаних таблиць — взагалі дуже просто і приємно!
# уявімо, що у нас є таблиця Cars і кожна людина може мати декілька машин # з даними можна працювати як зі списком for car in person.cars: print(car.name)
Але на практиці і я, і багато моїх колег і знайомих все ще частіше користуємось старим добрим SQL в коді. Тому що
- «та там щось складно, це ж розбиратись треба»
- «ну ми вже багато написали, переписувати довго буде»
- «а якщо щось зміниться, як мігрувати?»
І от, нещодавно в мене з’явилась задача написати тести, що використовують дані з 10+ нових таблиць, при чому в коді вже був написаний абстрактний шар коду з SQL на інші 10 таблиць. Раніше я б тільки мріяв про ORM, бо руками створювати моделі для купи таблиць з купою полів — неприємна багатогодинна рутина. Але я вирішив спробувати мігрувати все на ORM, щоб дізнатись, наскільки це складно і довго насправді, використовуючи ШІ.
Пишу на Python і обрав для роботи одну з найпопулярніших ORM — SQLAlchemy.
✅ По-перше, я створив моделі — скопіював структуру таблиць у вигляді SQL запитів (будь-який редактор таке вміє) і попросив Copilot згенерувати мені моделі. На все витратив десь годину
create table persons ( id INTEGER not null primary key, name VARCHAR not null, age INTEGER );
✅ По-друге, переписав існуючі функції, що читали чи модифікували дані, замінюючи SQL відповідними моделями. Витратив ще годину. Тобто, був, умовно, метод get_persons_by_age(age: int) -> list[tuple], де «під капотом» був написаний SQL запит. Він і залишився, але всередині працює через ORM. Тільки повертав список списків* значень, а тепер — список об’єктів. Що навіть спрощує роботу з ним.
✅ По-третє, написав нові функції вже на нові таблиці. Їх було багато, тож 2 години. При чому, писав навіть не зовсім сам. Пишучи назву функції, Copilot сам пропонував мені підходящий код, тож робота йшла дуже швидко!
✅ Запустив тести, щоб перевірити, чи нічого не зламав, пофіксив кілька перетворень типів і все! На повну міграцію я витратив менше 1 дня!
Як наслідок я, і інші інженери використовують ці моделі для швидкого написання інтеграційних тестів для перевірки прототипів. Код став красивіший і зрозуміліший.
Тож якщо ви сумніваєтесь — рекомендую спробувати. З ШІ це питання годин, а не днів😉
10 коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарівкогда орм вешают тупо ради 1 инсерта или селекта это булщит. Я видел проекты, где орм в шопе тянул продукты-аттрибуты, категории, какие-то субпродукты, дочерние айтемы. На голом сиквеле там свихнуться можно от джоинов с кучей сопуствующих орм болячек, когда он генерит хреновые плохо оптимизированные запросы или не может все сделать, что хочет бизнес, и остаток нужно прикручивать к орм сырым сиквелем. И это не все, дурной орм плодит груду объектов, которые перегружают GC. Так что сырой сиквел в подавляющем большинстве решает задачи. Если орм правильно готовить и либа писалась не идиотами, то он полезен на проектах с многоуровневыми зависимостями
все правильно написали. Але перелічені проблеми, це проблеми поганого дизайну, а не конкретної технології.
а якщо сирий sql писатимуть ідіоти, то будуть ін’єкції, left join і купа інших поетнційно кривих місць.
Навіть заради одного інсерту можна взяти якусь прослойку, якщо сам не дуже тойво в sql. Бо в цей єдиний інсерт — якщо він криво зроблений — можна, наприклад, запихнути якийсь варіант
'';drop table user;Тобто якщо ідіоти — то і в сирому і вареному вигляді буде швах.
якщо не ідіоти — то і в тому і в іншому випадку буде норм і роялить вже зручність, швидкість, тести, ком’юніті.
В вашій «статті» ви розглянули «ґетітґ стартед» приклади. На цьому рівні все просто.
Проблеми з ОРМ починаються трохи пізніше, коли окрім власне мапінгу починаєш використовувати його в складніших сценаріях — транзакції, міграції (на не пустій базі), складна чи високо нормалізована схема, кешування, оптимізація запитів,
ви праві. Це «getting stated», або «перша доза безкоштовно» :D
Для тестів зазвичай і не треба космічні кораблі будувати, тож зайшло ідеально.
Міграції в python ORM однаково жахливі і химерні. Як мінімум, я намагався їх робити в Django, SQLAlchemy та Tortoise.
З транзакціями проблем не мав — ліпив декоратор типу @transactional і все працювало (але може просто пощастило)
З кешуванням та оптимізацією так само — якщо написати нормально — буде працювати нормально. Це ж не проблема технології, а радше підходу. Лайнокод можна і на чистому sql написати
Тут питання як ви визначаєте поняття «нормально»?
Скоріше за все ви маєте на увазі: спочатку об’єктна модель, а потім під неї схема. Це спрощує суттєво багато речей. Але лишає проблеми з оптимізацією запитів під схему, що рано чи пізно стане не оптимальною з точки зору теорії реляційних баз.
Якщо нормальна — це у 5+ нормальний формі, то там ОРМ створить купу проблем і нелогічного коду (я перевіряв)
Так ви б описали специфічний для тестування кейс або як в цілому це спрощує роботу АКуА. Це було б справді цікаво.
Перша ж проблема для тестування, яку я можу уявити — це те що ви не контролюєте схема взагалі, принаймні коли читаєте з БД «системи, що тестується». Ще бачив проблему, коли тестам треба працювати з декількома базами. Впевнений, що у вас знайдуться ще якість специфічні сценарії.
ORM це просто... шкідлиіва, токсична інженерна ПОМИЛКА.
Частіше за все — в ДНК.
Бгг, «то ви просто не вмієте готувати» :)
Ви якось аргументуйте — бо в мене ORM працює чудово, і часу на розробку і підтримку я витратив мінімум.
Я працював з ORM, чистим SQL, Query Builder-ами, писав прості Query Builder-и сам, і зараз найзручнішим інструментом для мене є sqlc, який генерує код на основі SQL-запитів та схеми бази даних. sqlc також підтримує Python, тож за можливості придивіться до нього.
Як sqlc працює з Go, з Python має бути схоже — я описав це у статті «Go: ефективна робота з SQL»
дякую! Виглядає прикольно. буду мати на увазі.
Подивився python реалізацію — вона буде генерувати або датакласи, або pydantic моделі. Тобто по суті генерувати orm шар за мене.
Можливо, граний варіант для повноцінної розробки продукту, але в моєму випадку це явно буде занадто. Я не створюю таблиці, не роблю міграції. Моїм тестам треба швиденько створити/модифікувати дані, а потім зачистити все за собою.