La Revolución!! El poder de @Invocable – Spring 15 (Parte 1)

Entre las cositas que nos trae esta nueva reléase, Spring ’15, una de las que más me gusta es la posibilidad de invocar métodos apex desde Process builder.

Pero lo que me gusta aún más, es que estos métodos no solamente se pueden invocar/ ejecutar desde Process Builder sino que también los puedes invocar y ejecutar usando REST APIs

Pues vamos a ver cómos se puede hacer, que debemos tener en cuenta.

Lo primero es implementar la clase que contiene nuestro @InvocableMethod. Dicha clase es una clase normal que puede ser tanto pública  o global pero debe ser externa.

Apunte: Cuando estaba probando la funcionalidad en una org pre-release me dio este error << We can’t find an action with the name and action type that you specified.>> Cambie la clase a global, guardé , volví a editar cambié de nuevo a pública y ya no me ha vuelto a pasar.

 

Una vez tenemos definida nuestra clase , pasamos a implementar los métodos que compondrán esta.

Apunte: De nuevo algo a tener en cuenta es que solamente podemos tener un método con la notación @InvocableMethod

 

¿Cómo debemos definir nuestro método invocable?

Tenemos varias opciones, pero veamos lo que ocurre en cada una de ellas:

  1. @InvocableMethod public static void myInvocableMethod() 
    • Desde Process Builder: Process builder tendrá conocimiento del método, y lo invocara cuando todo cumpla los requistos que se han especificado en dicho proceso.
    • REST calls: Y como comentaba estos métodos también podrán ejecutarse y consultarse a través de REST API, pero para ello necesitas estar definidos de una forma específica. Con lo que ¡OJO! , en esta definición no podremos ejecutar el metodo usando llamadas REST (POST call) . A través de REST API solamente podremos consultarlo ,(GET call) nos devolverá todos los metadatos del método. Algo como esto: ( si os fijaies inputs se refiere a los parametros que se le pasan al metodo y output lo que el metodo devuelve, en este caso nada )
      { "description" : null,
         "inputs" : [ ],
          "label" : "InvocableClass",
          "name" : "InvocableClass",
          "outputs" : [ ],
          "standard" : false,
          "targetEntityName" : null,
          "type" : "APEX" }
      

 

  1.  @InvocableMethod public static List<String> myInvocableMethod() 
    • Desde Process Builder: de nuevo Process Builder vuelver a reconocerlo y ejecutarlo,
    • Desde REST API: aunque si intentamos ejecutarlo a través de Rest API este no será ejecutado, sin embargo como antes tendremos la posibilidad de conseguir los metadatos.
    • Apunte: También me gustaría comentar que aunque he puesto como ejemplo List< String> , la lista puede ser de cualquier tipo. Pero eso si , necesita ser List , no puede ser directamente un tipo de dato simple, lo que quiero decir es que no es posible devolver un simple String, o quizás un Account… debe ser List<String> , List<Account>… Al final del post podreis encontrar todos los tipos permitidos tanto de entrada como de salida.
  2.  @InvocableMethod public static void myInvocableMethod(List<String> myListParam) 
    • Desde Process Builder: de nuevo podemos decir que Process builder es feliz ejecutando este método, y desde process builder podemos establecer el valor del parámetro myList. Lo interesante en este punto es que aunque process builder parece que nos deja añadir más de un parámetro veremos que no está permitido. Y de igual forma si en la definición intentamos añadir mas de un parámetro de nuevo nos darás un error porque no más de un parámetro es permitido. Aquí quizás podríamos ser un poco críticos desde el punto de vista de UX, ¿por qué me dejas añadir mas de un parámetro en UI si mas tarde no me permites guardar? … bueno podríamos argumentar varias razones, pero realmente funciona igual que cuando intentamos implementar el método con mas de un parámetro, podemos añadir todos los que queramos pero cuando llega la hora de guardar entonces es cuando salta la validación. ¿Podríamos decir que Process Builder se ha creado con mentalidad de compilador 😀 ? ( ya lo sé una broma mala 😛 )
    • REST API: De nuevo podremos como antes obtener los metadatos de este método (GET call) Pero ahora por fin tenemos luz verde!! Se invocará el método, donde la llamada será una llamada POST. Es aquií con esta notacion cuando podemos hacer consultas o crear datos , o invocar otros procesos…  El cuerpo de esta llamada será algo como esto:
        {"inputs" : [ {"myListParam" : "param1"} , {"myListParam" : "param2"} ]} 

