Salesforce로 개발을 하기 위해 기능 설계를 하는 단계에서, 무엇보다도 가장 중요하고 고려해 보아야 할 것 중 하나가 Salesforce의 Governor Limits라는 것입니다.

 

대량의 데이터를 처리해야 하는 기능을 개발하는 단계에 있어, 이 제한이 가장 짜증나는 부분이며 예상치 못하고 있다간, 이 제한으로 인해 로직을 변경해야 하는 일이 발생합니다.

 

이번 포스팅에서는 Gorvernor Limits에 대해 설명하겠습니다.


# 목차

  • Governor Limits 란?

  • 남은 제한 확인 방법

  • 회피 방법 예시


 

# Governor Limits 란?

Gorvernor Limits이란, 멀티 테넌트 환경에서 실행하기 위해, Apex 런타임 엔진이 Apex 코드 또는 프로세스가 공유 자원을 독점하지 못하도록 강제로 제한하는 것을 말합니다.

즉, Salesforce 측에서 지나친 독점을 막기 위해 지정해 둔 제한이라고 할 수 있습니다.

(물론, 라이센스 단계에 따라 한계치가 바뀌기도 합니다.)

 

Salesforce의 거의 모든 부분에 있어서 이 제한이 걸려있다고 할 수 있기 때문에, 설계나 개발 단계에서 항상 고려해야 하는 부분입니다.

 ex) 개체 등록 가능 수, 레코드 등록 가능 수, 1트랜잭션 에서 SELECT 가능한 데이터 수, Apex 클래스 등록 가능수, Apex 코드 문자수 등등

 

Gorvernor Limits에 대한 상세내용은 다음을 참고해주세요.

Salesforce Developer Guide - Apex Governor Limits

Salesforce Developer Guide - Execution Governors and Limits


 

# 남은 제한 확인 방법

Apex에는 Gorvernor Limits을 확인하기 위한 "Limits" 클래스가 존재합니다.

 

Limits 클래스에 대한 상세내용은 다음을 참고해주세요.

Salesforce Developer Guide - Limits

 

Apex 코드 상에서는 이 Limit을 통해 남아 있는 제한 수를 확인하는 것이 가능합니다.가끔 확인을 위해 SOQL 쿼리의 실행 수나 스텝 수를 확인하는 메소드를 주로 사용했지만, 이 외에도 여러가지로 많았기 때문에 전부 확인해 보았습니다.ex) 샘플 코드

