Apex Trigger vs Flow

실무에서 Flow가 더 위험해지는 순간들

세일즈포스 개발자로 일하다 보면 자동화를 구현할 때 가장 많이 고민하게 되는 선택지가 있다. Apex Trigger를 사용할지, 아니면 Flow로 처리할지에 대한 문제다. Salesforce는 최근 몇 년간 Flow를 중심으로 한 로우코드 자동화를 적극적으로 권장하고 있고, 실제로 간단한 요구사항에서는 Flow가 빠르고 효율적인 선택이 된다.

하지만 실무에서는 “Flow로 구현했는데 운영 중에 장애가 났다”거나, “Apex로 다시 구현하고 나서야 문제가 사라졌다”는 상황을 종종 마주하게 된다. 이 글에서는 단순한 기능 비교가 아니라, 실무에서 Flow가 오히려 더 위험해지는 순간은 언제인지, 그리고 어떤 기준으로 Apex Trigger와 Flow를 선택해야 하는지를 정리해본다.

Governor Limit에 더 민감해지는 Flow

Salesforce의 Governor Limit은 Apex뿐 아니라 Flow에도 동일하게 적용된다. 문제는 Flow에서는 이 제한을 개발자가 체감하기 어렵다는 점이다.

Record-Triggered Flow는 내부적으로 SOQL과 DML을 자동으로 생성해 실행한다. Flow 화면에서는 단순히 “Get Records”, “Update Records”처럼 보이지만, 실제 실행 시에는 Apex와 동일하게 limit이 누적된다. 특히 Apex 로직과 Flow가 같은 트랜잭션 안에서 함께 실행되면, CPU Time이나 DML limit 초과로 이어지는 경우가 많다.

이런 문제는 테스트 환경에서는 잘 드러나지 않고, 데이터가 쌓인 이후 운영 환경에서 처음 발생하는 경우가 많아 더 위험하다.

대량 데이터 처리에서 드러나는 한계

Apex Trigger는 기본적으로 bulk 처리를 전제로 설계된다. 개발자가 직접 컬렉션을 다루고, SOQL과 DML을 한 번씩만 실행하도록 제어할 수 있기 때문이다.

반면 Flow는 반복 구조가 시각적으로 명확하지 않다. 특히 다음과 같은 구조는 실무에서 자주 문제를 일으킨다.

  • Record-Triggered Flow에서 관련 레코드를 조회
  • Loop를 사용해 레코드를 하나씩 처리
  • Loop 안에서 Update Records 실행

설계 단계에서는 문제가 없어 보이지만, 관련 레코드 수가 늘어나면 DML limit에 빠르게 도달한다. 데이터가 적을 때는 정상 동작하다가, 어느 순간 갑자기 장애로 이어지는 전형적인 패턴이다.

복잡해질수록 유지보수가 어려워지는 Flow

Flow는 처음에는 직관적이고 이해하기 쉽다. 하지만 요구사항이 추가되면서 Decision 요소가 늘어나고, Subflow가 연결되고, 예외 처리를 위한 Fault Path가 추가되면 전체 흐름을 파악하기가 점점 어려워진다.

코드는 코드 리뷰와 버전 관리로 변경 이력을 추적하기 쉽지만, Flow는 변경이 누적될수록 영향 범위를 파악하기가 쉽지 않다. 여러 명이 동시에 관리하는 조직에서는 이 문제가 더욱 크게 드러난다.

해결

Flow에서 문제가 되는 대표적인 구조 예시

실무에서 자주 보게 되는 Flow 구조를 예로 들어보자.

  • Account 업데이트 시 실행되는 Record-Triggered Flow
  • 특정 조건을 만족하면 관련 Contact 조회
  • Loop를 돌며 Contact를 하나씩 업데이트

Flow 상에서는 자연스러운 구조지만, 내부적으로는 다음과 같은 문제가 발생한다.

  • Loop 안에서 DML이 반복 실행됨
  • Contact 수가 많아질수록 DML limit 소모 증가
  • 다른 Apex 로직과 함께 실행될 경우 limit 초과 가능성 증가

Flow에서는 이런 구조가 즉각적으로 위험해 보이지 않기 때문에, 데이터가 늘어난 뒤에야 문제가 드러나는 경우가 많다.

같은 로직을 Apex Trigger로 구현했을 때

위와 동일한 요구사항을 Apex Trigger로 구현하면 구조가 훨씬 명확해진다.