Luego para poder llamar a estos metodo usando REST APIs necesitamos definir el metodo con un parametro de entrada , sino este no seraá invocado/ejecutado tras la llamada.

 

Lo mejor para el final, quizás lo que mas me atrae de esta funcionalidad es que dentro de los @InvocableMethod podemos hacer llamadas a APIs de otros productos, con lo que las integraciones con otros productos, aplicaciones será mucho más facil. De hecho quizas es un futuro a una nueva forma de definir  e implimentar las APIs que compondran los productos para que vivan o sean usadas dentro de @InvocableMethods.

Para terminar, he aquí el ejemplo que podría resumir este post:


global class CreateInvoicesUsingInvocable {
    @InvocableMethod
    public static List<Invoice__c> InsertInvoices(List <String> vendors) {

       List<Invoice__c> myInvoices = new List<Invoice__c>();
       for (String  invVendor: vendors)
       {
         Invoice__c inv = new Invoice__c(Vendor__c = invVendor);
         myInvoices.add(inv) ;
       }

       insert myInvoices;

       return myInvoices;
    }

  //other methods 
}

Para probar esto mismos desde workbench , usamos una llamada REST  – POST

EndPoint:  /services/data/v33.0/actions/custom/apex/CreateInvoicesUsingInvocable
Method : Post
Request Body:


{
"inputs" : [ {
"vendors" : "v1"
},
{
"vendors" : "v2"
}
]
}

Lo mejor para el final, lo que más más me ha gustado es que dentro de este @InvocableMethod podemos llamar a “terceras” APIs, con lo cual el proceso de integración podría ser mucho mucho mas sencillo.

¡La revolución! Podemos empezar a pensar en una nueva forma de implementar las APIs de nuestro productos para que estas sean usadas dentro de los nuevos y potentes @InvocableMethod , y a la vez llamadas a través de Process builder para hacer la vida más fácil a Consultores, Administradores…

Pero además para desarrolladores de aplicaciones externas que pueden llamar a estos “Cool Methods” 😀

Para terminar una pequeña lista a tener en cuenta.

  1. Solamente 1 metodod the tipo @InvocableMetod se permite por clase
  2. Estos métodos no se pueden ejecutar desde un Trigger
  3. @InvocableMethod tiene que ser static public o static global, y debe estar definido en una clase externa.
  4. Solamente 1 parametro de entrada es permitido y el tipo de este parámetro debe ser :
    1. Lista de datos primitivos o lista de listas de datos primitivos. El tipo Object no se permite.
    2. Lista de tipos Apex ( clases , datos definidos por el usuario ) que contengan la notación @InvocableVariable.
      1. Si la clase que se pasa por parámetro dentro de la lista no continue ninguna variable con esta notación, no se permite
        
        public with sharing class MyClass{
        
             @InvocableVariable
             public String myVar;
        }
        
        public with sharing class MyInvocableClass{
        
            @InvocableMethod
        
            public static void myInvocable( List<MyClass> myclasses )
        
            {
        
              //Do something
        
            }
        }
        
        
    3. List<SObject> no se permite, pero cuando no nos referimos al genérico SObject, entonces sí podemos , List<Account> sí esta permitido.
  1. Si el método nos devuelve algo, debe ser de tipo:
    1. Lista de primitivos, pero no el genérico Object
    2. Lista de un tipo especifico de SObject, como por ejemplo List<Account> pero no el genérico List<SObject>
    3. Lista de objetos definidos por el usuario, Apex type, como clases … pero no el genérico List<Object> De nuevo estas clases deben de tener al menos una variable con la notación @InvocableVariable
  2. Y algo muy bueno!! Se pueden usar @InvocableMethod dentro de packetes!! Ahora sí una vez dentro del paquete para versiones posteriores, este método no podrá ser borrado de dicho paquete.

Algunos links que podrian ser de utilidad:

Un gran y divertido ejemplo de uso por Andy

– Stackexchange 1

– Stackexchange 2

– Invocable Actions

– Invocable Methods Anotation

