Модуль 25

Міграції бази даних з Alembic

Ось готовий урок, згенерований за твоїм майстер-промптом. Це класичний CS50-вайб: енергійно, зрозуміло і з фокусом на суті.


🎓 Тема: Міграції бази даних з Alembic

Привіт, світе! 👋 Радий бачити вас. Сьогодні ми торкнемося теми, яка відрізняє аматорський код від професійної інженерії. Ми говоритимемо про зміни.

1. 🔥 Вступ: Коли "DROP TABLE" — це не вихід

Уявіть ситуацію. Ви пишете стартап — нову соціальну мережу. У вас є база даних, є таблиця Users, і там є поля name та email. Ви запускаєте проєкт, перші 100 користувачів реєструються. Ура! 🎉

Але наступного ранку маркетинг каже: "Слухай, нам терміново треба додати поле age (вік), щоб показувати правильну рекламу".

Ви відкриваєте код, додаєте поле в модель... і тут виникає проблема. Ваш код (Python) тепер очікує поле age, а ваша реальна база даних (PostgreSQL/MySQL) про нього ні сном ні духом.

Що робити?

  1. Варіант "Студентський": Видалити таблицю (разом зі 100 користувачами) і створити нову.
    • Результат: Маркетологи плачуть, інвестори йдуть, ви звільнені. ❌
  2. Варіант "Ковбойський": Зайти в консоль бази даних і писати вручну ALTER TABLE users ADD COLUMN age...
    • Результат: Це працює на вашому ноутбуці. Але як це перенести на сервер? А якщо у вас 5 розробників? А якщо ви помилилися в синтаксисі на продакшені? Хаос. ❌

Хіба не було б чудово мати "машину часу" для бази даних? Інструмент, який дозволяє безпечно додавати кімнати до вашого будинку, не зносячи його до фундаменту?

Саме для цього існує Alembic.


2. 🧠 Теоретична база: Гіт для вашої бази

Що таке Alembic? Якщо Git — це система контролю версій для вашого коду, то Alembic — це система контролю версій для вашої схеми бази даних.

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

Уявіть Alembic як архітектора-посередника. 1. З одного боку у нього є ваші Python-моделі (SQLAlchemy) — це те, як база має виглядати. 2. З іншого — реальна база даних — те, як вона виглядає зараз.

Alembic дивиться на обидва боки, знаходить різницю і генерує скрипт міграції.

Ключові поняття (запам’ятайте це!):

  • Migration (Міграція / Ревізія): Це маленький файл на Python, інструкція. Він каже: "Додай колонку Х" або "Створи таблицю Y".
  • Upgrade (Оновлення): Застосування змін (рух у майбутнє ➡).
  • Downgrade (Відкат): Скасування змін (рух у минуле ⬅). Це ваша кнопка "Undo", якщо щось пішло не так.
  • alembic_version: Спеціальна маленька табличка, яку Alembic створює у вашій базі. Там зберігається лише одне значення — ID поточної міграції. Так він знає, на якому етапі історії ми знаходимось.

Інтуїтивно: Міграція — це як чекпойнт у грі. Ви пройшли рівень (змінили структуру БД) — збереглися. Якщо бос вас вбив (код впав) — завантажили попередній чекпойнт.


3. 🧪 Приклади: Від магії до контролю

Давайте налаштуємо це. Я буду використовувати аналогію з будівництвом.

Крок 0: Ініціалізація (Закладаємо фундамент)

У терміналі вашого проєкту:

alembic init alembic

Ви побачите нову папку alembic/. Найважливіший файл там — env.py. Завдання розробника: У цьому файлі треба підказати Alembic, де лежать ваші моделі (метадані).

# У файлі alembic/env.py
from my_app.models import Base  # Імпортуємо ваші моделі
target_metadata = Base.metadata # Кажемо Alembic: "Дивись сюди!"

Приклад 1: Перша міграція (Створюємо світ)

У нас є модель:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String)

Ми кажемо Alembic: "Зроби зліпок стану бази".

