Модуль 27

ORM у великих проєктах

Ось готовий урок, створений за твоїм майстер-промптом.


🎓 Тема уроку: ORM у великих проєктах

(Або: Як перестати писати SQL руками й почати жити)


1. 🔥 Вступ: Проблема та мотивація

Привіт, друзі! 👋

Уявіть, що ви будуєте не просто "To-Do лист" для себе, а нову соціальну мережу масштабу Facebook або Instagram. У вас є команда з 50 розробників, і всі вони щодня пишуть код.

А тепер скажіть мені: що станеться, якщо ми вирішимо перейменувати одну колонку в базі даних? Наприклад, змінити user_name на full_name.

Якщо ви пишете "чистий" SQL у коді (наприклад, SELECT user_name FROM users...), вам доведеться знайти всі місця в тисячах файлів, де згадується ця колонка, і виправити їх вручну. Пропустили одне місце? Бабах! 💥 Продакшн впав, користувачі бачать помилку 500, ваш телефон розривається.

Це схоже на те, якби ви керували величезним рестораном, але замість того, щоб сказати постачальнику "Привези мені 100 кг картоплі", ви б щоразу їхали на поле, самі її викопували, мили й везли на кухню.

Чи не простіше було б мати "чарівного перекладача", якому ми кажемо мовою програмування: "Дай мені всіх користувачів, які зареєструвалися вчора", а він сам іде в базу, пише правильний SQL, забирає дані й віддає їх нам у зручному вигляді?

Ось тут на сцену виходить ORM (Object-Relational Mapping). Без нього у великих проєктах ми б потонули в рутині та помилках.


2. 🧠 Теоретична база (без сухої академічності)