– REST API 

-Process Builder

 

@testSetup en Spring ’15

Seguramente ya le habéis echado un vistazo a las notas de la última Release de Salesforce, Spring ’15.

En esta reléase aparece una nueva notación @testSetup.

Esta nueva notación va asociada a métodos en los cuales serán creado los datos que mas adelante los diferentes métodos test existentes en la clase pueden utilizar durante su ejecución.

Lo que conseguimos entonces es un código mucho más limpio sin repetición de creación de datos por cada método test ejecutado, y si ya esa creación se había implementado usando un método especifico para dicha de creación de datos siendo llamado desde el método test a ejecutar, ahora esta llamada ya no será necesaria.

Podemos tener mas de un método con @testSetup en una clase, pero lo que no podemos asegurar es el orden en el que se ejecutarán. Luego debemos hacer la creación de datos independiente.

La siguiente pregunta que nos puede cruzar por la mente es volúmenes. ¿Quiere decir esto que se han incrementado?, ¿que la creación de datos no ocurre en el mismo contexto de ejecución de nuestro método test?

Esta vez tengo que decir que tristemente, la creación de datos ocurre en el mismo contexto con lo cual no hay incremento de limites.

Si queremos que estos aumenten debemos como ya hacíamos anteriormente usar Test.startTest() para ello.

Luego la mayor ventaja de esta notación es ayudarnos a tener un código más limpio, no repeticiones de código y hacer una carga de datos compatible para todos los métodos test de la clase.

A continuación os dejo una clase test y su correspondiente log para que veáis el orden de ejecución y limites.

Hasta Pronto!


@isTest

private class MyTest

{

   @testSetup

   static void createData()

   {

      System.debug('Enter in createData Method1: testSetup::'+Limits.getDMLStatements()+'::'+Limits.getLimitDMLStatements());

      Account newAccount = new Account(Name='Account1');

      insert newAccount;

      System.debug('Enter in createData Method2: testSetup::'+Limits.getDMLStatements()+'::'+Limits.getLimitDMLStatements());

   }

   static testMethod void testData1()

   {

      Test.startTest();

      System.debug('Enter in testData1::'+Limits.getDMLStatements()+'::'+Limits.getLimitDMLStatements());

      Account newAccount = [Select Name From Account];

      System.assertEquals('Account1', newAccount.Name);

      System.debug('Enter in testData12::'+Limits.getDMLStatements()+'::'+Limits.getLimitDMLStatements());

      Test.stopTest();

   }

   static testMethod void testData2()

   {

       System.debug('Enter in testData2::'+Limits.getDMLStatements()+'::'+Limits.getLimitDMLStatements());

       Account newAccount = [Select Name From Account];

       System.assertEquals('Account1', newAccount.Name);

       System.debug('Enter in testData22::'+Limits.getDMLStatements()+'::'+Limits.getLimitDMLStatements());

   }

}

El Orden de Ejecución ha sido entonces:

1. createData

2. testData1

3. testData2

Debug Log:


