ORM — це просто

💡 Усі статті, обговорення, новини про тестування — в одному місці. Приєднуйтесь до QA спільноти!

Привіт друзі! Нещодавно жалівся в блозі, що не маю з ким поговорити про код, який я пишу, тож поділюсь із вами, бо нарешті маю трохи вільного часу 😃

В програмуванні є різні підходи до роботи з базами даних:

  • можна написати код, який просто виконує 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 дня!

Як наслідок я, і інші інженери використовують ці моделі для швидкого написання інтеграційних тестів для перевірки прототипів. Код став красивіший і зрозуміліший.

Тож якщо ви сумніваєтесь — рекомендую спробувати. З ШІ це питання годин, а не днів😉

👍ПодобаєтьсяСподобалось4
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

когда орм вешают тупо ради 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 шар за мене.
Можливо, граний варіант для повноцінної розробки продукту, але в моєму випадку це явно буде занадто. Я не створюю таблиці, не роблю міграції. Моїм тестам треба швиденько створити/модифікувати дані, а потім зачистити все за собою.

Підписатись на коментарі