Це чудова ідея! Стиль Девіда Малана — це золотий стандарт викладання: енергія, чіткість і фокус на розумінні суті.
Ось твій урок про Асинхронний ORM, згенерований за всіма правилами цього майстер-промпту.
🎓 Урок: Асинхронний ORM (async/await)
1. 🔥 Вступ: Чому ваш сервер "завмирає"?
Уявіть, що ви працюєте баристою в найпопулярнішій кав’ярні міста. Ранок, черга аж до дверей.
Приходить клієнт, замовляє складне лате. Ви натискаєте кнопку на кавомашині... і що ви робите далі? 1. Стоїте і дивитеся на цівку кави 40 секунд, ігноруючи решту черги? 2. Чи, поки кава готується, приймаєте замовлення у наступного клієнта?
Якщо ви обрали варіант №1 — ви працюєте синхронно. У світі вебу це катастрофа. Поки один користувач чекає відповідь від бази даних (БД), всі інші бачать білий екран, що "крутиться".
Але якщо ви обрали варіант №2 — вітаю, ви зрозуміли суть асинхронності!
Питання до вас: Чому ми дозволяємо нашим програмам "простоювати", поки база даних шукає інформацію на диску? Адже процесор у цей час вільний!
Сьогодні ми навчимося не просто спілкуватися з базою даних через об’єкти (ORM), а робити це ефективно, не блокуючи роботу всього додатку. Ласкаво просимо у світ Async ORM.
2. 🧠 Теоретична база: Як це працює "під капотом"?
Давайте розберемося з поняттями, щоб не плутатися.
Що таке ORM? (Коротке нагадування)
ORM (Object-Relational Mapping) — це перекладач. Ви говорите мовою об’єктів (Python/JS класів), а ORM перекладає це в SQL-запити для бази даних.
* Ви пишете: User.save()
* База чує: INSERT INTO users ...
Синхронний vs Асинхронний ORM
- Синхронний (Sync): Код відправляє запит і засинає. Програма зупиняється на цьому рядку. "Я не зрушу з місця, поки ти не повернеш мені дані про юзера!".
- Асинхронний (Async): Код відправляє запит і каже: "Окей, база даних, ти поки шукай, а я піду займуся іншими справами. Коли знайдеш — поклич (await), і я заберу результат".
⚙️ Магія Event Loop (Цикл подій)
Уявіть, що всередині вашої програми є менеджер зі списком завдань. Це Event Loop.
1. Коли ви пишете await database.get_users(), ви фактично кажете менеджеру: "Запиши, що ми чекаємо на базу. Я поки вільний".
2. Менеджер дивиться список: "О, тут прийшов новий HTTP-запит, давай поки обробимо його".
3. Коли база даних відповідає, менеджер каже: "Гей, дані прийшли! Повертаємося до виконання попередньої функції".
❗️ Запам’ятати обов’язково: Ключові слова
asyncтаawait— це ваші інструменти управління часом. *async def— оголошує функцію, яка вміє "призупинятися". *await— точка, де ми кажемо: "Тут треба почекати, але не блокуй усіх інших".
3. 🧪 Приклади (на базі Python + SQLAlchemy)
Для прикладів використаємо Python, бо це класика бекенду, але логіка ідентична в Node.js (Prisma/TypeORM).
Приклад 1: Мінімум для старту
Що ви очікуєте побачити при підключенні? Напевно, просто рядок з URL бази. Але в асинхронному світі нам потрібен спеціальний драйвер (наприклад, asyncpg для PostgreSQL), бо звичайний драйвер заблокує все.
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
# Зверніть увагу: postgresql+asyncpg (асинхронний драйвер!)
DATABASE_URL = "postgresql+asyncpg://user:pass@localhost/dbname"
# Створення двигуна
engine = create_async_engine(DATABASE_URL, echo=True)
# Фабрика сесій (наш "портал" у базу)
async_session = sessionmaker(
engine, class_=AsyncSession, expire_on_commit=False
)
Пояснення: Ми створили двигун, який вміє "не блокувати". echo=True покаже нам SQL, який генерується.
Приклад 2: Типовий запит (SELECT)
У звичайному коді ми б написали users = session.query(User).all().
Як ви думаєте, як це виглядатиме в async?
from sqlalchemy import select
async def get_all_users():
async with async_session() as session:
# 1. Створюємо запит (це ще не виклик до БД!)
stmt = select(User).where(User.age > 18)
# 2. Виконуємо запит АСИНХРОННО
print("Відправляємо запит...")
result = await session.execute(stmt)
print("Отримали відповідь!")
# 3. Витягуємо дані
users = result.scalars().all()
for user in users:
print(f"Знайдено: {user.name}")
# Запуск (у реальному додатку це робить фреймворк, тут — ми самі)
import asyncio
asyncio.run(get_all_users())
Чому так?
Ключовий момент — await session.execute(stmt). Саме в цю мілісекунду управління передається іншим частинам програми. Якби тут не було await, ми б отримали об'єкт "очікування" (coroutine), а не дані.
Приклад 3: Вищий пілотаж (Конкурентність)
Уявіть, треба знайти інформацію про трьох різних користувачів. Синхронний підхід: Запит 1 (чекаємо), потім Запит 2 (чекаємо), потім Запит 3 (чекаємо). Сума часу: 3 секунди. Асинхронний підхід: Запускаємо всі три запити "паралельно" і чекаємо, коли завершаться всі. Час: ~1 секунда (найдовшого запиту).
async def get_user_by_id(user_id):
async with async_session() as session:
result = await session.execute(select(User).where(User.id == user_id))
return result.scalars().first()
async def main():
# Запускаємо три задачі одночасно!
task1 = get_user_by_id(1)
task2 = get_user_by_id(2)
task3 = get_user_by_id(3)
# await asyncio.gather чекає виконання їх усіх
print("Шукаємо всіх трьох одразу...")
user1, user2, user3 = await asyncio.gather(task1, task2, task3)
print(f"Знайшли: {user1.name}, {user2.name}, {user3.name}")
Результат: Це працює набагато швидше, ніж послідовне виконання.
4. 🛠 Практична частина
Час забруднити руки кодом!
🔹 Завдання 1: "Hello Async World"
Налаштуйте з'єднання з базою (можна використати SQLite з драйвером aiosqlite для простоти: sqlite+aiosqlite:///test.db). Створіть просту модель Item (id, name, price) і напишіть асинхронну функцію, яка додає один товар у базу.
🔹 Завдання 2: "Знайди помилку" Студент написав код:
users = session.execute(select(User))
print(users.scalars().all())
Код видає помилку або дивний об'єкт. Чому? Виправте код. (Підказка: де ключове слово на літеру 'a'?)
🔹 Завдання 3: "Масова знижка"
Напишіть функцію apply_discount(), яка вибирає всі товари дорожче 100 грн і зменшує їх ціну на 10%. Важливо: зробіть commit змін у базу асинхронно.
🔹 Завдання 4: Міні-кейс "Дашборд адміністратора"
Вам треба відобразити на екрані:
1. Кількість користувачів.
2. Кількість замовлень за сьогодні.
3. Суму продажів.
Напишіть код, який збирає ці три метрики одночасно (використовуючи gather), а не послідовно.
🔹 Питання "А що, якщо..."
А що буде, якщо всередині вашої асинхронної функції викличете time.sleep(5) замість await asyncio.sleep(5)? Як це вплине на користувачів вашого сайту?
5. 💡 Мислення як у розробника
Ви тепер знаєте синтаксис, але як думає профі?
⚠️ Типові пастки новачків:
- Забутий
await: Ви викликаєте функцію, а вона повертає об'єкт coroutine замість даних. Це класика. - Синхронщина в асинхронності: Якщо ви вставите "важкий" цикл обробки даних або синхронний запит до API всередині
asyncфункції — ви заблокуєте Event Loop. Весь сервер "стане колом". - Lazy Loading (Ліниве завантаження): В синхронному ORM ви могли написати
user.posts, і ORM сама робила запит. В Async ORM це часто не працює або викликає помилку, бо не можна зробити запит "магічно" безawait.- Рішення: Використовуйте "eager loading" (завантажуйте зв'язані дані одразу в головному запиті через
options(selectinload(User.posts))).
- Рішення: Використовуйте "eager loading" (завантажуйте зв'язані дані одразу в головному запиті через
🎓 Порада від сеньйора:
Асинхронність — це не про те, щоб зробити один запит швидшим. Це про те, щоб ваш сервер міг тримати тисячу запитів одночасно, не купуючи при цьому суперкомп'ютер. Думайте про "пропускну здатність" (throughput), а не просто про "швидкість" (latency).
6. 🧩 Підсумок
Отже, що ми сьогодні зрозуміли?
1. Синхронний код блокує чергу, асинхронний дозволяє обслуговувати інших, поки чекаємо.
2. async/await — це спосіб сказати: "Я почекаю результат тут, а процесор хай займеться іншим".
3. В ORM ми повинні явно писати await для будь-якої операції, яка йде в мережу або на диск.
Тепер ви вмієте: ✅ Підключатися до БД асинхронно. ✅ Робити SELECT та INSERT без блокування. ✅ Виконувати кілька запитів паралельно для прискорення.
🔍 Що далі? У нас є дані, ми вміємо їх швидко діставати. Але як віддати їх світу? На наступному уроці ми поєднаємо це з FastAPI і створимо повноцінний, блискавичний API, який витримає навантаження Reddit!
Готові? Тоді до зустрічі! 👋