Salesforce 개발 효율을 높이는 Apex Trigger 모범 사례: Trigger Handler 패턴 구현 가이드

복잡해지는 비즈니스 로직과 트리거 관리의 중요성

세일즈포스 개발 환경에서 Apex 트리거는 데이터 변경에 따라 실시간으로 비즈니스 로직을 실행하는 가장 강력한 도구 중 하나입니다. 하지만 프로젝트의 규모가 커지고 요구사항이 복잡해질수록 트리거 내부에 직접 작성한 코드는 유지보수를 어렵게 만드는 주범이 됩니다. 특히 하나의 오브젝트에 여러 개의 트리거가 생성되거나, 수백 줄의 로직이 트리거 문체(Body) 안에 뒤섞여 있는 경우 코드의 실행 순서를 보장할 수 없고 디버깅은 불가능에 가까워집니다.

스파게티 코드와 실행 순서 제어의 불확실성

많은 초보 개발자나 관리자가 실수하는 부분은 트리거 내부에 모든 비즈니스 로직을 기술하는 것입니다. 이러한 방식은 크게 세 가지 치명적인 문제를 야기합니다.

첫째, 실행 순서의 혼란입니다. 세일즈포스는 동일한 오브젝트에 여러 개의 트리거가 존재할 경우 어떤 트리거가 먼저 실행될지 보장하지 않습니다. 이는 데이터 무결성을 해칠 수 있는 위험 요소입니다.

둘째, 테스트 코드 작성의 어려움입니다. 트리거 내부에 로직이 박혀 있으면 특정 비즈니스 로직만 분리하여 단위 테스트(Unit Test)를 수행하기가 매우 까다롭습니다.

셋째, 가독성 저하와 코드 중복입니다. 비슷한 로직을 여러 이벤트(Before Insert, After Update 등)에서 공유해야 할 때, 트리거 내부에 코드를 짜면 중복 코드가 발생하게 됩니다. 이러한 문제는 결국 세일즈포스의 거버너 리밋(Governor Limits) 초과로 이어져 전체 시스템의 성능을 저하시킵니다.

Trigger Handler 패턴을 활용한 로직 분리와 체계화

이러한 문제를 해결하기 위한 업계 표준은 Trigger Handler 패턴을 도입하는 것입니다. 이 패턴의 핵심은 트리거 자체는 오직 ‘이벤트를 감지하고 핸들러를 호출하는 역할’만 수행하게 하고, 실제 모든 비즈니스 로직은 별도의 Apex 클래스(Handler)에서 관리하는 것입니다.

1. 단일 트리거 원칙 (One Trigger Per Object)

가장 먼저 지켜야 할 원칙은 한 오브젝트당 단 하나의 트리거만 생성하는 것입니다. 이를 통해 실행 순서를 명확하게 제어할 수 있습니다.

2. Handler 클래스 구조화

핸들러 클래스는 트리거 이벤트를 메서드로 분리하여 수용합니다. 예를 들어 beforeInsert, afterUpdate와 같은 메서드를 정의하여 로직을 배치합니다.

3. 실제 코드 구현 예시: Account 오브젝트 기준

먼저 트리거는 다음과 같이 단순하게 구성합니다.

Apex

trigger AccountTrigger on Account (before insert, before update, after insert, after update) {
    AccountHandler handler = new AccountHandler();
    
    if (Trigger.isBefore) {
        if (Trigger.isInsert) {
            handler.onBeforeInsert(Trigger.new);
        } else if (Trigger.isUpdate) {
            handler.onBeforeUpdate(Trigger.oldMap, Trigger.newMap);
        }
    } else if (Trigger.isAfter) {
        if (Trigger.isInsert) {
            handler.onAfterInsert(Trigger.newMap);
        }
    }
}

그리고 실제 로직을 담은 핸들러 클래스를 작성합니다.

Apex

