Flexqueue: Liberación de Batch Apex

Hace un tiempo que tuve la oportunidad de trabajar con una nueva funcionalidad aun en “piloto” que posiblemente el año que viene vea la luz ( siempre Safe Harbor 🙂 ) . Dicha funcionalidad es Flexiqueue.

¿Qué es Flexiqueue?

Flexiqueue es el nombre corto para Flexible Queue, cola flexible. Flexiqueue nos va a permitir lanza ¡más de 5 procesos Batch a la vez! Como lo lees, hasta ahora solamente podíamos tener 5 procesos Batch por Organización/ Entorno, sin embargo cuando esta nueva funcionalidad vea la luz podremos lanzar hasta un total de 100 procesos a la vez en la misma organización/entorno.

Pero importante, quizás estamos pensando en estos momentos que dichos ,100 procesos, van a entrar en proceso de ejecución, es aquí donde tengo que matizar, ya que solamente 5 procesos como hasta ahora van a ser ejecutados paralelamente y los restantes 95 se quedarán en estado de espera.

 ¿Qué podemos hacer mientras dichos 95 procesos están en estado de espera?

Como sabemos por ahora los procesos batch se ejecutan en el orden en que son lanzados ( no como @future), sabemos que el primer proceso batch que se ha llamado es el primero en entrar en ejecución ( al menos eso nos han dicho 😉 )

Pues es aquí, en esta espera que cuando … recibimos una llamada y necesitamos que uno de los proceso batch que están en espera sea el siguiente en entrar en ejecución, ¡no podemos esperar! Lo necesitamos ¡YA! , ¡Quiero lanzar mi proceso Batch! ( Exactamente igual que Jonah Jameson cuando dice “I want Spiderman!!” , pues eso nosotros queremos nuestro proceso Batch ya ya ya! )

Con Flexiqueue, podemos tenerlo, podemos re-ordenar los procesos Batch que están en espera para poner al principio de la lista el que deseamos que sea lanzado primero.

Todo ello lo haremos en una nueva UI la cual nos permitirá re-ordenar procesos Batch.

Esta nueva UI que se encuentra bajo el menu: Apex Flex Queue

Screenshot 2014-11-23 15.12.28

¿Entonces, vamos a tener dos UIs y dos tablas para representar los procesos Batch?

Sí, tendremos dos UIs , una para poder re-ordenar los procesos que se encuentran a la espera

Screenshot 2014-11-23 15.13.08

 

Y otra para poder cancelar aquellos procesos que se están ejecutando.

Screenshot 2014-11-23 15.13.29

Y no, solamente tendremos una tabla, exactamente como antes para preguntar y controlar los procesos batch: AsyncApexJobs 

¡Ojo! Como seguramente habéis notado , he dicho que puedo abortar procesos que están ejecutándose en estos momentos… entonces ¿quiere decir eso que no voy a poder parar/ abortar los procesos que están en la cola a la espera de ser ejecutados hasta que no entren en ejecución?

Como digo esto es un piloto, no se me permite abortar procesos a través de la UI pero lo que si puedo es parar/abortar cualquier proceso usando : System.abort(BatchId).

Seguramente para cuando se haga reléase de esta funcionalidad también se pueda abortar a través de UI.

¿Qué nos quedaría por pedir?

¡Queremos poder re-ordenar a través de código!! Queremos tener un método una API, algo que nos permita en nuestros desarrollos poder re-ordenar, tener el control sobre los procesos batch sin necesidad de hacer esta reordenación manualmente. Pues bien, en el piloto que trabajé no me fue posible, pero en conversaciones con Salesforce , dicha llamada/método/API esta en el “roadmap” con lo cual ¡también la tendremos!

 

… pero ¡Atención! ¡Cuidado! Con un gran poder vienen grandes responsabilidades…

 caution

Debemos tener en cuenta cuando se debe hacer dicha reordenación porque podemos caer en el error que re-ordenar y re-ordenar y re-ordenar … (hasta el infinito y mas allá ) y que alguno de los procesos batch que están en la cola nunca se ejecuten. Así que cuidado!

Además podría ocurrir que alguno de los procesos batch sea dependiente de otros en cadena y si re-ordenamos dejaríamos sin terminar un proceso que alguno de nuestros clientes necesite…

