Паттерн «Транзакционный сценарий»

Transaction Script (М. Фаулер) организует бизнес-логику как одну процедуру на каждый конкретный пользовательский сценарий. Внутри этой процедуры выполняются все проверки, изменения данных и вызовы внешних сервисов. Логика работает в рамках единой атомарной транзакции: если на любом шаге возникнет исключение, всё откатывается. Такой подход прост и хорошо подходит там, где предметная область ещё не требует сложной объектной модели или когда важна прозрачность операций.

Паттерн Active Record

Active Record (AR) — это способ хранить данные, при котором объект модели одновременно инкапсулирует строку таблицы и методы чтения/записи, а заодно может содержать часть доменной логики. Фреймворки вроде Django поставляются с AR-ORM «из коробки», поэтому связка «TS + AR» получается практически бесплатно.

Django-пример: оформление брони авиабилета

Контексты из предыдущего примера: bookings, payments, flights.

# bookings/services.py
from django.db import transaction
from django.utils import timezone
from bookings import exceptions
from bookings.models import Booking, Seat, Flight
from payments.services import charge_card   # отдельный сервис оплаты

@transaction.atomic()
def book_flight(user, flight_id, seat_class, card_token) -> Booking:
    """
    Оформить бронь и сразу списать деньги.
    Все шаги проходят в одной транзакции. Если что-то пойдёт не так,
    изменения не попадут в БД.
    """
    # 1. Блокируем нужные строки, чтобы избежать гонки мест
    flight = Flight.objects.select_for_update().get(id=flight_id)

    seat = (
        Seat.objects
        .select_for_update()              # блокировка строк
        .filter(flight=flight,
                seat_class=seat_class,
                status=Seat.Status.AVAILABLE)
        .first()
    )
    if seat is None:
        raise exceptions.NoAvailableSeat

    # 2. Помечаем место занятым
    seat.status = Seat.Status.RESERVED
    seat.save(update_fields=["status"])

    # 3. Создаём запись бронирования
    booking = Booking.objects.create(
        user=user,
        flight=flight,
        seat=seat,
        price=flight.get_price(seat_class),
        booked_at=timezone.now(),
        status=Booking.Status.PENDING,
    )

    # 4. Списываем оплату внешним сервисом
    payment = charge_card(
        user_id=user.id,
        amount=booking.price,
        currency="RSD",
        card_token=card_token,
        reference=str(booking.id),
    )

    # 5. Обновляем бронь и подтверждаем
    booking.payment_id = payment.id
    booking.status = Booking.Status.CONFIRMED
    booking.save(update_fields=["payment_id", "status"])

    return booking

Что здесь делает Transaction Script

Оркестрирует сценарий: проверяет наличие места, создаёт бронь, вызывает внешний платёжный сервис, подтверждает результат.

Ключевые моменты

Что здесь делает Active Record

Каждая модель (Flight, Seat, Booking) сама читает и пишет себя:

Плюсы подхода