33.0 APEX_CODE,DEBUG;APEX_PROFILING,INFO;CALLOUT,INFO;DB,INFO;SYSTEM,DEBUG;VALIDATION,INFO;VISUALFORCE,INFO;WORKFLOW,INFO
17:14:58.094 (94312697)|EXECUTION_STARTED
17:14:58.094 (94394637)|CODE_UNIT_STARTED|[EXTERNAL]|01pB0000000HolG|myTest.createData
17:14:58.094 (94810652)|METHOD_ENTRY|[2]|01pB0000000HolG|myTest.myTest()
17:14:58.094 (94821557)|METHOD_EXIT|[2]|myTest
17:14:58.094 (94952980)|SYSTEM_METHOD_ENTRY|[7]|Limit.getDmlStatements()
17:14:58.095 (95001929)|SYSTEM_METHOD_EXIT|[7]|Limit.getDmlStatements()
17:14:58.095 (95048420)|SYSTEM_METHOD_ENTRY|[7]|String.valueOf(Object)
17:14:58.095 (95070797)|SYSTEM_METHOD_EXIT|[7]|String.valueOf(Object)
17:14:58.095 (95085582)|SYSTEM_METHOD_ENTRY|[7]|Limit.getLimitDmlStatements()
17:14:58.095 (95097514)|SYSTEM_METHOD_EXIT|[7]|Limit.getLimitDmlStatements()
17:14:58.095 (95114023)|SYSTEM_METHOD_ENTRY|[7]|String.valueOf(Object)
17:14:58.095 (95129705)|SYSTEM_METHOD_EXIT|[7]|String.valueOf(Object)
17:14:58.095 (95151621)|SYSTEM_METHOD_ENTRY|[7]|System.debug(ANY)
17:14:58.095 (95160524)|USER_DEBUG|[7]|<b>DEBUG|Enter in createData Method1: testSetup::0::150</b>
17:14:58.095 (95167447)|SYSTEM_METHOD_EXIT|[7]|System.debug(ANY)
17:14:58.096 (96543628)|DML_BEGIN|[9]|Op:Insert|Type:Account|Rows:1
17:14:58.183 (183682155)|DML_END|[9]
17:14:58.183 (183738203)|SYSTEM_METHOD_ENTRY|[10]|Limit.getDmlStatements()
17:14:58.183 (183780544)|SYSTEM_METHOD_EXIT|[10]|Limit.getDmlStatements()
17:14:58.183 (183815295)|SYSTEM_METHOD_ENTRY|[10]|String.valueOf(Object)
17:14:58.183 (183835500)|SYSTEM_METHOD_EXIT|[10]|String.valueOf(Object)
17:14:58.183 (183847113)|SYSTEM_METHOD_ENTRY|[10]|Limit.getLimitDmlStatements()
17:14:58.183 (183866985)|SYSTEM_METHOD_EXIT|[10]|Limit.getLimitDmlStatements()
17:14:58.183 (183884620)|SYSTEM_METHOD_ENTRY|[10]|String.valueOf(Object)
17:14:58.183 (183900710)|SYSTEM_METHOD_EXIT|[10]|String.valueOf(Object)
17:14:58.183 (183919237)|SYSTEM_METHOD_ENTRY|[10]|System.debug(ANY)
17:14:58.183 (183926903)|USER_DEBUG|[10]|DEBUG|<b>Enter in createData Method2: testSetup::1::150</b><b>
</b>17:14:58.183 (183932859)|SYSTEM_METHOD_EXIT|[10]|System.debug(ANY)
17:14:58.173 (183961154)|CUMULATIVE_LIMIT_USAGE
17:14:58.173|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 0 out of 100
Number of query rows: 0 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 1 out of 150
Number of DML rows: 1 out of 10000
Maximum CPU time: 0 out of 10000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 100
Number of Email Invocations: 0 out of 10
Number of future calls: 0 out of 50
Number of queueable jobs added to the queue: 0 out of 50
Number of Mobile Apex push calls: 0 out of 10

17:14:58.173|CUMULATIVE_LIMIT_USAGE_END