Una seria de escenarios que debemos tener en cuenta a la hora de hacer dicha reordenación.

Para terminar os dejo el link de la sesión que hice en Dreamforce donde también hay una pequeña demo de cómo Flexiqueue funciona.

¡Muchas gracias!!¡Y hasta pronto!!

 

QUEUEABLE – Regreso al @future

Anteriormente he hablado de Batch , y seguramente volveré con ello , pero hoy quiero hablar de otro proceso asíncrono al que no le tenia tanto cariño.

 ¿Conocéis @future?

Pues bien este es otro proceso asíncrono que podemos usar dentro de la plataforma Salesforce.

La gran cualidad de los procesos asíncronos es que tienen limites especiales los cuales son mayores a los procesos o transacciones síncronos.

Échale un vistazo a los Governos Limits en esta página: Governor limits

Como ves el número de registros, tanto como el tiempo , número de consultas SOQL es mayor que en una transacción síncrona.

Luego usar procesos asíncronos como @future o Batch Jobs es muy beneficiosos para algunos casos siempre y cuando no necesites el resultado .. ¡Ya!

Hoy me voy a centrar en @future y su “Futuro” la interfaz Queueable.

Pues bien, métodos @future se ejecutan cuando Salesforce tiene recursos disponibles.

Las limitaciones que existen en estos métodos son las siguientes:

  • Deben ser métodos estáticos : static
  • Solamente son de tipo void , no podemos devolver ningún otro tipo
  • No tenemos forma de saber en que estado se encuentran
  • No podemos cancelarlos
  • No son ejecutados en el orden que son llamados
  • No se pueden encadenar
  • Los parámetros que podemos usar son solamente “primitivos” , no podemos usar sObjets por ejemplo.

Esta es la nomenclatura usada :

 


public with sharing class FutureClass {

   @future
   static void myMethod(String a, Integer i) {

        System.debug('Method called with: ' + a + ' and ' + i+'... and I loved the sunny days :)');

       // Aquí va nuestro código, toda la lógica

    }

}

Es en este punto… donde aparece la ¡Interfaz Queueable!!

Que desde Winter 15 nos va a permitir:

  • Poder saber el estado de nuestro proceso asíncrono
  • Poder cancelarlo
  • Poder encadenar procesos
  • Poder usar cualquier tipo de parámetros

Y así es como se puede implementar:


public class AsyncExecutionExample implements Queueable {

   public void execute(QueueableContext context) {

      //escribo mi lógica

   }

}

Como veis el ejemplo nos muestra el método principal que debemos sobreescribir: execute

Dentro de dicho método es donde pondremos toda nuestra lógica, donde procesaremos todos los registros que necesitamos.

¿Cómo deberíamos entonces lanzar or llamar este proceso?


ID jobID = System.enqueueJob(new AsyncExecutionExample());

Entonces es aquí una vez el proceso se ha llamado podemos ver el estado en el que se encuentra haciendo una consulta a la tabla de procesos asíncronos :


AsyncApexJob jobInfo = [SELECT Status,NumberOfErrors FROM AsyncApexJob WHERE Id=:jobID];

Para encadenar procesos asíncronos de tipo Queueable solamente hemos de hacerlo dentro de nuestro execute:

 


public class AsyncExecutionExample implements Queueable {

   public void execute(QueueableContext context) {

      // Your processing logic here

      // Chain this job to next job by submitting the next job

       System.enqueueJob(new SecondJob());

       // o quizás hacer una llamada recursiva a nosotros mismos

   }

}

 

No olvidemos que debemos hacer Unit test a nuestro código con lo que como pasaba con @future , la llamada la haciemos entre las etiquetas Test.startTest()/ Test.stopTest() , en Queueable aún debemos seguir haciéndolo.

Quedaría de la siguiente manera:


@isTest

public class AsyncExecutionExampleTest {

    static testmethod void test1() {

          // startTest/stopTest block to force async processes

          //   to run in the test.

          Test.startTest();

          ID jobID = System.enqueueJob(new AsyncExecutionExample());

          Test.stopTest();

          // Validate that the job has run

          AsyncApexJob jobInfo = [SELECT Status,NumberOfErrors FROM AsyncApexJob WHERE Id=:jobID];

          System.assertEquals('Completed', jobInfo.Status);

          System.assertEquals(0, jobInfo.NumberOfErrors);

      }

}