System.debug('limits.getAggregateQueries()                 = ' + limits.getAggregateQueries());                     // SOQL 쿼리 문이 처리 된 집계 쿼리의 수
System.debug('limits.getLimitAggregateQueries()            = ' + limits.getLimitAggregateQueries());                // SOQL 쿼리 문에서 처리 할 수 있는 집계 쿼리의 총 수
System.debug('limits.getCallouts()                         = ' + limits.getCallouts());                             // 처리 된 Web 서비스의 수
System.debug('limits.getLimitCallouts()                    = ' + limits.getLimitCallouts());                        // 처리 할 수있는 Web 서비스의 총 수
System.debug('limits.getCpuTime()                          = ' + limits.getCpuTime());                              // 현재의 트랜잭션에서 사용 된 Salesforce 서버의 CPU 시간 (밀리 초)
System.debug('limits.getLimitCpuTime()                     = ' + limits.getLimitCpuTime());                         // 트랜잭션에서 사용 가능한 최대 CPU 시간 (밀리 초)
System.debug('limits.getDMLRows()                          = ' + limits.getDMLRows());                              // DML 제한에 포함되는 모든 문을 사용하여 처리 된 레코드의 수
System.debug('limits.getLimitDMLRows()                     = ' + limits.getLimitDMLRows());                         // DML 제한에 포함되는 모든 문을 사용하여 처리 할 수있는 총 레코드 수
System.debug('limits.getDMLStatements()                    = ' + limits.getDMLStatements());                        // 호출 된 DML 문 (insert,update또는 database.EmptyRecycleBin 메소드 등)의 수
System.debug('limits.getLimitDMLStatements()               = ' + limits.getLimitDMLStatements());                   // 호출 할 수있는 DML 문 또는database.EmptyRecycleBin 메소드의 총 수
System.debug('limits.getEmailInvocations()                 = ' + limits.getEmailInvocations());                     // 호출 된 메일 호출 수(sendEmail 등) 
System.debug('limits.getLimitEmailInvocations()            = ' + limits.getLimitEmailInvocations());                // 호출 할 수있는 메일 호출의 총 개수(sendEmail 등)
System.debug('limits.getFindSimilarCalls()                 = ' + limits.getFindSimilarCalls());                     // getSoslQueries 같은 값을 반환
System.debug('limits.getLimitFindSimilarCalls()            = ' + limits.getLimitFindSimilarCalls());                // getLimitSoslQueries 같은 값을 반환
System.debug('limits.getFutureCalls()                      = ' + limits.getFutureCalls());                          // 실행 된 future 주석이 있는 메소드의 수
System.debug('limits.getLimitFutureCalls()                 = ' + limits.getLimitFutureCalls());                     // 실행할 수 future 주석이 있는 메소드의 총 개수
System.debug('limits.getHeapSize()                         = ' + limits.getHeapSize());                             // 힙에 사용 된 메모리의 대략의 용량 (Byte 단위)
System.debug('limits.getLimitHeapSize()                    = ' + limits.getLimitHeapSize());                        // 힙을 사용할 수 있는 메모리의 총 용량 (Byte 단위)
System.debug('limits.getLimitMobilePushApexCalls()         = ' + limits.getLimitMobilePushApexCalls());             // 모바일 푸시 알림으로 1 트랜잭션 당 허용되는 Apex 총 통화 수
System.debug('limits.getQueries()                          = ' + limits.getQueries());                              // 발행 된 SOQL 쿼리의 수
System.debug('limits.getLimitQueries()                     = ' + limits.getLimitQueries());                         // 발급 할 SOQL 쿼리의 총 수
System.debug('limits.getQueryLocatorRows()                 = ' + limits.getQueryLocatorRows());                     // Database.getQueryLocator 메소드에서 반환 된 레코드 수
System.debug('limits.getLimitQueryLocatorRows()            = ' + limits.getLimitQueryLocatorRows());                // Database.getQueryLocator 메소드에서 반환 할 수있는 총 레코드 수
System.debug('limits.getQueryRows()                        = ' + limits.getQueryRows());                            // SOQL 쿼리의 발행에서 반환 된 레코드 수
System.debug('limits.getLimitQueryRows()                   = ' + limits.getLimitQueryRows());                       // SOQL 쿼리의 발행에 반환 할 수있는 총 레코드 수
System.debug('limits.getQueueableJobs()                    = ' + limits.getQueueableJobs());                        // 트랜잭션마다 추가된 큐 중,  큐 처리가 가능한 작업 수
System.debug('limits.getLimitQueueableJobs()               = ' + limits.getLimitQueueableJobs());                   // 트랜잭션마다 추가된 큐 중,  큐 처리가 가능한 작업의 최대수
System.debug('limits.getRunAs()                            = ' + limits.getRunAs());                                // getDMLStatements 같은 값을 반환
System.debug('limits.getLimitRunAs()                       = ' + limits.getLimitRunAs());                           // getLimitDMLStatements 같은 값을 반환
System.debug('limits.getSavepointRollbacks()               = ' + limits.getSavepointRollbacks());                   // getDMLStatements 같은 값을 반환
System.debug('limits.getLimitSavepointRollbacks()          = ' + limits.getLimitSavepointRollbacks());              // getLimitDMLStatements 같은 값을 반환
System.debug('limits.getSavepoints()                       = ' + limits.getSavepoints());                           // getDMLStatements 같은 값을 반환
System.debug('limits.getLimitSavepoints()                  = ' + limits.getLimitSavepoints());                      // getLimitDMLStatements 같은 값을 반환
System.debug('limits.getSoslQueries()                      = ' + limits.getSoslQueries());                          // 발행 된 SOSL 쿼리의 수
System.debug('limits.getLimitSoslQueries()                 = ' + limits.getLimitSoslQueries());                     // 발급 할 SOSL 쿼리의 총 수
//System.debug('limits.getAggregateQueries()                 = ' + limits.getChildRelationshipsDescribes());          // 반환 된 자식 관계 개체의 수

 

☞실행 결과

 

Apex 코드 내에서 확인하는 방법 이외에, "Workbench" 라는 사이트를 통해서 확인하는 방법도 있습니다.

 

Workbench(워크 벤치)를 이용해서 확인하는 방법은 다음과 같습니다.

먼저 확인 대상이 되는 환경에 로그인 하기 위해, [I agree to the terms of service]에 체크하고 [Login with Salesforce] 버튼을 눌러 로그인을 합니다.

로그인 화면 표시

 

로그인 허용, 환경 로그인

환경에 로그인이 되었다면, [Utilities - REST Explorer]를 선택해주세요.

그 후 표시되는 REST Explorer 화면에서, "/services/data/v48.0/limits" 를 입력하고 [Execute] 버튼을 누르면 현재 사용 가능한 Gorvernor Limits의 수가 종류별로 표시가 됩니다.


 

# 회피 방법 예시

 

Apex 개발을 하다보면, 데이터 처리 등에 있어 Gorvernor Limits이 걸리는 경우, 제한이 걸리지 않게 하기 위해 로직을 변경하여 회피해야 합니다.

 

이번 예시에서는 작성자 본인이 평소 일을 하면서 자주 사용하는 회피 방법 3가지에 대해 설명하겠습니다.

( 혹시 고수분들 중에, 더 나은 방법이라 생각되는 것이 있으시다면 알려주시기 바랍니다. (_o_) )

 

1. Batch를 통한 일괄 처리 방법 

Apex에서는 다량의 데이터(수천~수백만 개의 데이터)를 처리하기 위해, "Database.Batchable" 이라는 인터페이스를 제공하고 있습니다.

이 Batch를 사용하면 해당 플랫폼의 제한을 초과하지 않고, 레코드를 Batch 단위로 나누어, 비동기로 일괄 처리할 수 있습니다. 

그렇기 때문에, 데이터의 정리 및 보관 등, 처리해야 하는 레코드의 수가 많을 경우, Batch를 통한 일괄 처리가 적합합니다.

Apex Batch를 통한 일괄 처리 가이드

