Краткий ответ

Вопрос «Flow или Apex» часто получает неверный ответ при оценке по удобству разработки или доступности разработчиков. Правильный ответ зависит от четырех технических факторов: объем записей в одной транзакции, необходимость полной атомарности логики, сложность условий и ветвлений, и кто будет поддерживать компонент через год. Flow является правильной опцией по умолчанию для большинства бизнес-автоматизаций, но существуют четкие точки перехода, когда продолжение работы с Flow создает операционный риск, а не просто приводит к «менее элегантному коду».

Данная статья посвящена самому процессу принятия решения: как заранее определить, что логика оправдывает использование Apex, и как предотвратить ситуацию, когда выбор делается по умолчанию, а не на основе обдуманного суждения. Вопрос очистки существующих Flow и процессов автоматизации, которые уже накопились как технический долг, рассматривается в отдельной статье и не является частью данного обсуждения.

Фактические различия между двумя инструментами на уровне платформы

Flow — это декларативный механизм, который во время выполнения транслируется в инструкции, выполняющие DML и SOQL от имени пользователя. Apex же представляет собой скомпилированный код, который работает в тех же губернаторских лимитах, но с прямым контролем над порядком операций. Первое практическое отличие заключается в балкификации (Bulkification): разработчик на Apex явно строит цикл, собирающий все записи в один массив и выполняющий одну DML-операцию, тогда как в Flow легко построить цикл, выполняющий DML-операцию или запрос на каждой итерации по отдельности. Такой подход гораздо быстрее исчерпывает лимит в 101 запрос.

Второе отличие – контроль над транзакциями. Apex позволяет использовать Savepoint и Database.rollback для частичного отката, обработку DmlException на уровне отдельной записи через Database.insert(list, false) и сложную условную логику без ограничения глубины ветвлений. В Flow же обработка ошибок определяется на уровне Fault Path для каждого элемента, и это хорошо работает для линейных сценариев, но становится трудно отслеживаемым при наличии более чем нескольких параллельных путей отказа.

Рамки принятия решения: четыре теста перед выбором инструмента

Тест объема

Эмпирическое правило: если процесс выполняется над одной записью в результате действия пользователя (создание лида, изменение статуса возможности), Flow почти всегда достаточно. Если процесс выполняется над десятками или тысячами записей за один раз – периодическое обновление, обработка пакета, поступающего из интеграции, запланированная очистка данных – Apex с Batchable или Queueable является безопасным выбором, поскольку он обеспечивает полный контроль над Bulkification и управлением губернаторскими лимитами при изменении объема.

Тест атомарности

Необходимо задать вопрос: если часть обновления завершилась неудачей, допустимо ли, чтобы другая часть сохранилась? Если ответ «нет» — например, обновление заказа и создание записи о выставлении счета должны произойти одновременно — Apex с Savepoint является правильным способом гарантировать это. Flow не обеспечивает полный откат между элементами без сложной ручной компенсационной логики.

Тест сложности ветвей

Flow с более чем 6-8 вложенными элементами Decision становится сложным для чтения и дорогим для тестирования, даже если каждая отдельная ветвь проста. Когда сложность бизнес-логики превышает этот уровень, написание той же логики в виде документированной функции Apex с модульными тестами (@isTest) обычно обходится дешевле в обслуживании, даже если начальное время написания дольше.

Тест на поддержку и владение

Необходимо ответить на вопрос, кто будет поддерживать компонент через год, а не на то, кто создает его сейчас. Если команда администраторов должна будет регулярно обновлять бизнес-правила — например, изменять условия скидок или пороговые значения — Flow предпочтительнее, даже если Apex технически «чище», поскольку он доступен для обновления без цикла развертывания. Если изменения требуют знаний схемы данных и регрессионного тестирования, Apex является правильным выбором, даже если есть только небольшая команда разработчиков для его поддержки.

Таблица решений

КритерийВыбрать FlowВыбрать Apex
Объем записей в одной транзакцииДо нескольких десятковСотни до тысяч
Требование атомарности между несколькими объектамиНе критичноКритично — требуется полный откат
Количество ветвей решенияДо ~6-8Более, или рекурсивная логика
Скорость изменения бизнес-правилЧасто, администраторомРедко, требует регрессионного тестирования
Необходимость вызова сложного внешнего APIПростой одиночный вызов (HTTP Callout)Логика повторных попыток, сложная аутентификация или пакетная обработка
Требование автоматизированных тестов (CI)ОграниченноеПолное, @isTest с покрытием
Интеграция с постоянным запланированным заданиемНе подходит напрямуюЕстественно через Schedulable

Пример сценария: компания по производству медицинского оборудования с процессом утверждения заказов

