비동기 프로세스는 사용자가 작업이 완료될 때까지 기다릴 필요없이 백그라운드에서 작업을 실행하는 프로세스를 의미한다. 그럼 반대로 동기는? 순차적으로 작업을 처리하는 것을 의미하겠지.ㅇㅇ
비동기식 처리의 장점
효율성
동기적으로 처리하면 앞 작업이 굉~장히 오래걸린다고 가정했을 때, 바로 뒤에 있는 작업은 엄청 빨리 끝날 수 있는데도 앞 작업이 끝날 때까지 기다려야 한다. 비동기적으로 처리한다면 앞 작업이 오래걸리던 말던 다른 작업도 동시에 할 수 있으니 효율적임!
확장성
순차적으로 일을 처리 하지 않아도 되니까 특정 시점에 처리할 작업을 예약해놓을 수 있으니 리소스를 확장하기가 매우 편리함.
Higher limit
세일즈포스에서 정말 중요한.. limit의 한도가 높아진다는 것이 아주 큰 이점이다. 비동기 호출을 사용하면 SOQL 쿼리 수도 100개에서 200개로 두 배나 늘어난다. 비슷하게 힙 크기와 CPU 시간도 늘어남..!!!!
세일즈포스에서도 Asyncronous Apex를 제공하고 있음. 하나씩 알아보자~
Future method는 @future 어노테이션이 붙어 있는 메소드를 식별하여 비동기적으로 처리한다.@future 는 나중에 시스템 리소스를 사용할 수 있게 되면 별도의 스레드에서 프로세스를 실행하고 싶을 경우에 사용한다.
동기적 처리는 모든 메소드가 Apex 코드를 실행하는 동일한 스레드에서 이뤄지기 때문에 추가적인 메소드 호출이 불가능하다. 하지만~ @future 를 사용하면 비동기적으로 실행하고 싶은 메소드를 별도의 스레드에서 동시 실행시킬 수 있다.
가장 일반적으로 비동기식으로 처리하려는 레코드 ID를 List parameter로 받아 처리하는 것이다.
public class SomeClass {
@future
public static void someFutureMethod(List<Id> recordIds) {
List<Account> accounts = [Select Id, Name from Account Where Id IN :recordIds];
// process account records to do awesome stuff
}
}
Apex callout을 사용하면 외부 웹 서비스를 호출하거나 Apex 코드에서 HTTP 요청을 보낸 후 응답을 수신하여 Apex를 외부 서비스와 통합할 수 있다. callout을 비동기적으로 만들기 위해선 @future(callout=true)로 작성해주면 된다!!
💥💥주의할 점💥💥
나중에 실행될 메소드를 어떻게 테스트 하나 싶었는데 startTest()와 stopTest() 사이에 비동기 메소드를 넣으면 된다. 시스템은 startTest() 이후에 이루어진 모든 비동기 호출을 수집한다. stopTest()가 실행 되면 수집된 모든 비동기 프로세스가 동기적으로 실행된다. 그러면 비동기 호출이 제대로 작동했음을 확인할 수 있다.
그럼 외부에 callout을 보내는 future 메소드를 test 해야한다면? 테스트 코드는 실제로 외부 시스템에 callout을 보낼 수 없으므로 우리가 지어내야한다. (그니까 respond를 우리가 짜야함!!)
Batch Apex는 수천 또는 수백만개의 레코드를 처리해야하는 대규모 작업을 실행할 때 사용된다. 내부적으로 작동하는 방식은 다음과 같다. Batch Apex를 사용하여 100만개의 레코드를 처리한다고 가정해보면, 처리중인 각 레코드 배치 당 한 번씩 호출된다. 배치 클래스를 호출할 때마다 Apex 작업 큐에 배체되고 개별 트랜잭션으로 실행된다.
Batch Apex는 메소드를 생성하는게 아니라 클래스를 생성해야 한다. Database.Batchable을 implements 하여 인터페이스를 구현한다.
public class MyBatchClass implements Database.Batchable<sObject> {
public (Database.QueryLocator | Iterable<sObject>) start(Database.BatchableContext bc) {
// 실행되어야 할 레코드나 객체를 모으는 메소드
}
public void execute(Database.BatchableContext bc, List<P> records){
// 처리 과정
}
public void finish(Database.BatchableContext bc){
// 후 처리 과정 (ex. 이메일 보내기)
}
}
start 메소드는 batch Apex 작업이 시작할 때 호출된다. 이 메소드로 레코드나 객체를 모아서 execute 메소드로 전달할 수 있다. 이 메소드는 Database.QueryLocator 객체나 레코드나 객체를 포함한 Iterable을 전달할 수 있다.
보통 SOQL 쿼리문이 쓰이는QueryLocator를 사용하지만, API를 반복하거나 레코드가 메소드에 전달되기 전에 사전 처리를 해야하는 경우 Custom Iterator를 사용하는 것이 좋다.
QueryLocator를 사용하면 SOQL 쿼리로 최대 5천만개의 레코드를 쿼리할 수 있다. 그러나 Iterable은 SOQL governor limit이 그대로 적용된다.
execute 메소드에 전달된 배치에 대한 실제 처리를 수행하는 메소드이다. 기본 배치 크기는 200개이고, start 메소드에서 수신된 순서대로 실행된다는 보장이 없다.
배치 작업이 끝나고 후처리 작업을 실행할 때 사용되는 메소드. 마지막 한번만 호출된다.
배치 클래스를 호출하려면 인스턴스화 하고 Database.executeBatch로 호출하면 된다.
MyBatchClass myBatchObject = new MyBatchClass();
Id batchId = Database.executeBatch(myBatchObject);
parameter를 전달하여 레코드 수를 지정할 수도 있다.
Id batchId = Database.executeBatch(myBatchObject, 100);
각 배치 클래스 호출은 작업 진행상황을 추적할 수 있도록 AsyncApexJob 레코드를 생성한다. SOQL을 통해 진행상황을 보거나 Apex Job Queue에서도 확인할 수 있다.
AsyncApexJob job = [SELECT Id, Status, JobItemsProcessed, TotalJobItems, NumberOfErrors FROM AsyncApexJob WHERE ID = :batchId ];
일괄적으로 Apex작업의 각 실행은 개별 트랜잭션으로 간주된다. 예를 들어, 1000개의 레코드를 포함하는 Batch Apex 작업은 200개의 레코드로 구성된 5개의 트랜잭션으로 간주된다.
Batch 클래스를 정의할 때 Database.Stateful도 같이 정의하면, 모든 트랜잭션의 상태를 유지할 수 있다. 상태 유지는 처리되는 레코드를 계산하거나 요약하는데 유용하다.
어떤 회사에서 모든 contact에는 account의 billing address와 같은 Mailing address가 있어야 한다고 가정해보자. 근데 사용자가 정확한 mailling address를 입력하지 않고 contact를 저장하고 있다면? 이 요구사항이 적용되도록 보장하는 Batch Apex 클래스를 작성해보자.
public class UpdateContactAddresses implements
Database.Batchable<sObject>, Database.Stateful {
// 각 트랜잭션들 사이에서 상태를 유지할 멤버 변수 선언 - 여기선 레코드 개수
public Integer recordsProcessed = 0;
//batch로 처리할 쿼리문을 호출하는 Database.getQueryLocator 반환
public Database.QueryLocator start(Database.BatchableContext bc) {
return Database.getQueryLocator(
'SELECT ID, BillingStreet, BillingCity, BillingState, ' +
'BillingPostalCode, (SELECT ID, MailingStreet, MailingCity, ' +
'MailingState, MailingPostalCode FROM Contacts) FROM Account ' +
'Where BillingCountry = \'USA\''
);
}
//각 배치당 200개의 레코드가 두번째 parameter에 전달됨. 비즈니스 요구사항에 맞게 처리
public void execute(Database.BatchableContext bc, List<Account> scope){
// 각 배치의 레코드들이 처리됨.
List<Contact> contacts = new List<Contact>();
for (Account account : scope) {
for (Contact contact : account.contacts) {
contact.MailingStreet = account.BillingStreet;
contact.MailingCity = account.BillingCity;
contact.MailingState = account.BillingState;
contact.MailingPostalCode = account.BillingPostalCode;
// 업데이트 할 contact들 컬렉션 변수에 저장
contacts.add(contact);
// 상태 업데이트
recordsProcessed = recordsProcessed + 1;
}
}
update contacts;
}
// 작업이 완료되면 시작되는 메소드
public void finish(Database.BatchableContext bc){
System.debug(recordsProcessed + ' records processed. Shazam!');
// 배치 작업에 대한 정보가 담겨있는 객체 쿼리
AsyncApexJob job = [SELECT Id, Status, NumberOfErrors,
JobItemsProcessed,
TotalJobItems, CreatedBy.Email
FROM AsyncApexJob
WHERE Id = :bc.getJobId()];
// 이메일 보내기
EmailUtils.sendMessage(job, recordsProcessed);
}
}
배치 클래스는 @testSetup으로 레코드를 삽입하고 Batch Apex 클래스를 호출한 다음 레코드가 제대로 업데이트 되었는지 확인하면 된다. 중요한 점은, 테스트 메소드는 하나의 배치만 실행할 수 있으므로 레코드 수는 200개로 제한되어야 한다. 또한 Iterable에서 반환한 값이 start() 배치 크기와 일치하는지도 확인해야 한다.
더 자세한 사항은 요기 참고..