Salesforce Trailhead - Apex 일괄처리(Batch)

 

"Database.Batchable" 인터페이스는 다음과 같이 구성되어 있습니다.

global class MyBatchClass implements Database.Batchable<sObject> {
    global (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
        // execute에 전달하여 일괄 처리할 레코드를 습득
        // 주로, Database.QueryLocator를 통해 레코드 습득
        // QueryLocator를 사용하는 경우 Gorvernor Limits "SOQL 쿼리로 검색된 레코드의 총 개수"가 무시되고 최대 5,000만 레코드까지 조회가능
        // Iterable를 사용하는 경우 Gorvernor Limits "SOQL 쿼리로 검색된 레코드의 총 개수"가 그대로 적용
    }

    global void execute(Database.BatchableContext bc, List<P> records){
        // start로 부터 전달 된 레코드 일괄 처리
        // 기본 Batch 크기는 200 레코드
        // 레코드 배치가 start 메소드로부터 받은 순서대로 실행된다는 보장이 없음
    }

    global void finish(Database.BatchableContext bc){
        // 레코드 일괄 처리 실행 후 처리할 마무리 작업(이메일 전송 등)
        // 모든 배치가 처리 된 후 1회만 호출
    }
}

 

즉, 그림으로 표현하면 아래와 같이 됩니다.

Batch 실행도 

 

Database.Batchable의 사용 예시는 다음과 같습니다.

/**
 * @description 회원명 갱신 Batch
 */
global class UpdateMemberName implements Database.Batchable<sObject>, Database.Stateful {

    /**
     * @description 일괄 처리 대상 레코드 SELECT
     */
    global Database.QueryLocator start(Database.BatchableContext bc) {

        // 회원 레코드 SELECT, return으로 execute에 레코드 전달
        return Database.getQueryLocator(
            'SELECT '
            + ' ID '
            + ', Name '
            + 'FROM '
            + ' Member__c '
        );
    }

    /**
     * @description 레코드 일괄 처리 메서드(비동기)
     */
    global void execute(Database.BatchableContext bc, List<Member__c> memberList){
        
        for (Member__c member : memberList) {

            // 회원 이름 뒤에 "님" 추가
            member.Name = member.Name + ' 님 ';
        }

        // 회원 레코드 UPDATE
        update memberList;
    }    
    global void finish(Database.BatchableContext bc){

        // 현재 처리중인 Batch 의 Job 정보 표시(단순 확인용)
        AsyncApexJob job = [SELECT Id, Status, NumberOfErrors, 
            JobItemsProcessed,
            TotalJobItems, CreatedBy.Email
            FROM AsyncApexJob
            WHERE Id = :bc.getJobId()];
        System.debug(job);

        // execute 일괄 처리 이후의 회원 정보 확인
        List<Member__c> memberList = [SELECT Id, Name FROM Member__c];
        System.debug(memberList); 
    }    
}

 

Batch 클래스를 작성하였다면, 작성한 Batch를 실행해보겠습니다.

지금은 개발자 콘솔에서 실행하겠습니다만, 실제 개발에서는 Apex 코드 내에서 실행하시면 되겠습니다.

 

[개발자 콘솔 - Open Execute Anonymous Window]로 Apex 코드 입력창을 표시합니다.

그 후, 아래의 코드를 작성한 후 [Execute]버튼을 눌러 Batch를 실행합니다.

// 실행 대상 Batch 클래스 정의
UpdateMemberName umnBatch = new UpdateMemberName(); 

// Database.executeBatch를 통해 Batch 실행
// prm1 : 실행 대상 Batch
// prm2 : 1개의 Batch 내에서의 일괄 처리 레코드 수
Id batchId = Database.executeBatch(umnBatch, 200);

 

2. 1개의 트랜잭션에서 실행 가능한 SOQL 제한을 회피하는 방법

Gorvernor Limits 가이드를 보셨다면 아마 아시겠지만, 1개의 트랜잭션에서 실행 가능한 SOQL의 쿼리 수에 제한이 있습니다.

즉, 가능한 반복문에서는 SOQL 쿼리를 사용하지 않는 것이 중요하다는 의미가 됩니다.

 

혹시, 반복문 내에서 다른 데이터의 정보를 습득해서 수정하는 처리 등을 해야할 필요가 있을 경우에는 로직을 통해 회피하는 수 밖에 없습니다.

 

여기서는 주로 제가 사용하는 방법에 대해 설명하겠습니다.

(아마 경험 많은 개발자 분들이면 충분히 사용하고 있을 법한 로직이 되겠습니다만..)

 

간단히 설명하면 다음과 같은 방법이 되겠습니다.

  ① 반복문에 들어가기 이전에 필요로 하는 레코드를 전부 SELECT 해서 Map에 저장

    ※ 여기서도 1개의 SOQL 쿼리로 SELECT 가능한 Gorvernor Limits(50,000개)이 있기 떄문에, 필요에 따라 WHERE로 필요한 레코드만 SELECT

  ② 반복문에서는 그 Map으로부터 데이터를 습득해서 사용

 

그럼 샘플 코드로 확인해보겠습니다.

/**
 * @description 도서 대여중인 회원의 이름
 */