Y para finalizar un pequeño ejemplo para hacer llamadas externas usando Queueable

public class MyQueueable implements Queueable,Database.AllowsCallouts  {
    public void execute(QueueableContext context) {
        
         HttpRequest req = new HttpRequest();
         req.setMethod('GET');
         req.setHeader('Content-Type', 'application/json');
         req.setEndPoint('https://maps.googleapis.com/maps/api/geocode/json?address=1600+Amphitheatre+Parkway,+Mountain+View,+CA&key=API_KEY');
         req.setHeader('Authorization', 'Bearer ' + UserInfo.getSessionId());
         HttpResponse response = new Http().send(req);
         ID jobID = System.enqueueJob(new AsyncExecutionExample());
   }
}

Hemos visto lo que ganamos cuando usamos Queueable, pero cuales son sus limitaciones:

  • Podemos encadenar solamente 2 procesos, no podemos encadenar hasta el “Infinito y más allá”

Entonces la siguiente pregunta sería cuando usar Queueable en lugar de Batch, pues bien, podríamos decir que usaremos Queueable cuando el volumen de registros a procesar sea mayor del permitido en un proceso síncrono pero sea proporcionalmente menor que el tamaño máximo de un proceso Batch, digamos para procesos asíncronos que solamente sean necesarios ejecutarse una vez o como máximo 2 , si no entonces necesitaremos implementar un proceso Batch. 🙂

 

 

 

 

 

Batch Jobs: Concurrencia y Experiencia de usuario (part 1)

Trabajando con mi Business Analyst  ( cuando digo mi, no es que me haya comprado un Analista de Negocio para mí sola, es que  trabajaba conmigo en el mismo equipo )  me encontré con una serie de casos en los cuales necesitábamos procesar grandes cantidades de datos… ya sabéis que el mundo contable es una fiesta!! Tuvimos una serie de problemillas intentando no tener datos procesados a la vez por diferentes usuarios, por el mismo usuario incluso, no tener resultados dobles… y obviamente queríamos mantener al usuario informado del estado en que el proceso Batch que estaba procesando sus datos se encontraba. Me imagino lo que estáis pensando… bueno quizás lo que pensaba yo… Sabemos que a veces es un poco “doloroso” trabajar con procesos batch, por una serie de razones: tiempo de procesamiento,  solapamiento de procesos que pueden usar los mismos registros,  mantener al usuario informado cuando los resultados deben de mostrarse en UI,  como mantener una traza de los registros procesados y creados en el Batch Job…. En este post voy a intentar dar una solución al solapamiento de procesos que usen mismos registros y tienen como resultado otros diferentes, y además una forma de mantener al usuario informado de los registros a procesar y ya procesados. Como os habréis dado cuenta esta es la primera parte, en la segunda parte tratare de dar una solución para trazar los registros procesados y recuperación en caso de que ocurran problemas en dicho procesamiento. Una serie aburrida sobre procesos batch!! 😛 Para ello tengo dos objetos:

  •  Batch Control :  Usaré este objeto para mantener a mi usuario informado del estado del proceso batch, si esta en cola si se esta procesando y  lo mas importante de los registros procesados y los registros a procesar
  • Batch Record Tracking : Este objeto lo usare para guardar la traza de aquellos registros que están siendo procesados por el proceso batch para no permitir que otros usuarios los usen, o el mismo usuario.  El caso que describiré aquí es el caso simple de bloqueo de concurrencia de registros entre procesos batch, pero tú quizás podrías extenderlo a : Bloqueo de registros por un criterio determinado , no solamente a  usuarios, solamente tendrías que modificar el objeto y quizás añadir un trigger.
  • Record Processed in Batch: Este objeto me dirá exactamente que objeto esta siendo procesado , tiene una relación master-detail a Batch Record Tracking , lo cual me permitirá el borrado de forma mucho mas sencilla.