trigger AccountTrigger on Account (after update) {
    Set<Id> accountIds = new Set<Id>();

    for (Account acc : Trigger.new) {
        if (acc.Status__c == 'Active') {
            accountIds.add(acc.Id);
        }
    }

    if (!accountIds.isEmpty()) {
        List<Contact> contactsToUpdate = [
            SELECT Id, Status__c
            FROM Contact
            WHERE AccountId IN :accountIds
        ];

        for (Contact con : contactsToUpdate) {
            con.Status__c = 'Active';
        }

        update contactsToUpdate;
    }
}

이 코드의 핵심은 다음과 같다.

  • SOQL은 한 번만 실행
  • DML도 한 번만 실행
  • 레코드 수가 늘어나도 구조는 변하지 않음
  • Governor Limit에 대한 예측이 쉬움

실무에서는 이런 명시적인 bulk 처리 구조가 안정성 측면에서 큰 차이를 만든다.

Flow와 Apex를 함께 사용하는 현실적인 방식

Flow를 완전히 배제할 필요는 없다. 오히려 실무에서는 Flow와 Apex를 역할에 따라 분리해서 사용하는 방식이 가장 안정적이다.

Flow의 역할은 다음과 같다.

  • Record-Triggered Flow에서 조건 판단
  • 관리자가 수정해야 하는 비즈니스 조건 관리
  • Apex 호출 트리거 역할

Apex의 역할은 다음과 같다.

  • 대량 데이터 처리
  • 복잡한 로직 실행
  • 성능에 민감한 연산

이를 위해 Invocable Apex를 사용하는 경우가 많다.

public class UpdateContactsService {

    @InvocableMethod
    public static void updateContacts(List<Id> accountIds) {
        List<Contact> contacts = [
            SELECT Id, Status__c
            FROM Contact
            WHERE AccountId IN :accountIds
        ];

        for (Contact con : contacts) {
            con.Status__c = 'Active';
        }

        update contacts;
    }
}

Flow에서는 이 메서드를 호출만 하고, 실제 데이터 처리 책임은 Apex에 맡긴다.
이 구조의 장점은 명확하다.

  • Flow는 단순하고 읽기 쉬움
  • Apex는 성능과 안정성 확보
  • 역할 분리가 명확해 유지보수 비용 감소

Flow에서 특히 조심해야 할 패턴

실무 기준으로 Flow에서 문제가 되는 패턴은 거의 정해져 있다.

  • Loop 안에서 레코드 업데이트
  • 동일 객체에 여러 개의 Record-Triggered Flow 존재
  • 조건 분기마다 Get Records 반복 실행
  • Apex, Flow, 기존 자동화가 동시에 실행되는 구조

이 패턴들이 겹치기 시작하면, 문제는 코드가 아니라 설계 구조 자체에서 발생한다.

Flow는 분명 강력한 자동화 도구다. 하지만 모든 상황에서 Apex Trigger를 대체할 수 있는 만능 해결책은 아니다. 특히 다음과 같은 경우에는 Flow가 오히려 더 위험해질 수 있다.

  • 대량 데이터가 동시에 처리되는 트랜잭션
  • 여러 자동화가 한 객체에 중첩된 구조
  • 복잡한 조건과 반복 로직이 필요한 시나리오

이런 경우에는 Apex Trigger 또는 Apex 기반 로직을 병행하는 것이 안정성과 유지보수 측면에서 훨씬 낫다. 중요한 것은 “Flow냐 Apex냐”의 문제가 아니라, 현재 요구사항에 가장 적합한 도구를 선택하는 개발자의 판단이다.

자주 묻는 질문 (FAQ)

Flow는 Apex Trigger보다 항상 성능이 나쁜가?

그렇지 않다. Before-Save Record-Triggered Flow는 DML 비용을 거의 발생시키지 않아 단순 필드 업데이트에서는 Apex보다 빠를 수 있다. 다만 복잡한 조건, 반복 처리, 대량 데이터 환경에서는 Apex가 더 안정적인 경우가 많다.

같은 오브젝트에 Flow와 Apex Trigger를 함께 사용해도 괜찮은가?

가능하지만 주의가 필요하다. 실행 순서와 Governor Limit 누적으로 인해 예상치 못한 결과가 발생할 수 있다. 역할을 명확히 분리하고 중복 로직을 피하는 것이 중요하다.

모든 자동화를 Flow로 통일하는 것이 좋은 전략인가?

초기에는 관리가 쉬워 보일 수 있지만, 장기적으로는 권장되지 않는다. Flow는 단순 자동화에 강점이 있고, Apex는 복잡한 로직과 성능 제어에 강점이 있다. 두 도구를 상황에 맞게 병행하는 것이 가장 현실적인 전략이다.

댓글 남기기