public with sharing class AccountHandler {
    
    // Before Insert 로직: 예를 들어 특정 필드 자동 채우기
    public void onBeforeInsert(List<Account> newList) {
        for (Account acc : newList) {
            if (acc.Industry == null) {
                acc.Industry = 'Other'; // 기본값 설정
            }
        }
    }
    
    // Before Update 로직: 변경 사항 검증
    public void onBeforeUpdate(Map<Id, Account> oldMap, Map<Id, Account> newMap) {
        for (Account acc : newMap.values()) {
            Account oldAcc = oldMap.get(acc.Id);
            if (acc.AccountNumber != oldAcc.AccountNumber) {
                // 계좌 번호 변경 시 로직 수행
            }
        }
    }
    
    // After Insert 로직: 관련 레코드 생성 (예: 기회 생성)
    public void onAfterInsert(Map<Id, Account> newMap) {
        List<Opportunity> oppsToInsert = new List<Opportunity>();
        for (Account acc : newMap.values()) {
            oppsToInsert.add(new Opportunity(
                Name = acc.Name + ' New Opp',
                StageName = 'Prospecting',
                CloseDate = System.today().addMonths(1),
                AccountId = acc.Id
            ));
        }
        if (!oppsToInsert.isEmpty()) {
            insert oppsToInsert;
        }
    }
}

이 구조를 사용하면 트리거는 매우 깨끗하게 유지되며, 모든 로직이 핸들러 클래스에 집중되어 있어 코드 재사용성이 극대화됩니다. 특정 기능이 문제를 일으킬 때 해당 메서드만 확인하면 되므로 유지보수 비용이 획기적으로 줄어듭니다.

결론: 지속 가능한 Salesforce 아키텍처를 위한 첫걸음

Trigger Handler 패턴은 단순한 코딩 스타일을 넘어 대규모 엔터프라이즈 환경에서 세일즈포스 인스턴스의 안정성을 보장하는 필수적인 아키텍처 전략입니다. 단일 트리거 원칙을 고수하고 로직을 핸들러로 분리함으로써, 개발자는 거버너 리밋을 더 효율적으로 관리할 수 있고 향후 비즈니스 로직이 확장되더라도 유연하게 대처할 수 있습니다. 지금 작성하고 있는 트리거가 너무 비대하다면, 지금 바로 핸들러 패턴으로 리팩토링하는 것을 권장합니다. 이는 기술 부채를 줄이고 팀 전체의 생산성을 높이는 가장 빠른 길입니다.


자주 묻는 질문 (FAQ)

Q1. 왜 한 오브젝트에 트리거를 하나만 만들어야 하나요?

세일즈포스 플랫폼은 동일한 오브젝트에서 여러 트리거가 실행될 때 그 순서를 제어하는 기능을 제공하지 않습니다. 두 개의 트리거가 서로 다른 필드를 업데이트하거나 연쇄 반응을 일으킬 경우 예상치 못한 데이터 오류가 발생할 수 있습니다. 단일 트리거 패턴을 사용하면 코드 내에서 if 문이나 핸들러 호출 순서를 통해 실행 로직의 선후 관계를 완벽하게 제어할 수 있습니다.

Q2. 트리거 핸들러 패턴이 거버너 리밋(Governor Limits) 해결에 도움이 되나요?

직접적인 해결책은 아니지만 리밋 관리를 훨씬 수월하게 해줍니다. 핸들러 패턴을 쓰면 코드가 구조화되므로, 중복되는 SOQL 쿼리나 DML 작업을 하나의 리스트로 모아서 처리(Bulkification)하기가 훨씬 용이해집니다. 로직이 파편화되어 있을 때 발생하는 ‘루프 내 쿼리 실행’ 같은 실수를 방지하는 데 큰 도움이 됩니다.

Q3. 인터페이스(Interface)를 활용한 핸들러 프레임워크를 꼭 써야 할까요?

프로젝트 규모에 따라 다릅니다. 소규모 프로젝트라면 위 예시처럼 단순한 핸들러 클래스만으로도 충분합니다. 하지만 대규모 팀 프로젝트나 복잡한 패키지 개발의 경우, ITriggerHandler 인터페이스를 정의하고 트리거 실행을 자동화하는 프레임워크를 도입하면 핸들러 작성 방식을 통일하고 공통 유틸리티(재귀 실행 방지 등)를 일괄 적용할 수 있어 더욱 강력한 제어가 가능합니다.

댓글 남기기