Una cosita a tener en cuenta, es que los registros de estos objetos serán borrados una vez que el proceso batch termine para liberar espacio. Cómo son estos objetos? Pues muy bonitos, con sus campos , sus etiquetas …  aquí os dejo una fotito: Screenshot 2014-03-31 23.11.54 Aparece un objeto más, éste es el que se va a procesar en el batch job. Luego vamos a ver que tienen estos objetos:

  • Batch Control:

                     Batch Class: campo de texto que contendrá el nombre de la clase batch que contiene el código para el proceso batch. Batch Id: Campo de texto, de 18 caracteres , único y que diferencia entre mayúsculas y minúsculas. Representara el Id correspondiente al proceso batch el cual esta en ejecución en este momento. Error: es un campo de texto para guardar los errores que puedan ocurrir. Sin embargo se podría extender a otro objeto y creando una relación master-detail con Batch Control. Records Already Processed : este campo será usado para mostrar al usuario cuantos registros han sido procesados ya.

  • Batch Record Tracking:

Batch Class: campo de texto que contendrá el nombre de la clase batch que contiene el código para el proceso batch. Batch Id: Campo de texto, de 18 caracteres , único y que diferencia entre mayúsculas y minúsculas. Representara el Id correspondiente al proceso batch el cual esta en ejecución en este momento. Records Processed: Roll- up que ira contando los registros que están y han sido procesados. A esta altura seguramente te estarás preguntando, ¡¿Estos dos objetos son muy parecidos?!… vale es más bien una afirmación. Tienes toda la razón, son muy parecidos casi iguales, sin embargo he creado diferentes objetos ya que para el borrado es mas fácil.  ¿Por qué? Ya que Salesforce recomienda no tener relaciones master-detail con mas de 50K registros, puede que se necesite crear mas de un registro del mismo tipo que contendrá a los hijos, si es así recuerda Quitar el “único” del campo Batch Id.

  • Record Processed In Batch:

Batch RecordTracking: Master – Detail a Batch Record Tracking MyTestObject / Project: es un Lookup al objeto que se procesa en el batch, para poder navegar directamente a él. Processed Record Id: campo de texto el que representara  al id del objeto procesado ( no es una buena opción usar campos de texto para representar Ids , pero sin embargo si quieres hacer el seguimiento de mas de 25 tipos de objetos diferentes deberás usarlo) Y ahora la clase!