Средняя компания по производству медицинского оборудования с примерно 40 торговыми представителями внедрила один Flow для процесса утверждения заказов: проверка запасов, расчет скидки, создание записи об утверждении и отправка уведомления менеджеру. Вначале это хорошо работало для одиночных заказов. Через полгода добавился новый сценарий — импорт партий заказов из файла интеграции с ERP, который создавал от 200 до 800 заказов одновременно.

Flow, запускаемый через Record-Triggered Flow на уровне «для каждой записи», выполнял запрос на проверку запасов в рамках каждого отдельного запуска. При импорте 500 заказов система превысила лимит в 100 запросов в одной транзакции, и заказы завершились с ошибкой без четкого сообщения об ошибке для пользователя. Команда определила, что проблема не в самом Flow, а в несоответствии между процессом, разработанным для одной записи, и сценарием большого объема, который не существовал во время разработки.

Решение заключалось не в том, чтобы отказаться от Flow. Команда разделила логику: Flow остался отвечать за ручной процесс обработки отдельных заказов (низкий объем, необходимость частых обновлений правил скидок администратором), тогда как процесс пакетного импорта был переведен на Apex Batch Job, который выполнял полную балкаризацию, проверял запасы одним централизованным запросом и выполнял одну DML-операцию для всех записей. Оба механизма вызывали один и тот же общий слой бизнес-логики (один Apex Class, который также вызывался Flow через Invocable Method), чтобы правило скидки не поддерживалось в двух местах.

Распространенные риски и профилактические меры

РискКак это проявляется на практикеПрофилактическая мера
Flow на постепенно увеличивающемся объемеПроцесс работал полгода, затем тихо завершился ошибкой из-за лимитов губернатораЗаранее оценить ожидаемый объем и запланировать переход на Apex до достижения предела
Дублирование бизнес-логики в Flow и ApexДва места рассчитывают скидку по-разномуЦентрализовать бизнес-расчеты в общем слое Apex, который также вызывается Flow
Непредсказуемый порядок запуска триггеровНесколько Flow и триггеров на одном объекте конфликтуютОдин централизованный обработчик триггеров (Trigger Handler) в Apex для каждого критического объекта
Частичная обработка ошибок в сложном FlowЧасть записей обновляется, часть нет, без видимостиПеренести процессы, требующие атомарности, в Apex с Savepoint
Apex без достаточного тестированияНебольшое изменение ломает критический процесс при следующем развертыванииТребовать реальное покрытие тестами, а не только формальный процент, включая сценарии отказов

Чек-лист для принятия решения перед разработкой

  • ☐ Проверен ожидаемый объем на год, а не только текущее состояние
  • ☐ Определена необходимость атомарности процесса между несколькими объектами
  • ☐ Подсчитаны ожидаемые ветви решений в логике
  • ☐ Известно, кто будет поддерживать компонент и как часто будут меняться правила
  • ☐ Проверено, существует ли уже аналогичная логика в Apex или другом Flow для того же объекта
  • ☐ Определен порядок запуска триггеров, если на объекте используется несколько механизмов автоматизации
  • ☐ Если выбран Apex — определены тестовые сценарии, включая частичную неудачу
  • ☐ Если выбран Flow — определен Fault Path для каждого критического элемента

Как это вписывается в общую архитектуру

Выбор правильного инструмента для отдельной автоматизации — это лишь один уровень в более широкой картине архитектуры CRM, где как модель данных, так и разрешения влияют на то, к чему Flow или Apex вообще могут получить доступ. Когда автоматизация выходит за пределы внешней организации — например, проверка запасов с ERP в режиме реального времени — выбор между Flow и Apex также учитывается в соображениях паттернов интеграции и вопроса о том, как Salesforce соединяется с ERP с точки зрения задержки и обработки сбоев.

В организациях, использующих несколько Org-ов, также необходимо проверить, является ли бизнес-логика одинаковой во всех, — тема, обсуждаемая в руководстве Один Org против нескольких Org, и влияющая на вопрос о необходимости централизации логики в общем Apex Package.

Заключение

Выбор между Flow и Apex — это не вопрос навыков команды или личных предпочтений, а результат четырех технических тестов: объема, атомарности, сложности ветвлений и частоты изменений. Flow является правильным решением по умолчанию для большинства автоматизаций, затрагивающих одиночные записи и часто меняющихся. Apex требуется при значительном объеме данных, необходимости полного контроля над транзакциями или когда логическая сложность превышает порог, который все еще можно поддерживать через декларативный интерфейс. Организация, которая внедряет эти тесты как часть рабочего процесса — а не оставляет их на усмотрение каждого разработчика — предотвращает большинство случаев, когда автоматизация, хорошо работавшая вначале, незаметно выходит из строя при увеличении объема.