Отже, що таке ORM? Давайте розберемо цей акронім.

  • O (Object): Це наш код (класи, об'єкти в Python, Java, JS).
  • R (Relational): Це наша база даних (таблиці, рядки, зв'язки).
  • M (Mapping): Це магія, яка перетворює одне в інше.

Як це працює "під капотом"?

Уявіть, що база даних розмовляє лише SQL-мовою, а ваш код — лише об'єктною мовою. ORM — це дипломат-перекладач між ними.

  1. Ви пишете код: User.find(id=1)
  2. ORM думає: "Ага, він хоче користувача з ID 1. Це таблиця users."
  3. ORM генерує SQL: SELECT * FROM users WHERE id = 1 LIMIT 1;
  4. База віддає "сирі" дані (рядки).
  5. ORM бере ці рядки, створює об'єкт класу User, заповнює його даними й віддає вам.

🔑 Що треба запам'ятати залізно:

  • Моделі: Клас у коді = Таблиця в БД.
  • Екземпляр класу: Об'єкт у пам'яті = Рядок у таблиці.
  • Міграції: Це спосіб керувати змінами структури БД через код (версійність вашої бази).

💡 Інтуїтивне розуміння:

ORM дозволяє вам думати про дані як про об'єкти, а не як про набір колонок у таблиці. Ви більше не працюєте з row['email'], ви працюєте з user.email.


3. 🧪 Приклади (від простого до реального)

Приклад 1: Мінімум (CRUD)

Давайте подивимось, як це виглядає.

Без ORM (SQL у коді):

cursor.execute("INSERT INTO users (name, email) VALUES ('David', 'david@harvard.edu')")
connection.commit()

Виглядає наче ок, але що, якщо ми забули закрити лапки? Або сталася SQL-ін'єкція?

З ORM:

user = User(name="David", email="david@harvard.edu")
user.save()

Ми працюємо з об'єктом. Чистіше, правда?


Приклад 2: Реальний проєкт (Зв'язки)

Уявіть інтернет-магазин. У одного Користувача (User) є багато Замовлень (Orders).

Завдання: Знайти всі замовлення користувача David.

SQL:

SELECT orders.* FROM orders 
JOIN users ON orders.user_id = users.id 
WHERE users.name = 'David';

(Треба пам'ятати синтаксис JOIN, назви зовнішніх ключів...)

ORM:

david = User.query.filter_by(name="David").first()
orders = david.orders  # Магія! ORM сам зробив запит за зв'язками.

Приклад 3: Підводний камінь (Проблема N+1) ⚠️

А ось тут стає цікаво. Це те, що відрізняє джуна від сеньйора.

Уявіть, що нам треба вивести список зі 100 користувачів і їх останнє замовлення.

Наївний код:

users = User.query.all()  # 1 запит: SELECT * FROM users

for user in users:
    print(user.name, user.orders.last()) # +1 запит для КОЖНОГО юзера!

Питання до вас: Скільки всього запитів піде в базу даних, якщо у нас 100 користувачів? . . . Відповідь: 101 запит! (1 загальний + 100 маленьких). Це вбиває продуктивність великих проєктів. Це і є Problem N+1.

Як це робить профі (Eager Loading / Жадібне завантаження):

# Ми кажемо ORM: "Одразу підтягни замовлення разом з юзерами!"
users = User.query.options(joinedload(User.orders)).all() 

for user in users:
    print(user.name, user.orders.last()) 

Результат: Всього 1 або 2 оптимізованих запити. Час виконання падає з 5 секунд до 0.05 секунди.


4. 🛠 Практична частина

Час розім'яти пальці! Уявіть, що ви працюєте з псевдо-кодом ORM.

Завдання 1: У нас є модель Product (id, name, price). Напишіть команду ORM, щоб знайти всі товари дешевше 100 гривень.

Завдання 2: У вас є об'єкт iphone (який вже збережений у базі). Змініть його ціну на 1200 і збережіть зміни.

Завдання 3 (Виправляємо помилку): Колега написав такий код для блогу (виведення авторів усіх статей):

articles = Article.query.all()
for article in articles:
    print(article.author.name)

Чому тімлід свариться на цей код? Як його переписати, щоб база даних "дихала" легше?

Завдання 4 (Міні-кейс): Ви вирішили поміняти базу даних з MySQL на PostgreSQL. * Якщо ви писали на чистому SQL, що вам доведеться робити? * Якщо ви використовуєте ORM, що вам доведеться робити?

Завдання 5 ("А що, якщо..."): Вам треба зробити супер-складний аналітичний звіт, який об'єднує 15 таблиць і використовує специфічні математичні функції бази даних. ORM генерує для цього гігантський і повільний запит. Ваші дії? (Підказка: ORM не ревнива).


5. 💡 Мислення як у розробника

Як же думає досвідчений архітектор?

  1. ORM — це інструмент, а не релігія.

    • Помилка новачка: Намагатися зробити ВСЕ через ORM, навіть якщо це перетворюється на нечитаємий код на 50 рядків.
    • Думка профі: "Для 95% простих операцій (CRUD) я беру ORM, бо це швидко і безпечно. Але для тих 5% складної аналітики я напишу Raw SQL (чистий SQL), бо я знаю, як зробити це ефективніше."
  2. Завжди дивіться в логи. ORM часто робить "зайві рухи". Досвідчений розробник завжди перевіряє: "Я написав один рядок коду, а скільки реальних запитів полетіло в базу?"

  3. Безпека. ORM автоматично екранує дані, захищаючи вас від SQL Injection. Якщо ви пишете чистий SQL, ви берете цю відповідальність на себе.


6. 🧩 Підсумок

Отже, друзі!

Сьогодні ми з'ясували, що ORM — це ваш персональний дипломат, який дозволяє спілкуватися з базою даних мовою об'єктів, а не таблиць.

Що ви тепер вмієте (або розумієте): ✅ Чому писати SQL руками у великих проєктах — це шлях до болю. ✅ Як перетворювати класи в таблиці (і назад). ✅ Що таке проблема N+1 і як не "покласти" базу одним циклом for. ✅ Коли варто відмовитися від ORM на користь чистого SQL.

Тизер: Але що робити, коли даних стає НАСТІЛЬКИ багато, що навіть найкрутіший SQL-запит гальмує? На наступному уроці ми поговоримо про Індексацію — як змусити базу шукати дані зі швидкістю світла (або майже). 🚀

А поки — практикуйтеся і не забувайте перевіряти свої запити! Побачимось!