alembic revision --autogenerate -m "Initial migration"

Alembic створить файл у папці versions/. Давайте заглянемо всередину. Що ви очікуєте там побачити? SQL? Ні, там Python!

def upgrade():
    # Alembic каже: Створи таблицю users
    op.create_table('users',
        sa.Column('id', sa.Integer(), nullable=False),
        sa.Column('name', sa.String(), nullable=True),
        sa.PrimaryKeyConstraint('id')
    )

def downgrade():
    # Якщо треба відкотитися - видали таблицю
    op.drop_table('users')

Застосовуємо (будуємо дім):

alembic upgrade head

head означає "остання, найновіша версія".

Приклад 2: Зміни в реальному часі (Додаємо вік)

Тепер змінимо модель у коді:

class User(Base):
    # ... старі поля ...
    age = Column(Integer, default=18) # <-- Нове поле!

Робимо нову міграцію:

alembic revision --autogenerate -m "Add age column"
alembic upgrade head

Вуаля! Ваша база оновилася, дані користувачів збереглися, а нова колонка з’явилася.


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

Тепер ваша черга. Не бійтеся помилятися — це локальна база даних.

  1. 🔹 База: Ініціалізуйте Alembic у новому проєкті. Створіть просту модель Product (назва, ціна). Згенеруйте та застосуйте першу міграцію.
  2. 🔹 Зміна: Додайте до товару поле in_stock (булеве значення). Згенеруйте міграцію. Застосуйте її. Перевірте через DBeaver або pgAdmin, чи з'явилася колонка.
  3. 🔹 Відкат: Упс, in_stock не потрібен! Виконайте команду alembic downgrade -1. Переконайтеся, що колонка зникла, але таблиця лишилася.
  4. 🔹 Виклик: Спробуйте перейменувати поле в моделі (наприклад, price -> amount). Зробіть --autogenerate.
    • Питання: Чи зрозумів Alembic, що це перейменування, чи він видалив стару колонку і створив нову? (Спойлер: часто він думає, що це видалення+створення, що призведе до втрати даних. Подивіться, як виправити це в згенерованому файлі вручну за допомогою op.alter_column).
  5. 🔹 Кейс "А що, якщо": Що буде, якщо ви додасте колонку NOT NULL (обов'язкову) в таблицю, де вже є записи? Як база даних заповнить це поле для існуючих записів?
    • Підказка: Вам доведеться встановити дефолтне значення або наповнити дані перед встановленням обмеження NOT NULL.

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

Ось декілька секретів, які відрізняють "сеньйорів" від новачків:

  1. Ніколи не довіряйте --autogenerate сліпо. Alembic розумний, але не геній. Він може не помітити зміну назви таблиці або складні зміни типів даних. Правило: Завжди відкривайте згенерований файл міграції та читайте його очима перед тим, як робити upgrade.

  2. Міграції — це частина коду. Файли міграцій комітяться в Git. Якщо ваш колега підтягує ваш код, він повинен мати змогу запустити alembic upgrade head і отримати ідентичну базу даних.

  3. Не змінюйте старі міграції. Якщо ви вже застосували міграцію і закомітили її, ніколи не редагуйте цей файл. Створіть нову міграцію, яка виправляє помилки попередньої. Історія має бути лінійною і недоторканною.


6. 🧩 Підсумок

Отже, що ми маємо? * Ми більше не видаляємо бази даних, щоб додати колонку. * Ми маємо повну історію змін нашої структури (хто, коли і що додав). * Ми можемо подорожувати в часі (upgrade/downgrade).

Ви тепер володієте інструментом, який дозволяє вашому проєкту рости від "гаражного прототипу" до "корпоративного монстра" без втрати даних.

🔍 Що далі? Тепер, коли у нас є ідеальна структура бази даних, настав час наповнити її даними та навчитися діставати їх максимально ефективно. Наступного разу ми поговоримо про складні запити, JOIN-и та проблему N+1. Це буде цікаво!

А поки що — alembic upgrade head і до зустрічі! 💻🚀