private void getMemberNameByBookLender() {

    // 모든 회원을 SELECT해서 Map에 추가 (Key:레코드ID, Value: 회원 개체)
    Map<ID, Member__c> allMemberMap = (Map<ID, Member__c>)[SELECT Id, Name FROM Member__c];
    List<Book__c> allBookList = [SELECT Id, Name, Lender__c FROM Book__c];

    for (Book__c book : allBookList) {

        if (String.isBlank(book.Lender__c) || allMemberMap.containsKey(book.Lender__c)) {
            // 도서가 대여중이 아닌 경우 또는 대여 중인 회원 정보를 찾지 못한 경우, 다음 도서로 넘어감

            continue;
        }

        // 회원Map에서 회원ID가 일치하는 회원 정보를 검색
        Member__c lender = allMemberMap.get(book.Lender__c);

        System.debug('도서명 : ' + book.Name + ' 대여 중인 회원명 : ' + lender.Name);
    }
}

 

이러한 형식으로 반복문에서 SOQL 쿼리를 사용하지 않고도 필요한 데이터를 습득하고 사용할 수 있습니다.

특히, Map을 잘 사용할 수 있도록 익혀두면, 여러 방면에서 유용하게 사용할 수 있습니다.

 

3. 1개 컬렉션으로 표시 가능한 데이터 제한 수

어떻게 보면, Gorvernor Limits으로 인해 가장 짜증나는 경우가 이 경우일 수 있습니다.

Gorvernor Limits 중, 1개의 컬렉션에서 표시 가능한 데이터 수가 있는데 쉽게 말하면, 

검색 화면에서 데이터를 검색했을 때, 검색 결과가 제한 수인 1000개 이상이 되면 에러가 발생한다는 것입니다.

 

 

이 부분에 대해서는 딱히 1000개 이상을 표시하기 위한 회피 방법이 없으므로, 로직상에서 1000개 이하가 되도록 데이터에 제한을 걸고, 화면에 INFO 메시지로 "최대 표시 가능한 데이터 수를 초과했으므로, 선두 1000개만 표시합니다." 라던가, "검색 조건을 통해 결과를 좁혀주세요." 라는 등의 방법을 사용하고 있습니다.

( 좋은 방법 있으면 알려주시면 감사하겠습니다. )

 

샘플 코드

final integer VF_COLLECTION_ITEM_LIMIT = 1000;
if (VF_COLLECTION_ITEM_LIMIT < memberList.size()) {
    // 검색 결과가 1개 컬렉션에서 표시 가능한 데이터 제한수(1000개)를 초과한 경우

    // 화면에 메시지 표시
    ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.INFO, '표시 가능한 데이터 수 초과, 검색 조건을 지정해 대상을 좁히시오.'));

    while (VF_COLLECTION_ITEM_LIMIT < memberList.size()) {

        // 제한 수에 도달할 때 까지, 맨 뒤의 요소를 삭제
        memberList.remove(memberList.size() - 1);
    }
}

 

이 외에 가장 큰 문제가 되는 부분이 바로 csv출력 입니다.

csv를 출력할 때에도 출력에 사용되는 컬렉션에 1000개 이상의 데이터가 있는 경우, 위와 동일한 에러가 발생하게 됩니다.

 

작성자의 경우, 이 문제에 대해서는 정적으로 2개 이상의 컬렉션를 정의 해놓은 다음, 각각의 컬렉션을 순서대로 출력하는 방법을 사용하고 있습니다.

허나, 이 방법을 사용할 때의 문제점으로는 각 컬렉션에 분할에서 넣기 위한 로직이 필요하다는 것입니다.

( 이 부분에 대해서도 좋은 방법 있으면 공유 해주시면 감사하겠습니다. )


 

이것으로, Salesforce로 개발을 할 때에 가장 신경써야 하는 부분 중 하나인 Gorvernor Limits에 대해 알아보았습니다.

 

개인적으로 이 Gorvernor Limits 때문에 Salesforce를 때려치고 싶다는 생각이 들 정도로 개발을 하다보면 상당히 불편한 부분입니다.

그렇기 때문에, 설계 단계에서 우선적으로 조사한 후, 거기에 맞춰서 설계를 해놓을 필요가 있습니다.

 

 

 

 

 

 

 

참고1) https://trailhead.salesforce.com/ja/content/learn/modules/asynchronous_apex/async_apex_batch - Apex Batch 일괄 처리

참고2) developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_limits.htm - Limits Class

참고3) https://mokochi.tistory.com/manage/newpost/?type=post&returnURL=%2Fmanage%2Fposts - Governor Limits

# 목차

  • Sobject 란?

  • Apex, Visualforce, Lightning Component

  • 개발자 콘솔(Developer Console)

  • Generate & Analyze logs

  • SOQL & SOSL Query

  • DML Statement & Database Method


# SObject 란?

 : Salesforce의 개체를 선언하는 생성자

 : SObject와 Field는 "API 이름" 으로 호출

 : SObject는 모든 개체를 상속 받을 수 있다. 즉, 다른 언어에서의 Object와 비슷하지만, 개체만을 위한 Object형 이라고 생각하면 쉽다.

 ex)

SObject sobj = new Book__c(Name = '원피스 1권');       ( O )
SObject sobj = '문자열';      ( X )
List<SObject> sobjList = { new Book__c(Name = '원피스 1권'), new Member__c(Name = '홍길동') };  ( O )

 

