Transactional Script: основа бизнес-логики

Transactional Script: основа бизнес-логики

Сегодня мы рассмотрим паттерн Transactional Script — пожалуй, самый популярный и наиболее старый подход к организации бизнес-логики в приложениях. В простых сценариях и контекстах Domain Driven Design именно он будет часто и широко использоваться.

Этот паттерн был впервые формально описан Мартином Фаулером в его книге Patterns of Enterprise Application Architecture (2002), хотя самим подходом разработчики пользуются столько же, сколько существует программирование. Фаулер каталогизировал его как один из основных способов организации логики в корпоративных приложениях.

Суть паттерна предельно проста: каждый бизнес-сценарий выполняется в одной функции, которая получает входные данные, обрабатывает их, сохраняет результат и возвращает ответ. Но за этой простотой часто скрывается множество подводных камней, которые превращают ваш простой скрипт в сложный ком спагетти.

Давайте разберем все по порядку.

Как правильно писать transactional script

Давайте посмотрим, как организовать Transactional Script на «классическом» примере оформления заказа в интернет-магазине:

public class OrderService { public OrderDto createOrder(CreateOrderRequest request) { // 1. Валидация входных данных validateRequest(request); // 2. Создание заказа Order order = new Order(); order.setCustomerId(request.getCustomerId()); order.setStatus(OrderStatus.CREATED); order.setItems(request.getItems()); // 3. Расчёт итоговой цены BigDecimal total = calculateTotal(request.getItems()); // 4. Применение скидки (если есть) if (request.hasPromoCode()) { PromoCode promo = promoRepository.findByCode(request.getPromoCode()); if (promo != null && promo.isValidFor(total)) { total = total.subtract(promo.getDiscountAmount()); } } // 4.1. Установка итоговой цены order.setTotal(total); // 5. Сохранение orderRepository.save(order); // 6. Отправка события eventPublisher.publish(new OrderCreatedEvent(order.getId())); // 7. Возврат DTO return toDto(order); } }

Ключевой принцип:

Вся бизнес-логика должна быть явно видна в одной функции — «скрипте» транзакции.

Можно выносить:

  • валидацию,
  • маппинг в DTO,
  • обработку ошибок,
  • технические детали (сохранение, публикация событий).

Но не стоит прятать саму суть операции — расчёт цены или применение скидки, в другие классы или методы без явной причины. Иначе логика «размазывается» и понять, что происходит становится сложно.

Ошибки при реализации Transactional Script

Очень частая ошибка — пытаться сделать красиво. Вспоминаем все практики из Clean Code и начинаем разбивать логику, чтобы код соответствовал всем принципам ООП — SOLID, DRY, SLAP и т. п. В результате бизнес-логика начинает размазываться по куче классов, и нам придется потратить много времени, чтобы разобраться.

Вот один из таких примеров:

public OrderDto createOrder(CreateOrderRequest request) { Order order = orderFactory.create(request); // <- Что делает фабрика? Непонятно. orderRepository.save(order); return orderAssembler.toDto(order); // <- А здесь внезапно считается цена? }

Такой код выглядит чистым, но скрывает и размазывает бизнес-логику по куче классов.

Еще один пример, который часто встречается во множестве проектов:

public OrderDto createOrder(CreateOrderRequest request) { // 1. Валидация входных данных validateRequest(request); // 2. Создание заказа // ОШИБКА: Бизнес-логика скрыта! Мы не видим, как считается цена, // применяется ли скидка и проверяются ли остатки. Order order = orderFactory.buildOrderFromRequests(request); // 3. Сохранение orderRepository.save(order); // 4. Отправка события eventPublisher.publish(new OrderCreatedEvent(order.getId())); // 5. Возврат DTO return toDto(order); }

Здесь важная бизнес-логика скрыта в фабрике, которая создает заказ. Мы не видим, как считается цена, применяется ли скидка и проверяются ли остатки.

Transactional Script — это не про «чистоту», а про прозрачность бизнес-процесса.

Когда использовать transactional script

Transactional Script — идеальный выбор в следующих ситуациях:

✔ Простые CRUD-операции, где логика — это валидация и сохранение.

✔ Операции с четкой последовательностью действий, не требующие сложных инвариантов.

✔ Отчеты и аналитика — собрать данные и выдать результат.

✔ В рамках DDD — для простых ограниченных контекстов (Bounded Contexts), где сложность домена простая.

✔ На начальных этапах проекта, когда еще не разобрались в доменной области.

В контексте DDD важно понимать: использование Transactional Script — это нормально.

DDD не требует повсеместного применения Domain Model. Он требует осознанного выбора инструмента под задачу.

Если контекст прост — используйте простой инструмент.

Мой канал в telegram

Начать дискуссию