public with sharing class MyBatchClass implements Database.Batchable, Database.Stateful
{

    private BatchControl__c m_batchControl ;
    private BatchRecordTracking__c m_batchRecordsTracking;
    private MyNewTestObject__c m_myNewTestobject;
    private Id m_batchId;

    public Integer BATCHSIZE = 200 ; // you can always make it dynamic using a custom setting? 

    public MyBatchClass()
    {

    }

    public Id run()
    {

      // If you like to do some validations
      // or any previous prep for batch
      /// remember try - catch 😉
      return database.executeBatch(new MyBatchClass(), BATCHSIZE);

      }

      public Database.QueryLocator start(Database.BatchableContext batchableContext)
      {
         //initialize batch id to use it later
         m_batchId = batchableContext.getJobId();

         //PLEASE NOTE THAT THE QUERY IS SO GENERIC ONLY FOR PROTOTYPING -- YOU'LL HAVE A MORE RESTRICTING ONE
         String query = 'SELECT Id, Name FROM MyTestObject__c' ;
         return Database.getQueryLocator(query );
      }

      public void execute(Database.BatchableContext batchableContext, List<MyTestObject__c> scope)
      {

         SObjectUnitOfWork uow = new SObjectUnitOfWork( new List{ BatchControl__c.SObjectType, BatchRecordTracking__c.SObjectType, RecordCreatedfromBatch__c.SObjectType, RecordProcessedInBatch__c.SObjectType, MyNewTestObject__c.SObjectType, MyNewTestObjectDetail__c.SObjectType } );

         try
         {
            //record for Batch Control
            //query to AsyncApexJob to get the number of batches and get an aproximation of records to process
            List aaj = [Select Id, TotalJobItems from AsyncApexJob where Id=:m_batchId];
            //Obviously if we are here the batchId exists and the list will have an item --- BUT PLEASE CHECKIT !!
            //if(aaj!=null && !aaj.isEmpty())

            if(m_batchControl== null && m_batchRecordsTracking == null)
            {
                //now we can calculate the aprox
                Integer aproxRecordsToProcess = Integer.valueOf(aaj[0].TotalJobItems)* BATCHSIZE;
                m_batchControl = new BatchControl__c( BatchClass__c= 'MyBatchClass',BatchId__c=batchableContext.getJobId() , BatchStatus__c='Processing', RecordsAlreadyProcess__c=0,TotalRecordstoProcess__c=aproxRecordsToProcess);
                uow.registerNew(m_batchControl);

                // in part 2 of this post the object BatchRecordTracking will be linked to BatchControl
                m_batchRecordsTracking = new BatchRecordTracking__c(BatchClass__c= 'MyBatchClass',BatchId__c=m_batchId);
                uow.registerNew(m_batchRecordsTracking);
                uow.commitWork();
            }

            // Now I CREATE LINES FOR THE RECORDS THAT WILL BE PROCESS THIS WAY THEY WILL BE LOCK FOR OTHER BATCHS
            // OR FOR OTHER USERS --- YOU CAN MAKE THIS DEPENDENT ON A CRITERIA
            List<RecordProcessedInBatch__c> recordsWillBeProcessed = new List<RecordProcessedInBatch__c>();
            for(MyTestObject__c mto : scope)
            {
                RecordProcessedInBatch__c rpb = new RecordProcessedInBatch__c(BatchRecordTracking__c=m_batchRecordsTracking.Id,ProcessedRecordId__c =mto.Id, MyTestObject__c= mto.Id); // if you want more than 25 objects you will need to use text field to save the id
                recordsWillBeProcessed.add(rpb);
            }
            //if(!recordsWillBeProcessed.isEmpty())
            List saveResults = Database.insert(recordsWillBeProcessed,false);//--> this will insert only the ones that don't cause failure
            // because ProcessedRecordId__c is unique
            //if you would like all or none -->Database.insert(recordsWillBeProcessed,true);

             /*****************************************************
             Here is where the code for the batch goes.
             The records to be processed will be that ones that were inserted using Database.insert
             The records that will be excluded will be the ones that were not inserted

             How to check which ones where inserted properly?
             You can go throw the list records and check if they have id or no, if they don't have id it means they were not inserted.

             Note: Assuming that the records that were not inserted failed because the id was duplicated, if no the error should be check just in case 🙂
             *****************************************************/

             SObjectUnitOfWork uow2 = new SObjectUnitOfWork( new List{ BatchControl__c.SObjectType, MyNewTestObject__c.SObjectType } );

             if(m_myNewTestobject==null)
             {
                 //it is the master object that will contain the result objects
                 m_myNewTestobject = new MyNewTestObject__c(Status__c='Generating'); // --> this is my transaction Master object
                 uow2.registerNew(m_myNewTestobject);
             }

             /*********
             Once all the process is done, the main record is updated --- BATCH CONTROL
             **********/
            // we update the record with the number of records that have been processed here
            m_batchControl.RecordsAlreadyProcess__c = Integer.valueOf(m_batchControl.RecordsAlreadyProcess__c)+scope.size();
            uow2.registerDirty(m_batchControl); 

            uow2.commitWork();

       }
       catch(Exception e)
       {
           /****************************
           Any of the code the needs to be added to inform about the exception
           *****************************/

          //if there is any errors the New Object that is the result of the batch is set in error status
          if(m_batchControl==null)
          {
              m_batchControl = new BatchControl__c( Error__c = 'There is an error: '+ e, BatchClass__c= 'MyBatchClass',BatchId__c=batchableContext.getJobId() , BatchStatus__c='Failed', RecordsAlreadyProcess__c=0,TotalRecordstoProcess__c=0);
              uow.registerNew(m_batchControl);

              uow.commitWork();
          }
          else
          {
              // the batch control table is updated
              m_batchControl.BatchStatus__c= 'Failed';
              m_batchControl.Error__c= 'There is an error: '+ e;
              uow.registerDirty(m_batchControl);
           }
           if(m_myNewTestobject== null)
           {
              m_myNewTestobject = new MyNewTestObject__c(Status__c='Error', Error__c= 'There is an error in the "execute": '+ e); // --> this is my transaction Master object
              uow.registerNew(m_myNewTestobject);
           }
           else
           {
              m_myNewTestobject.Error__c= 'There is an error in the "execute": '+ e;
              m_myNewTestobject.Status__c='Error';
              uow.registerDirty(m_myNewTestobject);
           }

           /****************
           the batch record tracking is deleted in order to get the space back
           WHY IS IT DELETED??
           IT IS ONLY AN OPTION, YOU CAN ALWAYS KEEP IT AND RE-USE IN ORDER TO DONT PROCESS AGAIN THOSE RECORDS THAT WERE CORRECT...
           HOWEVER THIS CODE WILL DELETE THEM AND ALLOW THE USER TO REVIEW THE ERROR FIELD AND DELETE THE RESULT OR KEEP IT 

           --- >> NOTE THAT THE DELETE WILL DELETE 10K RECORDS IF YOU NEED MORE YOU WILL NEED TO CREATE THE WAY TO DO IT
           AT THIS MOMENT THE PROTOTYPE USE MASTER -DETAIL THEREFORE WILL BE EASIER TO DELETE BUT
           REMEMBER THAT SALESFORCE DON'T RECOMEND TO HAVE MORE THAN 50K CHILDS , THEN SHOULD BE GOOD TO CREATE MORE MASTERS IF YOU NEED IT
           *****************/
           uow.registerDeleted(m_batchRecordsTracking);

           uow.commitWork();

       }
       finally
       {
          // you can add here all that you would like to set or check
          // THIS CODE WILL BE ALWAYS EXECUTED!! EVEN WHEN THE JOB IS ABORTED
       }

   }

   public void finish(Database.BatchableContext batchableContext)
   { 

       SObjectUnitOfWork uow = new SObjectUnitOfWork( new List{ BatchControl__c.SObjectType, BatchRecordTracking__c.SObjectType ,MyNewTestObject__c.SObjectType } );
       try
       {
          /********************************
          Any code will be here
          *********************************/

       if(m_batchControl!=null)
       {

          //IF WE ARE HERE THIS IS THE LAST TO DO, THEN LET'S UPDATE THE RECORDS TO THE CORRECT STATUS
          m_myNewTestobject.Status__c= 'Completed';
          uow.registerDirty(m_myNewTestobject);
          m_batchControl.BatchStatus__c= 'Completed';
          uow.registerDirty(m_batchControl);

          // I dont need any more the tracking records , then let's get the space again
          uow.registerDeleted(m_batchRecordsTracking);

          /**********IMPORTANT***********
          ALL RECORDS SHOULD BE DELETED BECAUSE THE BATCH IS FINISH SUCCESSFULLY, THEREFORE THE USER WON'T NEED THEM ANY MORE.
          AT THE MOMENT THEY ARE NOT DELETED ONLY FOR REPORTING PUPOSES
          ******************************/
        }

     }
     catch(Exception e)
     {
     if(m_batchControl!=null)
     {

       //if there is any errors the New Object that is the result of the batch is set in error status
       m_myNewTestobject.Error__c= 'There is an error in the "finish" : '+ e;
       m_myNewTestobject.Status__c='Error';
       uow.registerDirty(m_myNewTestobject);
       // the batch control table is updated
       m_batchControl.BatchStatus__c= 'Failed';
       m_batchControl.Error__c= 'There is an error in the "finish": '+ e;
       uow.registerDirty(m_batchControl);

      /*******************************
      the batch record tracking is deleted in order to get the space back
      WHY IS IT DELETED??
      IT IS ONLY AN OPTION, YOU CAN ALWAYS KEEP IT AND RE-USE IN ORDER TO DONT PROCESS AGAIN THOSE RECORDS THAT WERE CORRECT...
      HOWEVER THIS CODE WILL DELETE THEM AND ALLOW THE USER TO REVIEW THE ERROR FIELD AND DELETE THE RESULT OR KEEP IT
      --- >> NOTE THAT THE DELETE WILL DELETE 10K RECORDS IF YOU NEED MORE YOU WILL NEED TO CREATE THE WAY TO DO IT
      AT THIS MOMENT THE PROTOTYPE USE MASTER -DETAIL THEREFORE WILL BE EASIER TO DELETE BUT
      REMEMBER THAT SALESFORCE DON'T RECOMEND TO HAVE MORE THAN 50K CHILDS , THEN SHOULD BE GOOD TO CREATE MORE MASTERS IF YOU NEED IT
      *******************************/

      uow.registerDeleted(m_batchRecordsTracking);

      uow.commitWork();
    }
   }
   finally
   {

     // you can add here all that you would like to set or check
     // THIS CODE WILL BE ALWAYS EXECUTED!! EVEN WHEN THE JOB IS ABORTED
   }
  }

}
Cómo funciona?
Integer aproxRecordsToProcess = Integer.valueOf(aaj[0].TotalJobItems)* BATCHSIZE;