형변환(Type Casting)

서로 다른 개체끼리형변환이 불가능하지만, SObject는 각각의 개체로 형변환이 가능하다.

ex)

// 서로 다른 개체끼리의 형변환은 불가능
Member__c member = new Book__c(Name = '원피스 1권');   ( ERROR )
Member__c member = (Member__c)book;   ( ERROR )

// SObejct의 형변환은 가능
SObject sobj = new Member__c(Name = '홍길동');   ( O )
Member__c member = (Member__c)sobj;   ( O )
String memName = ((Member__c)sobj).Name;   ( O )

단, SObject에서는 점 표기법을 사용한 직접적인 필드 접근이 불가능하다.(SObject.Id만 접근 가능)

 => get(), set() 메서드를 통한 접근 또는 형변환 이후 접근 가능

ex)

SObject sobj = new Member__c(Name = '홍길동');

ID memId = sobj.Id;   ( O )
String memName = sobj.Name;   ( ERROR )
String memName = ((Member__c)sobj).Name;   ( O )

 

# Apex, Visualforce, Lightning Component

1. Apex 란?

 : 조직의 비즈니스 프로세스를 사용자 정의하는 데에 사용되는 Salesforce 자체 개발 언어

 : Java를 토대로 만들어 졌으며, 조직의 데이터와 상호 작용하는 언어

 : Lightning Platform API에 대한 호출, Lightning Platform 서버에서 Flow 및 Transaction 제어문을 실행할 수 있도록 유형화된 객체 지향 언어를 호출 할 수 있음

 

2. Visualforce 란?

 : 모바일 및 데스크톱 앱의 사용자 인터페이스를 구축하기 위한 웹 개발 프레임워크

 : 페이지 위주의 개발

 

3. Lightning Component

 : 모바일 및 데스크톱 앱을 개발하기 위한 프레임워크

 : 앱의 탭, 페이지, 레코드 페이지, 커뮤니티 페이지 등을 구성하는 옵션 결정 가능

 

※ Visualforce 와 Lightning Component의 차이

 1) Visualforce는 페이지 중심

 2) 레코드 저장 시, Visualforce 페이지는 서버와 상호작용을 하고, 사용자 인터페이스를 Reload

 3) Lightning Component는 서버보다 단말(Device)에서 더 많은 작업을 한다.

  참고) Lightning Experience Development - Lightning Experience 개발 가이드


 

# 개발자 콘솔(Developer Console)

 : 앱 개발, 디버그 및 테스트를 할 수 있도록 Salesforce 브라우저 상에서 제공되는 통합 개발 환경(IDE)

  현재 로그인 중인 하나의 조직에만 연결되는 브라우저 기반의 IDE이기 때문에,

  복수의 조직에 연결하거나 파일 비교, 동기화, 버전 제어 및 관리 등을 하기 위해서는 Force.com IDE(ex. Eclipse, Visual Studio Code)를 사용

  ※ Eclipse의 경우, 2019년 10월 부터 지원이 중지되었기 때문에 요즘에는 주로 Visual Studio Code(이하 VSCode)를 많이 사용합니다.

 

VSCode 개발 환경 구축 방법

2020/07/25 - [Salesforce.com/SFDC Memo] - [SFDC] Visual Studio Code에서 Force.com 개발 환경 구축하기

 

 

☞ 개발자 콘솔 여는 방법


 

# Generate & Analyze Logs

 : 대체적으로, Salesforce 개발 중에 발생하는 에러 등, 로그 확인 및 디버깅은 개발자 콘솔에서 이루어진다.

 

컴파일 에러가 발생했 을 경우, 에러 메시지를 포함한 dialog box 가 표시된다.

상세 내용은, [Logs]탭에 표시되는 로그를 더블 클릭 해서 상세 로그를 표시할 수 있다.

 

[Debug > View Log Panels]로 현재 표시중인 로그를 다양한 패널 뷰로 표시할 수 있다.

  • [Stack Tree] : 하향식 트리보기를 통해 객체의 계층 구조 및 실행 내의 로그 항목 표시

  • [Execution Stack] : 선택한 항목의 상향식 보기를 표시, 로그 항목을 표시

  • [Execution Log] : 코드를 실행하는 동안 발생한 모든 작업을 표시

  • [Source] : 소스 파일의 내용을 표시, 선택한 로그 항목이 생성될 때 실행되는 코드를 표시

  • [Source List] : 이벤트가 기록될 때 실행중인 코드의 컨텍스트를 표시

  • [Variables] : 선택한 로그 항목을 생성한 코드가 실행될 때 범위에 있었던 변수와 할당된 값을 표시

  • [Execution Overview] : 실행 시간 및 힙의 크기를 포함하여 실행 중인 코드의 통계를 표시

 

Apex 코드 내에서 CheckPoint(Break point)를 지정하여 특정 지점에서 일어나고 있는 상태를 볼 수 있다.

Checkpoint는 Apex Code 왼쪽의 코드 라인을 클릭해서 지정할 수 있다.

Checkpoint 지정

 

Checkpoint의 확인은 개발자 콘솔의 [Checkpoints] 탭에서 확인 가능합니다.

 

