Ось готовий урок, створений за твоїм майстер-промптом.
🎓 CS50: Архітектура ПЗ. Repository та Service patterns
Привіт, друзі! Це CS50, і сьогодні ми поговоримо про те, що відрізняє код "студента, який вчора вивчив синтаксис", від коду професійного інженера.
Ми зануримося в тему Repository (Репозиторій) та Service (Сервіс) патернів.
1. 🔥 Вступ: Хаос у ресторані
Уявіть собі дорогий ресторан. Ви сідаєте за столик, підходить офіціант і приймає замовлення: "Стейк середньої просмажки".
А тепер уявіть, що після цього офіціант... 1. Біжить на кухню. 2. Сам бере ніж і починає різати м'ясо. 3. Раптом розуміє, що м'ясо закінчилося, тому біжить на ферму забивати корову. 4. Повертається, смажить стейк, миє тарілку. 5. І нарешті приносить його вам.
Це абсурд, правда? Але саме так виглядає код більшості початківців!
У вашому коді (наприклад, у контролері веб-додатку) часто змішано все: * Отримання HTTP-запиту (Офіціант). * Бізнес-логіка та правила (Шеф-кухар). * SQL-запити до бази даних (Постачальник продуктів).
Питання до вас: Що станеться з цим рестораном (вашим кодом), якщо ми вирішимо змінити постачальника м'яса? Або якщо офіціант захворіє? Весь процес зупиниться, бо все зав'язано в один величезний вузол.
Навіщо нам ці патерни? Щоб розділити відповідальність. * Ми хочемо, щоб код, який працює з базою даних, не знав про HTTP. * А код, який перевіряє бізнес-правила, не дбав про те, чи лежать дані в MySQL, чи в текстовому файлі.
2. 🧠 Теоретична база (Без нудьги)
Давайте розкладемо все по поличках. У нас є три шари (layers).
1. Repository (Комора / Постачальник)
Це шар доступу до даних. * Суть: Він поводиться як колекція об'єктів. Ви кажете йому: "Дай мені користувача з ID 5" або "Збережи цей товар". * Як це працює "під капотом": Репозиторій містить SQL-запити (SELECT, INSERT) або виклики ORM. * Головне правило: Тут немає бізнес-логіки. Репозиторій — це тупий виконавець. Він просто дістає або кладе дані.
2. Service (Шеф-кухар)
Це мозок вашої програми. * Суть: Тут живуть правила. Наприклад: "Якщо користувач купує товар, перевір, чи є він на складі, спиши гроші, надішли email". * Як це працює: Сервіс викликає методи Репозиторія, щоб отримати дані, обробляє їх і повертає результат. * Головне правило: Сервіс не повинен знати, як саме зберігаються дані (SQL чи NoSQL). Він просто каже Репозиторію: "Знайди мені це".
3. Controller / Handler (Офіціант)
- Приймає запит від клієнта.
- Викликає Сервіс.
- Віддає відповідь клієнту (JSON, HTML).
Інтуїтивно: * Repository — це руки, що беруть дані. * Service — це голова, що думає, що з ними робити.
3. 🧪 Приклади (Кодимо!)
Давайте подивимось на прикладі реєстрації користувача (Python-style псевдокод).
❌ Як робити НЕ треба (Spaghetti Code)
Уявіть, що ви відкриваєте файл контролера, а там таке:
def register_user(request):
username = request.form['username']
password = request.form['password']
# ПЕРЕВІРКА (Логіка)
if len(password) < 8:
return "Password too short!"
# РОБОТА З БД (SQL прямо в контролері? Жах!)
db = connect_to_database()
cursor = db.cursor()
cursor.execute("INSERT INTO users (name, password) VALUES (...)")
# ЩЕ ЛОГІКА
send_welcome_email(username)
return "Success!"
Що тут поганого? Якщо ви захочете змінити базу даних, вам доведеться лізти сюди. Якщо ви захочете змінити правила пароля — теж сюди. Цей код неможливо тестувати частинами.
✅ Крок 1: Створюємо Repository
Давайте винесемо роботу з базою даних.
Питання: Що ми очікуємо від класу UserRepository? Тільки методи для збереження та пошуку, так?
class UserRepository:
def save(self, user):
# Тут живе "брудний" SQL або ORM
print(f"DEBUG: Saving {user} to Database via SQL...")
# db.execute("INSERT INTO...")
def find_by_email(self, email):
print(f"DEBUG: SELECT * FROM users WHERE email = {email}")
return None # або знайдений юзер
Бачите? Жодної перевірки довжини пароля. Тільки дані.
✅ Крок 2: Створюємо Service
Тепер нам потрібен хтось, хто керує процесом.
Питання: Хто буде викликати репозиторій? Правильно, Сервіс.
class UserService:
def __init__(self, user_repository):
# Ми передаємо репозиторій всередину (Dependency Injection)
self.repo = user_repository
def register(self, username, password):
# 1. Бізнес-логіка: Валідація
if len(password) < 8:
raise Exception("Пароль надто короткий!")
# 2. Бізнес-логіка: Перевірка унікальності
if self.repo.find_by_email(username):
raise Exception("Користувач вже існує!")
# 3. Делегування збереження репозиторію
new_user = {"name": username, "pass": password}
self.repo.save(new_user)
# 4. Ще логіка
print(f"Sending email to {username}")
Тепер наш контролер (Офіціант) виглядає чисто і красиво:
# Controller
def register_user(request):
try:
service.register(request.form['username'], request.form['password'])
return "Success"
except Exception as e:
return str(e)
4. 🛠 Практична частина
Час розім'яти пальці! Уявіть, що ви будуєте інтернет-магазин.
Завдання 1: Знайди помилку У коді нижче в метод Репозиторія закралася помилка архітектури. Знайдіть її.
class ProductRepository:
def get_product(self, id):
product = db.query(f"SELECT * FROM products WHERE id={id}")
# Увага сюди:
if product.price < 0:
product.price = 0 # Виправлення ціни
return product
(Підказка: Чи повинен комірник змінювати цінники, якщо вони йому не подобаються?)
Завдання 2: Створи Service
Напишіть клас OrderService.
* У нього має бути метод create_order(user_id, item_id).
* Він має використовувати ProductRepository (щоб дізнатися ціну) та OrderRepository (щоб зберегти замовлення).
* Умова: Якщо ціна товару 0, сервіс має викинути помилку "Товар не для продажу".
Завдання 3: Міні-кейс
Клієнт каже: "Ми переїжджаємо з SQL бази даних на прості текстові файли".
* Який із ваших класів (Service чи Repository) доведеться переписати повністю?
* Чи доведеться змінювати код у Controller?
Завдання 4: А що, якщо... Що, якщо нам потрібно, щоб при створенні замовлення користувачу нараховувалися бонусні бали? В який клас ви допишете цю логіку?
5. 💡 Мислення як у розробника
Як думає Senior Developer, дивлячись на цей код?
-
"Тонкі контролери, Товсті сервіси". Новачки пхають все в контролер. Профі залишають контролер пустим — він просто "регулювальник руху", а вся "магія" відбувається в сервісах.
-
Абстракція — наш друг. Сеньйор думає: "Сьогодні ми пишемо в PostgreSQL, а завтра захочемо писати логи в Redis". Завдяки патерну Repository,
Serviceпро це навіть не дізнається. Ми просто підмінимо один клас репозиторія на інший. -
Типова помилка: Робити "God Service" (Божественний сервіс). Це коли
UserServiceпочинає керувати товарами, замовленнями, доставкою і погодою. Розділяйте:UserServiceдля юзерів,OrderServiceдля замовлень.
6. 🧩 Підсумок
Отже, що ми сьогодні зробили? Ми навели лад у нашому "ресторані" коду.
- Repository: Відповідає за те, де і як беруться дані (SQL, файл, API).
- Service: Відповідає за те, що з цими даними робити (бізнес-правила).
- Ви тепер вмієте: Писати код, який легко читати, легко тестувати і легко змінювати.
У наступному уроці ми подивимося, як автоматично зв'язувати ці компоненти, щоб не створювати їх вручну щоразу. Це називається магічним словом Dependency Injection.
А поки що... це був CS50. Успіхів у коді!