m_batchControl = new BatchControl__c( BatchClass__c= 'MyBatchClass',BatchId__c=batchableContext.getJobId() , BatchStatus__c='Processing', RecordsAlreadyProcess__c=0,TotalRecordstoProcess__c=aproxRecordsToProcess);

uow.registerNew(m_batchControl);

// in part 2 of this post the object BatchRecordTracking will be linked to BatchControl

m_batchRecordsTracking = new BatchRecordTracking__c(BatchClass__c= 'MyBatchClass',BatchId__c=m_batchId);

uow.registerNew(m_batchRecordsTracking);

uow.commitWork();

Lo primero que hago justo al entrar en la parte del Execute es crear dos registros. Un registro para BatchControl el cual iré actualizando con los registros procesados o errores en su caso. El otro registro será el “padre” de todos los registros que se crearan mas delante de tipo RecordProcessedInBatch. Ten en cuenta que por ahora solamente se crea un padre si deseas crear más , deberás cambiar un poquito el código J Lo segundo y más importante es crear los registros que son usados para bloquear a otros usuarios de procesarlos a la vez y crear datos duplicados o incorrectos en base de datos.

List<RecordProcessedInBatch__c> recordsWillBeProcessed = new List<RecordProcessedInBatch__c>();

for(MyTestObject__c mto : scope)