Apex 코드 내에서의 Debug log 표시하는 방법은 아래의 코드를 사용한다.

System.debug(msg);

 

또한, 개발자 코드의 로그에서 [Debug only]를 체크하면 Apex 코드 내에서 지정한 로그만을 표시하는 것이 가능하다.


 

# SOQL & SOSL Query

 

1. SOQL 이란?

 : Salesforce Object Query Language의 약자

 : SOQL을 사용하여 데이터베이스(개체)에 저장된 정보를 읽을 수 있음

 : SQL(Structured Query Language)와 유사

 

ex)

// 이름이 홍길동인 회원의 개체ID와 회원명을 검색
SELECT Id, Name FROM Member__c WHERE Name = '홍길동'

 

[개발자 콘솔 - Query Editor]에서 Query 실행 방법

 

Apex 코드 내에서의 코드 실행 방법

// Apex 코드내에서의 SOQL Qeury 실행
// 대체적으로 List<>형식으로 습득
List<Member__c> memberList = [SELECT Id, Name FROM Member__c WHERE Name = '홍길동'];

// 특정키를 통해 단일 개체로도 습득 가능
Member__c member1 = [SELECT Id, Name FROM Member__c WHERE Id = '[대상의 개체Id]'];

// WHERE구 등에서 변수를 사용하는 방법
// 변수의 앞에 "콜론(:)"을 붙여서 변수임을 알림
ID memberId = memberList.get(0).Id;
Member__c member2 = [SELECT Id, Name FROM Member__c WHERE Id = :memberId];

 

2. SOSL 이란?

 : Salesforce Object Search Language의 약자

 : 레코드에서 텍스트 검색을 수행하는 언어

 : SOQL과 달리, 여러 유형의 개체를 동시에 쿼리할 수 있음

 : SOQL은 정확한 구문을 필요로 하지만, SOSL은 단어 일치를 사용하여 필드를 검색할 수 있음.

ex) 회원명이 "꼬비" 인 회원 레코드와, "꼬비"회원이 대여중인 도서의 개체ID, 도서명 검색

FIND {꼬비} IN ALL FIELDS RETURNING Book__c(Id, Name), Member__c(Id, Name)

 

[개발자 콘솔 - Query Editor]에서 Query 실행 방법

 

Apex 코드 내에서의 코드 실행 방법

// SOSL 검색
List<List<SObject>> soqlResultList = [FIND '꼬비' IN ALL FIELDS RETURNING Book__c(Id, Name), Member__c(Id, Name)];

// SOSL 검색 결과로 부터 각 개체 정보 습득
List<Book__c> bookList = soqlResultList.get(0);
List<Member__c> memberList = soqlResultList.get(1);

// 각 검색 결과 확인
System.debug('도서명 : ' + bookList.get(0).Name);
System.debug('회원명 : ' + memberList.get(0).Name);

# DML Statement & Database Method

1. DML Statement

 : 단일 개체 또는 개체 리스트를 허용

 : Salesforce에는 INSERT, UPDATE, DELETE, UPSERT(특수DML) 이 존재

  ※ UPSERT : INSERT와 UPDATE가 합쳐진 DML로, 지정된 필드를 사용해 기존 개체의 존재 여부를 결정하거나 필드가 지정되지 않은 경우, ID를 통해 개체 레코드를 INSERT 또는 UPDATE한다.

    (기존 레코드가 없는 경우 => INSERT, 기존 레코드가 존재하는 경우 => UPDATE)

// 단일 레코드 INSERT
Member__c insMember = new Member__c(Name = '홍길동');
insert insMember;

// 다수 레코드 INSERT
List<Member__c> insMemberList = new List<Member__c>();
insMemberList.add(new Member__c(Name = '홍길동1'));
insMemberList.add(new Member__c(Name = '홍길동2'));
insMemberList.add(new Member__c(Name = '홍길동3'));
insert insMemberList;

// 단일 레코드 UPDATE
Member__c updMember = new Member__c(Id = insMember.Id, Name='고길동');
update updMember;

// 다수 레코드 UPDATE
List<Member__c> updMemberList = new List<Member__c>();
updMemberList.add(new Member__c(Id = insMemberList.get(0).Id, Name = '고길동1'));
updMemberList.add(new Member__c(Id = insMemberList.get(1).Id, Name = '고길동2'));
updMemberList.add(new Member__c(Id = insMemberList.get(2).Id, Name = '고길동3'));
update updMemberList;

// 단일 레코드 DELETE
delete insMember;

// 다수 레코드 DELETE
delete insMemberList;

// 단일 레코드 UPSERT
Member__c upsMemberIns = new Member__c(Name = '고길동');
Member__c upsMemberUpd = new Member__c(Id = insMember.Id, Name='고길동');
upsert upsMemberIns;   // UPSERT에 의한 INSERT
upsert upsMemberUpd;   // UPDATE에 의한 UPDATE

// 다수 레코드 UPSERT
List<Member__c> upsMemberList = new List<Member__c>();
upsMemberList.add(new Member__c(Name = '고길동'));
upsMemberList.add(new Member__c(Id = insMember.Id, Name = '고길동'));
upsert upsMemberList;  // 첫번째 요소는 INSERT, 두번째 요소는 UPDATE

 

