Какие недостатки хранимых процедур заставляют переносить логику из БД в код приложения?

Ответ

Перенос бизнес-логики из хранимых процедур (Stored Procedures) в код приложения продиктован принципами современной разработки:

Недостаток хранимых процедур Последствие Решение в коде приложения
Сложность тестирования Невозможность изолированного unit-тестирования, зависимость от конкретной БД для интеграционных тестов. Логика покрывается модульными и интеграционными тестами с использованием in-memory БД или моков.
Нарушение инкапсуляции Распределение логики между БД и приложением, усложнение понимания системы. Четкое разделение: БД — хранение данных, приложение — бизнес-логика.
Вендорная привязка Синтаксис и возможности специфичны для СУБД (Oracle PL/SQL, PostgreSQL PL/pgSQL), что блокирует миграцию. Использование ORM (Hibernate) или SQL-мапперов (MyBatis) с диалектами позволяет легче менять БД.
Сложность CI/CD и контроля версий Хранимые процедуры часто выпадают из стандартного цикла сборки и системы контроля версий (Git). Код приложения полностью интегрирован в Git и pipeline сборки/деплоя.

Пример: Вместо вызова процедуры CALL transfer_funds(acc_from, acc_to, sum); логика явно описывается в сервисе:

@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    Account from = accountRepository.findById(fromId).orElseThrow();
    Account to = accountRepository.findById(toId).orElseThrow();
    // Вся бизнес-логика (проверки, расчеты) здесь
    from.withdraw(amount);
    to.deposit(amount);
    accountRepository.saveAll(List.of(from, to));
    auditService.logTransaction(fromId, toId, amount); // Аудирование тоже в коде
}

Хранимые процедуры остаются оправданы для сложных аналитических отчетов или критичных по производительности массовых операций с данными.

Ответ 18+ 🔞

Да ты посмотри, что творят эти хранимые процедуры в базе! Ну реально, как будто в прошлом веке застряли, ей-богу.

Смотри, какие подводные камни всплывают, если всю логику в SQL-скрипты засунуть:

В чём косяк хранимок К чему это приводит А как надо-то, по-человечески
Тестировать нихуя невозможно Ну как ты это оттестируешь изолированно? Вечно нужна живая база, поднятая, настроенная — просто пиздец, а не разработка. Всё в коде приложения: юнит-тесты на логику, интеграционные — с in-memory базой или моками. Красота!
Логика распидарашена по всем щелям Половина — в приложении, половина — в недрах PostgreSQL или Oracle. Потом ищи-свищи, кто где что сломал. Чёткое разделение: база — это склад данных, а приложение — это мозги, где вся бизнес-логика и живёт.
Привязанность к вендору намертво Написал на PL/pgSQL — и всё, попал как кур в ощип. Захотел на другую СУБД переехать — переписывай всё, блядь, с нуля. Используешь ORM типа Hibernate или MyBatis — они с диалектами работают, и миграция становится делом техники, а не героизма.
С контролем версий и CI/CD — полный атас Эти процедуры часто в стороне от Git болтаются, деплоятся отдельными скриптами, в общем, бардак ебаный. Код приложения — вся логика в Git, в одном пайплайне сборки и деплоя. Порядок, сука!

Вот смотри, как это выглядит на практике:

Раньше вызывали какую-нибудь мутную процедуру: CALL transfer_funds(123, 456, 1000);. А что там внутри творится — хуй его знает, в недрах базы.

А теперь вся логика — вот она, прозрачная, перед тобой:

@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
    Account from = accountRepository.findById(fromId).orElseThrow();
    Account to = accountRepository.findById(toId).orElseThrow();
    // Вся бизнес-логика (проверки, расчеты) здесь
    from.withdraw(amount);
    to.deposit(amount);
    accountRepository.saveAll(List.of(from, to));
    auditService.logTransaction(fromId, toId, amount); // Аудирование тоже в коде
}

Красота же, ёпта! Всё видно, всё тестируется, всё в одном месте. Хранимые процедуры, конечно, не вымирают совсем — для тяжёлых аналитических отчётов или операций, где овердохуища данных гонять, они ещё могут быть полезны. Но основную-то логику — в код, блядь, в код! Там ей и место.