{

    RecordProcessedInBatch__c rpb = new RecordProcessedInBatch__c(BatchRecordTracking__c=m_batchRecordsTracking.Id,ProcessedRecordId__c =mto.Id, MyTestObject__c= mto.Id); // if you want more than 25 objects you will need to use text field to save the id

    recordsWillBeProcessed.add(rpb);

}

Database.insert(recordsWillBeProcessed,false);

Te estas preguntando por que no uso en esta parte la clase UnitOfWork verdad? Pues resulta que el “truco” esta en usar .insert(list, false), ya que si algún registro falla todos los demás serán creados. ¿Por qué? Deberán de fallar si hay algún otro registro que esté asociado con el mismo record que se desea procesar. Sin embargo no fallará con aquellos registros que no están siendo procesados, y son con los que realmente podemos trabajar. Y al final de cada Execute actualizaremos el registro creado para Batch Control. En cada update insertaremos el total de registros que han sido procesados.

m_batchControl.RecordsAlreadyProcess__c = Integer.valueOf(m_batchControl.RecordsAlreadyProcess__c)+scope.size();

uow2.registerDirty(m_batchControl);

La siguiente pregunta sería pensar, ¿qué pasará si un error ocurre? Si ocurriese un error los registros resultantes, los nuevos registros creados en el proceso batch, se quedaran en estado “Error” nunca pasarán a “Complete” . Ten en cuenta que se usará un registro como padre ( en el código será m_myNewTestObject ) y todos los demás se le añadirán como hijos, para que de esa manera con esta arquitectura podamos seguir y notificar de mejor manera al usuario si algo ocurre. De igual manera el registro de BatchControl__c será actualizado al estado de Error y se rellenara el campo erro con el dicho.

m_batchControl.BatchStatus__c= 'Failed';

m_batchControl.Error__c= 'There is an error: '+ e;

uow.registerDirty(m_batchControl);

m_myNewTestobject.Error__c= 'There is an error in the "execute": '+ e;

m_myNewTestobject.Status__c='Error';

uow.registerDirty(m_myNewTestobject);

Y no se borrara nada , se le deja la opción al usuario ( o al desarrollador ) Al final del proceso, en la parte Finish, lo que estoy haciendo es actualizar de nuevo el registro de BatchControl__c , y borrar todo lo demás, aunque claro solamente lo guardo para hacer reports. Realmente si todo va bien debería ser borrado y así liberar el espacio. Nota: voy a intentar poner todo el código en un repositorio publico de git lo antes posible. Recursos: financialforcedev/ fflib-apex-common Database class