2. Database Method

 : 정적 메소드(static)이므로 클래스명 및 메소드 명(Database.[메소드명])으로 호출(Salesforce의 표준 라이브러리)

 : DML작업을 부분적으로 성공시킬지에 대한 여부 설정 가능(allOrNone : Boolean)

  • [allOrNone = true]인 경우(Default), 전체 성공을 기준으로 함

  • [allOrNone = false]인 경우, 성공한 레코드는 데이터베이스에 commit, 실패한 레코드에러가 반환(예외 발생 X) 

 : Database Method는 DML처리 결과(성공 여부)를 배열로 반환해 준다.

// param1) INSERT할 개체 리스트(또는 단일 개체)
// param2) allOrNone
Database.SaveResult[] results = Database.insert([INSERT 개체리스트], false);

 

ex)

List<Member__c> insMemList = new List<Member__c>();
insMemList.add(new Member__c(Name = '홍길동'));
insMemList.add(new Member__c(Name = '고길동'));

// 회원 리스트 INSERT
Database.SaveResult[] srList = Database.insert(insMemList, false);

// DML처리 결과 확인
for (Database.SaveResult sr : srList) {

    // INSERT 성공여부 체크(isSuccess())
    if (sr.isSuccess()) { 
        // INSERT 성공

        System.debug('INSERT 성공 회원의 개체ID : ' + sr.getId());

    } else {
        // INSERT 실패

        for (Database.Error err : sr.getErrors()) {

            // ERROR메시지 표시
            System.debug(err.getStatusCode() + ' : ' + err.getMessage());
            System.debug('실패 레코드 정보 : ' + err.getFields());
        }
    }    
}

 

DML Statement 와 Database Method 사용

 : DML Statement는 예외처리(try-catch)를 통해 즉시 DmlException을 발생, 중단시키는 경우에 사용

 : Database Method는 DmlException을 무시하는 경우, 즉, DML 처리 결과에 상관없이, 일단 전부 실행한 뒤, 실패한 요소에 대해서만 따로 처리하는 경우

   (물론, Database Method에서도 Exception을 발생 시킬 수 있다.)

 

 

 

 

 

 

참고1) https://developer-cj.tistory.com/ - 어느 Salesforce Developer의 개발 성장기

참고2) https://trailhead.salesforce.com/ - Salesforce Trailhead

본 포스트에서는 Visual Studio Code(이하 VSCode)에서 Apex, VIsualforce를 개발할 수 있는 개발 환경을 구축하는 방법에 대해 알아보겠습니다.

 

이전에는 Eclipse에서 Force.com을 연동하여 사용했었지만, 2019년 10월 부터 지원이 중단되었기 때문에,

현재는 Eclipse에서 Force.com을 연동할 수가 없게 되었습니다.

(기존에 사용중인 Eclipse를 복제해서 사용하는 방법은 가능)

 

그 대신, VSCode에서 Salesforce의 개발이 가능하게 되었기 떄문에 대부분의 SFDC개발자들이 VSCode를 사용하는 추세입니다.

(지금 회사에서는 현재 진행중인 프로젝트까지 Eclipse로 하고있는건 비밀)


목차

  • Visual Studio Code 설치 및 환경 구축

  • Salesforce 계정 연동

  • 소스 코드 습득 및 갱신(Retrieve & Deploy)


# Visual Studio Code 설치 및 환경 구축

 

1. Salesforce CLI 설치

VSCode에서 Force.com을 사용하기 위해서는 "Salesforce CLI" 가 필요합니다.

Salesforce CLI는 이곳 자신의 OS에 맞는 버전을 다운로드 해주세요

 

그 후, Salesforce CLI를 설치해주세요.

다운로드 위치 이외에는 딱히 설정을 바꾸지 않고 다음만 눌러주셔도 무방합니다.

 

2. VSCode 설치

VSCode는 이곳 에서 자신의 OS에 맞는 버전을 다운로드 해주세요.

※ 참고로 저는 System Installer 로 설치했습니다만, 본인이 원하는 것으로 설치해주셔도 무방합니다.

 

☞ User Installer 와 System Installer의 차이점

이름 내용
User Installer 현재의 사용자(OS User)만 사용 가능하게 하고 싶은 경우
System Installer 모든 OS 사용자가 사용 가능하게 하고 싶은 경우 (주로 개인용 PC)

 

그 후, VSCode를 설치해주세요.

※ 설치 시, "PATH에 추가(다시 시작한 후 사용 가능)"에 체크해 주세요.

※ VSCode를 설치한 후, PC를 재부팅해주세요.

 

3. 확장기능 추가

VSCode 왼쪽에 있는 아이콘들 중 확장기능(Extensions)를 선택한 후, 아래의 확장 기능 들을 검색, 설치합니다.

"Salesforce Extension Pack"에 의해 대부분 자동으로 설치되지만 가끔 설치가 안되는 것들도 있기 때문에, 전부 확인할 필요가 있습니다.

 

☞추가할 확장 기능

  • Salesforce CLI Integration

  • Salesforce Extension Pack

  • Apex

  • Apex Interative Debugger

  • Apex Replay Debugger

  • Aura Components

  • ESLint

  • Lightning Web Components

  • Salesforce Toolkit

  • Visualforce

  • Prettier - Code formatter

  • Edit csv (VSCode를 CSV에디터로서 사용할 수 있게 해주는 확장기능)

  • XML