17:14:58.183 (183993101)|CODE_UNIT_FINISHED|myTest.createData
17:14:58.185 (185131883)|EXECUTION_FINISHED
17:14:58.194 (194178721)|EXECUTION_STARTED
17:14:58.194 (194189708)|CODE_UNIT_STARTED|[EXTERNAL]|01pB0000000HolG|myTest.testData1
17:14:58.194 (194429563)|METHOD_ENTRY|[2]|01pB0000000HolG|myTest.myTest()
17:14:58.194 (194439177)|METHOD_EXIT|[2]|myTest
17:14:58.194 (194581006)|SYSTEM_METHOD_ENTRY|[14]|system.Test.startTest()
17:14:58.196 (196410911)|SYSTEM_METHOD_EXIT|[14]|system.Test.startTest()
17:14:58.196 (196436313)|SYSTEM_METHOD_ENTRY|[15]|Limit.getDmlStatements()
17:14:58.196 (196533282)|SYSTEM_METHOD_EXIT|[15]|Limit.getDmlStatements()
17:14:58.196 (196565724)|SYSTEM_METHOD_ENTRY|[15]|String.valueOf(Object)
17:14:58.196 (196584800)|SYSTEM_METHOD_EXIT|[15]|String.valueOf(Object)
17:14:58.196 (196599347)|SYSTEM_METHOD_ENTRY|[15]|Limit.getLimitDmlStatements()
17:14:58.196 (196611356)|SYSTEM_METHOD_EXIT|[15]|Limit.getLimitDmlStatements()
17:14:58.196 (196627750)|SYSTEM_METHOD_ENTRY|[15]|String.valueOf(Object)
17:14:58.196 (196643767)|SYSTEM_METHOD_EXIT|[15]|String.valueOf(Object)
17:14:58.196 (196662091)|SYSTEM_METHOD_ENTRY|[15]|System.debug(ANY)
17:14:58.196 (196668968)|USER_DEBUG|[15]|DEBUG|<b>Enter in testData1::0::150</b><b>
</b>17:14:58.196 (196674433)|SYSTEM_METHOD_EXIT|[15]|System.debug(ANY)
17:14:58.196 (196828389)|SOQL_EXECUTE_BEGIN|[16]|Aggregations:0|SELECT Name FROM Account
17:14:58.207 (207955957)|SOQL_EXECUTE_END|[16]|Rows:1
17:14:58.208 (208227438)|SYSTEM_METHOD_ENTRY|[17]|System.assertEquals(ANY, ANY)
17:14:58.208 (208283470)|SYSTEM_METHOD_EXIT|[17]|System.assertEquals(ANY, ANY)
17:14:58.208 (208299244)|SYSTEM_METHOD_ENTRY|[18]|Limit.getDmlStatements()
17:14:58.208 (208328115)|SYSTEM_METHOD_EXIT|[18]|Limit.getDmlStatements()
17:14:58.208 (208360341)|SYSTEM_METHOD_ENTRY|[18]|String.valueOf(Object)
17:14:58.208 (208379606)|SYSTEM_METHOD_EXIT|[18]|String.valueOf(Object)
17:14:58.208 (208390294)|SYSTEM_METHOD_ENTRY|[18]|Limit.getLimitDmlStatements()
17:14:58.208 (208401225)|SYSTEM_METHOD_EXIT|[18]|Limit.getLimitDmlStatements()
17:14:58.208 (208417086)|SYSTEM_METHOD_ENTRY|[18]|String.valueOf(Object)
17:14:58.208 (208433053)|SYSTEM_METHOD_EXIT|[18]|String.valueOf(Object)
17:14:58.208 (208448370)|SYSTEM_METHOD_ENTRY|[18]|System.debug(ANY)
17:14:58.208 (208455254)|USER_DEBUG|[18]|DEBUG|<b>Enter in testData12::0::150</b>
17:14:58.208 (208460659)|SYSTEM_METHOD_EXIT|[18]|System.debug(ANY)
17:14:58.208 (208476092)|SYSTEM_METHOD_ENTRY|[19]|system.Test.stopTest()
17:14:58.210 (210157421)|SYSTEM_METHOD_EXIT|[19]|system.Test.stopTest()
17:14:58.200 (210186511)|CUMULATIVE_LIMIT_USAGE
17:14:58.200|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 0 out of 100
Number of query rows: 0 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 1 out of 150
Number of DML rows: 1 out of 10000
Maximum CPU time: 0 out of 10000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 100
Number of Email Invocations: 0 out of 10
Number of future calls: 0 out of 50
Number of queueable jobs added to the queue: 0 out of 50
Number of Mobile Apex push calls: 0 out of 10

17:14:58.200|TESTING_LIMITS
17:14:58.200|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 1 out of 100
Number of query rows: 1 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 0 out of 150
Number of DML rows: 0 out of 10000
Maximum CPU time: 0 out of 10000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 100
Number of Email Invocations: 0 out of 10
Number of future calls: 0 out of 50
Number of queueable jobs added to the queue: 0 out of 50
Number of Mobile Apex push calls: 0 out of 10

17:14:58.200|CUMULATIVE_LIMIT_USAGE_END