이 외에, 필요에 따라 확장 기능을 추가해주시면 되겠습니다.

 

4. 환경 설정

먼저, Java를 설정합니다.

[File - Preferences - Settings] 를 선택해서 설정 화면을 표시합니다.

 

설정 화면에 표시되는 검색란에 "apex" 를 입력한 후, 화면에 표시되는 메뉴에서

[Extenstions - Salesforce apex configuration]을 선택합니다.

 

그 후, "Salesforcedx-vscode-apex > Java : Home"에 사용 중인 Java의 경로를 설정해 줍니다.

※ JAVA가 없는 경우, 이곳 에서 다운로드 및 설치 해주시기 바랍니다.


# Salesforce 계정 연동

 

계정을 연동하기 위해서는 먼저 프로젝트를 생성해 줄 필요가 있습니다.

우선, 프로젝트를 생성하겠습니다.

 

다음의 순서로 프로젝트를 생성합니다.

 1) [View - Command Palette] 또는 [Ctrl + Shift + P] 키를 눌러 커맨드 팔레트를 표시

 2) 표시한 커맨드 팔레트에 ">SFDX:create project with manifest" 를 입력한 후 선택

 3) "Standard" 선택

 4) Standard 선택 후 표시되는 입력란에 "프로젝트 명"을 입력

 5) 프로젝트를 생성할 위치를 선택

 

프로젝트가 생성되었다면, 생성한 프로젝트에 Salesforce 계정을 연동해봅시다.

 

다음의 순서로 계정을 연동합니다.

 1) 커맨트 팔레트에 ">SFDX:Authorize an Org"를 입력한 후 선택

 2) 접속할 종류를 선택합니다. (일반적인 개발 환경인 경우 "Production")

 3) 연동할 계정의 목적에 맞는 조직명을 입력합니다. (대충 지어도 상관은 X)

 4) Salesforce 로그인 화면이 표시되면 연동할 계정의 ID와 패스워드를 입력한 후 로그인합니다.

 5) 접속이 완료되면 Salesforce CLI의 허가를 승낙한 후, 브라우저를 닫습니다.

 

계정이 연동되면 VSCode의 하단부에서 연동중인 개발 환경의 정보를 확인할 수 있습니다.


# 소스 코드 습득 및 갱신 (Retrieve & Deploy)

 

우선 연동 중인 환경에 저장되어 있는 패키지의 소스를 습득하는 방법에 대해 알아보겠습니다.

 

혹시, 현재 개발 중인 프로젝트가 있는 경우, 사용중인 package.xml을 복사해서 다음 디렉토리에 덮어씌울 필요가 있습니다.

package.xml위치 : [VSCode에서 생성한 프로젝트의 디렉토리 - manifest - package.xml]

 

VSCode의 프로젝트의 [manifest - package.xml]에서 오른쪽 버튼을 누른 후,

"SFDX: Retrieve Source in Manifest from org" 를 선택해 개발 환경에 있는 패키지를 습득합니다.

소스 코드의 습득(Retrieve)가 완료되면 [force-app] 에서 소스 코드를 확인할 수 있습니다.

 

반대로 VSCode에서 수정한 소스 코드를 개발 환경에 적용(Deploy)할 경우,

[manifest - package.xml]에서 우클릭, "SFDX: Deploy Source in Manifest to org" 를 선택합니다.

 

하지만, package.xmlRetrieve나 Deploy를 할 경우, package.xml에 지정되어 있는 모든 소스가 습득 및 저장 되기 때문에, 개발 중에는 수정한 코드에 대해서만 Retrieve나 Deploy를 해주는 것이 좋습니다.

 

수정한 코드에 대해서만 습득, 저장 하는 방법은 해당 파일에서 우클릭,

"SFDX: Retrieve Source from org(습득)" 또는 "SFDX: Deploy Source to org(저장)"를 선택하시면 됩니다.

 

※ package.xml에 대한 내용은 추후에 다시 다루도록 하겠습니다.

간단하게 설명하자면, Salesforce로 개발한 패키지(소스 코드, 개체, 등등의 모든 요소)를 관리하는데에 사용되는 xml파일 입니다.

이 package.xml 이 없으면, Eclipse나 VSCode로의 개발이 불가능합니다.


이상으로, Force.com을 Visual Studio Code에서 개발하기 위한 환경 구축 방법에 대해 알아보았습니다.

 

본 포스트에서 기술한 코드 습득 방법 외에, VSCode에서의 테스트 코드 실행 방법, SOQL 실행방법 등의 기능도 있습니다.

이 내용들에 대해서는 추후에 위 내용에 관련된 포스팅을 올릴 때에 설명하도록 하겠습니다.

 

또한 앞으로 올릴 포스팅에서는 브라우저의 개발자 콘솔이 아닌, VSCode로 코드를 작성하도록 하겠습니다.

(저도 사용법을 연습해야 하기 떄문에..)

 

 

'Salesforce.com > SFDC Memo' 카테고리의 다른 글

[SFDC] Salesforce Governor Limits  (0) 2020.08.12
[SFDC] 기본 개념 정리1  (0) 2020.08.10

+ Recent posts