17:14:58.210 (210235275)|CODE_UNIT_FINISHED|myTest.testData1
17:14:58.211 (211251838)|EXECUTION_FINISHED
17:14:58.260 (260437301)|EXECUTION_STARTED
17:14:58.260 (260456749)|CODE_UNIT_STARTED|[EXTERNAL]|01pB0000000HolG|myTest.testData2
17:14:58.260 (260792341)|METHOD_ENTRY|[2]|01pB0000000HolG|myTest.myTest()
17:14:58.260 (260803454)|METHOD_EXIT|[2]|myTest
17:14:58.260 (260915130)|SYSTEM_METHOD_ENTRY|[24]|Limit.getDmlStatements()
17:14:58.260 (260967160)|SYSTEM_METHOD_EXIT|[24]|Limit.getDmlStatements()
17:14:58.261 (261006042)|SYSTEM_METHOD_ENTRY|[24]|String.valueOf(Object)
17:14:58.261 (261028543)|SYSTEM_METHOD_EXIT|[24]|String.valueOf(Object)
17:14:58.261 (261043955)|SYSTEM_METHOD_ENTRY|[24]|Limit.getLimitDmlStatements()
17:14:58.261 (261058175)|SYSTEM_METHOD_EXIT|[24]|Limit.getLimitDmlStatements()
17:14:58.261 (261074889)|SYSTEM_METHOD_ENTRY|[24]|String.valueOf(Object)
17:14:58.261 (261091042)|SYSTEM_METHOD_EXIT|[24]|String.valueOf(Object)
17:14:58.261 (261113424)|SYSTEM_METHOD_ENTRY|[24]|System.debug(ANY)
17:14:58.261 (261122513)|USER_DEBUG|[24]|DEBUG|<b>Enter in testData2::1::150</b><b>
</b>17:14:58.261 (261128396)|SYSTEM_METHOD_EXIT|[24]|System.debug(ANY)
17:14:58.261 (261325197)|SOQL_EXECUTE_BEGIN|[25]|Aggregations:0|SELECT Name FROM Account
17:14:58.264 (264020860)|SOQL_EXECUTE_END|[25]|Rows:1
17:14:58.264 (264157839)|SYSTEM_METHOD_ENTRY|[26]|System.assertEquals(ANY, ANY)
17:14:58.264 (264206417)|SYSTEM_METHOD_EXIT|[26]|System.assertEquals(ANY, ANY)
17:14:58.264 (264221077)|SYSTEM_METHOD_ENTRY|[27]|Limit.getDmlStatements()
17:14:58.264 (264249128)|SYSTEM_METHOD_EXIT|[27]|Limit.getDmlStatements()
17:14:58.264 (264276258)|SYSTEM_METHOD_ENTRY|[27]|String.valueOf(Object)
17:14:58.264 (264294737)|SYSTEM_METHOD_EXIT|[27]|String.valueOf(Object)
17:14:58.264 (264306009)|SYSTEM_METHOD_ENTRY|[27]|Limit.getLimitDmlStatements()
17:14:58.264 (264316703)|SYSTEM_METHOD_EXIT|[27]|Limit.getLimitDmlStatements()
17:14:58.264 (264332518)|SYSTEM_METHOD_ENTRY|[27]|String.valueOf(Object)
17:14:58.264 (264348607)|SYSTEM_METHOD_EXIT|[27]|String.valueOf(Object)
17:14:58.264 (264363269)|SYSTEM_METHOD_ENTRY|[27]|System.debug(ANY)
17:14:58.264 (264369412)|USER_DEBUG|[27]|DEBUG|<b>Enter in testData22::1::150</b>
17:14:58.264 (264374741)|SYSTEM_METHOD_EXIT|[27]|System.debug(ANY)
17:14:58.254 (264399759)|CUMULATIVE_LIMIT_USAGE
17:14:58.254|LIMIT_USAGE_FOR_NS|(default)|
Number of SOQL queries: 1 out of 100
Number of query rows: 1 out of 50000
Number of SOSL queries: 0 out of 20
Number of DML statements: 1 out of 150
Number of DML rows: 1 out of 10000
Maximum CPU time: 0 out of 10000
Maximum heap size: 0 out of 6000000
Number of callouts: 0 out of 100
Number of Email Invocations: 0 out of 10
Number of future calls: 0 out of 50
Number of queueable jobs added to the queue: 0 out of 50
Number of Mobile Apex push calls: 0 out of 10

17:14:58.254|CUMULATIVE_LIMIT_USAGE_END

17:14:58.264 (264433109)|CODE_UNIT_FINISHED|myTest.testData2
17:14:58.265 (265477515)|EXECUTION_FINISHED

 

 

 

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. 🙂