Introducción a Change Data Capture en Salesforce

Change Data Capture es un mecanismo ya disponible en Winter ’19 que facilita la integración entre sistemas y permite crear nuevos procesos de negocio, generando eventos (Change Data Events) cuando creamos, actualizamos, eliminamos o recuperamos un registro de un objeto.

En esta entrada veremos que con tan solo pocos clics podremos activar la generación de eventos, y suscribirnos para tratarlos con 3 mecanismos distintos.

Índice

  1. Introducción
  2. Tecnologías de Eventos de Salesforce
  3. ¿Qué es y para que sirve CDC? Ventajas respecto a las otras tecnologías.
  4. Activación de Change Data Capture
  5. Consumir los eventos (EMP Connector, empApi y triggers)
  6. Eventos Gap y Overflow
  7. Despliegue de CDC
  8. Conclusiones
  9. Enlaces interesantes

Tecnologías de Eventos de Salesforce

Change Data Capture (en adelante CDC) no aparece de la nada en un anuncio inesperado como LWC (Lightning Web Components), sino que es la evolución esperada de las tecnologías de eventos que Salesforce ya implementaba (ha estado en Beta desde hace 1 año).

Ya escribí un artículo, Introducción a la Arquitectura de Eventos de Salesforce. Te recomiendo que si esta es la primera vez que te introduces en este campo, leas ese artículo para entender los objetivos, ventajas y los productos creados por Salesforce, porque te darán una visión bastante clara de como ha sido la evolución y las opciones existentes.
Luego retorna a este para una introducción completa sobre Change Data Capture.

Qué es y para que sirve CDC?

CDC se basa en la tecnología de Platform Events y en mi opinión sus principales objetivos:

  1. Facilitar enormemente la Integración entre sistemas desacoplados para sincronización de datos.
  2. Simplificar la creación de nuevos procesos** de negocio más sofisticados con mínimo esfuerzo.

A lo largo de este artículo ten en mente un ejemplo en tu mente: tu ORG permite la realización de pedidos de los productos de tu empresa, pero la gestión logística de la entrega de esos productos se realiza en otro sistema, digamos SAP.
¿Cómo puedo sincronizar ambos sistemas para que SAP sea lo más eficiente posible para crear la entrega de un pedido realizado?

¿Cómo encaja CDC en la arquitectura de Salesforce?

Un breve repaso a las tecnologías existentes de réplica de datos de Salesforce y veremos como encaja CDC en la foto.

Actualmente cuento hasta 5 maneras de replicar datos:

  1. SOQL para obtener registros modificados
  2. Triggers con Interfaces
  3. Replication API
  4. Streaming API
  5. Platform Events

SOQL con detección de última modificación

Es un mecanismo completamente manual, con una dificultad creciente respecto al número de entidades que queramos gestionar y en la medida que aparezcan relaciones entre entidades su complejidad puede ser muy superior al beneficio esperado.

Además en volúmenes elevados, la detección de cambios se complica, e incluso nos puede resultar muy complejo asegurar que no se ha perdido información. Además el consumo por parte del sistema destino, supone un acoplamiento muy alto, dado que ambos deberán mantener cierto sincronismo de realización, conocimiento de interfaces, etc.

Replication API

La Replication API, realiza pooling de cambios cada cierto tiempo, en base a una SOQL que hayamos definido.

El intervalo recomendado por Salesforce para obtener los cambios, con Replication API es > 5’, ya que en volúmenes altos podríamos llegar al límite de ejecuciones.
Esto puede suponer una limitación que hay que valorar.

Dado que tenemos que realizar pooling de los cambios cada X minutos, seguramente realizaremos llamadas innecesarias (sin cambios). Adelantar, que en un mecanismo de Pub/Sub esto no sucede, dado que solo recibimos un evento en un cambio en los datos ya producido.

Dado el requerimiento de escribir una SOQL, cuando deseemos realizar un cambio, estamos obligados a realizar un deployment, etc.
Seguro que coincidimos que este mecanismo puede ser muy útil para ciertos casos de uso, pero no parece ser un mecanismo de realización para grandes volúmenes, desacoplado, etc.

Streaming API

Esta API utiliza el mecanismo de Pub/Sub con publicación de eventos ad-hoc con un contenido que nosotros decidimos (Generic) o cuando se cumple las condiciones de la Query que proporcionamos (Push Topic).

Está disponible, para objetos Custom y para algunos objetos Standard (Account, Campaign, Case, Contact, ContractLineItem, Entitlement, Lead, LiveChatTranscript, Opportunity, Quote, QuoteLineItem, ServiceAppointment, ServiceContract, Task, WorkOrder, WorkOrderLineItem) donde seguramente detectarás notables ausencias.

Platform Events

Es la tecnología base de CDC, por lo tanto aporta muchos de sus beneficios. La diferencia con CDC radica en la definición del payload generado. Mientras que en Platform Events, nosotros definimos su contenido, en CDC está estructura en la Header y el Body, y es el propio sistema quien asegura la completa y correcta información del Evento.
Según los arquitectos de Salesforce, en volúmenes muy elevados, la escalabilidad que ofrece CDC es superior a Platform Events.

Ventajas del CDC sobre el resto de mecanismos

A continuación enumero las que creo principales ventajas de CDC respecto al resto de tecnologías vistas:

  • La entrega de los eventos en CDC garantiza el orden transaccional de los eventos sucedidos.
  • Actualmente se soportan todos los objetos Custom y algunos Standard, pero su vocación es soportarlos todos (según se comentan en los vídeos de presentación de la herramienta).
  • La utilización de un almacén temporal de eventos (actualmente almacena 3 días), permite desacoplar completamente el ritmo a que se producen los eventos en Salesforce, y el ritmo al que los suscriptores los consumen.
  • Dado que los suscriptores, pueden sufrir caídas/paradas y recuperar los eventos, mediante una sencilla llamada, esto permite la optimización de los suscriptores (por ejemplo si el suscriptor es una aplicación desplegada en Amazon AWS, podrá estar desactivada el fin de semana y recuperar los eventos al reiniciarse el lunes).
  • Los suscriptores implementan la lectura del evento, mediante un protocolo estándar (CometD) del cual existen librerías y ejemplos en varios lenguajes, que proporciona la misma Salesforce o la comunidad. Esto confirma la capacidad de implementación y soporte del desarrollo que realizemos.
  • La información del evento contiene una estructura conocida como Header que es igual para todos los eventos (qué evento sucedió, cuando, quién, etc.) y Body (detalle del cambio), y mediante un esquema de objeto conocido y publicado por Salesforce. Esto permite simplificar nuestro código de manera muy importante para el consumo de los eventos.

En la siguiente imagen (origen: Salesforce), se muestra una comparativa que ayuda a entender las diferencias:

Comparación entre CDC y los otros mecanismos - Fuente: Salesforce
Comparación entre CDC y los otros mecanismos – Fuente: Salesforce

Veamos a continuación el procedimiento para activar la generación de Change Data Capture en nuestra ORG.

Activación de Change Data Capture

Dado que en las ORGs de Developer tenemos disponible este mecanismo, podemos realizar nuestros tests sin problema. Existen ciertas limitaciones, claro está, pero no impiden, de ninguna manera, probar y testear su funcionamiento.

Para activar que se generen los Data Change Events de CDC en cada objeto, es tan sencillo como se muestra en la siguiente imagen:

Como activar eventos en un objeto de la ORG
Como activar eventos en un objeto de la ORG

A partir de este momento los Productos que añadimos, modificamos, eliminamos o recuperador a una Order, generarán eventos de cambios que serán publicados en el BUS y almacenados hasta 3 días.

Generarlos no supone ningún esfuerzo, veamos a continuación los mecanismos que tenemos para suscribirnos y consumir los eventos.

Consumir los eventos generados

Existen 3 mecanismos básicos:

  1. Clientes que implementen el protocolo CometD (como EMP Connector).
  2. Apex Triggers mapeando el evento After Trigger.
  3. Mediante el componente lightning:empApi que permite suscribirse y recibir eventos.

Antes de entrar en detalle veamos algunas consideraciones importantes sobre seguridad.

Consideraciones de seguridad para suscribirse a eventos

Las consideraciones de seguridad más importantes en mi opinión son las siguientes (aunque existen algunas más y te animo a que consultes la documentación oficial):

  • CDC no tiene en cuenta los sharing settings, es decir, envía los eventos a todos los suscriptores para todos los registros de cambio, y por ello el usuario con el que realizamos la suscripción debe tener los permisos adecuados sobre el canal de suscripción.

En la imagen a continuación se muestran los permisos necesarios según el tipo de canal de suscripción:

Permisos necesarios según el canal al que deseemos suscribirnos
Permisos necesarios según el canal al que deseemos suscribirnos – Fuente:Salesforce

  • Los eventos para CDC, respetan FLS (Field Level Security) por lo que el usuario solo recibirá los campos a los cuales tenga acceso.

Mencionados estos aspectos de seguridad, veamos para cada uno de los 3 mecanismos de suscripción, como es el proceso.

Suscripción mediante el EMP Connector

EMP acrónimo de Enterprise Messaging Platform es una librería Java, proporcionada por Salesforce con soporte de la comunidad, que implementa el protocolo CometD.

Tiene básicamente 2 funcionalidades:

  1. Implementa el protocolo y nos ofrece una API para que la podamos utilizar en nuestros desarrollos Java, y consumir los eventos facilitándonos el uso de CometD.
  2. Utilizando su propia API, propociona una serie de comandos, para obtener los eventos generados en una ORG.

Para utilizar EMP, debemos descargar la librería y construir el package:

$ git clone https://github.com/forcedotcom/EMP-Connector.git
$ cd emp-connector
$ mvn clean package

Construido el ejecutable invocamos la suscripción a todos los eventos generados, en mi caso, los relacionados con añadir/modificar/eliminar/restaurar Productos a una Order. 
Esta es la línea de comando que yo he utilizado:

Al recibir un evento, obtenemos la siguiente información:

Información recibida del Evento de creación al añadir el Product a la Order
Información recibida del Evento de creación al añadir el Product a la Order

Por supuesto la suscripción realizada /data/ChangeEvents, es una suscripción muy genérica, ya que se suscribe a todos los eventos de todos de la ORG. En la mayoría de los casos esta no será el caso deseado, por lo que para suscribirse a los eventos de un objeto concreto es /data/Standard_Object_Name>ChangeEvent, y por tanto, en nuestro caso lo más adecuado hubiera sido: /data/OrderItemChangeEvent.

Para los objetos Custom es ligeramente distinto: /data/__ChangeEvent, es decir /data/LectorBlog__ChangeEvent, que correspondería al objeto Custom LectorBlog__c._ 

Finalmente como parámetro opcional podemos indicar, desde que Identificador de mensaje queremos recuperar replay id (que comentábamos anteriormente).

Nota: En el respositorio de Git del EMP Connector, están disponibles varios ejemplos completos de como utilizar la librería disponibles en la ruta (EMP-Connector/src/main/java/com/salesforce/emp/connector/example).

Otra librería para consumo de Eventos pero basada en Javascript está disponible con JsForce, de la que te dejo un enlace al final del artículo.

Suscripción mediante Apex Triggers

Importante: Este mecanismo de suscripción está en Developer preview para Spring 19 en ORGs de developer.

Consiste en capturar el evento y gestionarlo mediante un trigger de tipo after insert, de hecho, la única opción disponible. El evento se ejecuta de forma asíncrona después que la transacción haya finalizado.

Su formato es muy sencillo:

 trigger CambioOrderItem on OrderItemChangeEvent (after insert)

Destacar que:

  • Los eventos se generan bajo la entidad ‘Automated Process’ como sucede también en los Platform Events.
  • No aparece en los logs por defecto, por lo que hay que añadir una traza para obtenerlos.
  • Es posible codificar un trigger para un objeto que aún no tiene activado CDC, con lo que no habrá ningún error, pero por supuesto ninguna activación del trigger.

Siguiendo con el ejemplo anterior, veamos como podemos capturar los eventos cuando modificamos los productos de una Order:

Código de suscripción a los eventos de OrderItem mediante un Trigger y correlación de acceso a la información del evento recibido.
Código de suscripción a los eventos de OrderItem mediante un Trigger y correlación de acceso a la información del evento recibido.

En esta imagen he intentado mostrar el código del trigger, y como en la instancia event tenemos toda la información correspondiente. Para acceder a esa información, accedemos a la estructura ChangeEventHeader y al interior de sus campos, y al resto de campos como por ejemplo event.Quantity.

El Log generado es el esperado:

...
19:07:52.0 (2936891)|USER_DEBUG|[7]|DEBUG|-ege- Evento de Objeto: OrderItem
...
19:07:52.0 (2978621)|USER_DEBUG|[8]|DEBUG|-ege- Operación: CREATE
...
19:07:52.0 (3082285)|USER_DEBUG|[11]|DEBUG|-ege- Cantidad del Order Item: 5.00
...
19:07:52.0 (3134551)|USER_DEBUG|[12]|DEBUG|-ege- Precio del Order Item: 100.00
...

Suscripción mediante lightning:EmpApi

El componente lightning:empApi expone los servicios de la librería EmpJs para la suscripción a un canal, y la recepción de eventos a través de éste canal mediante el protocolo CometD. Pero todo esto, queda simplificado mediante este componente.

En resumidas cuentas, para el developer, es un componente nativo lightning que ofrece 2 métodos fundamentales:

  • subscribe para suscribirse a un canal y mediante una callback recibir los eventos con su Header y Payload.
  • unsubscribe para cancelar la suscripción.

Para demostrar su uso, he creado un componente lightning, veamos como ha quedado:

Componente Lightning que hace uso de empApi para suscribirse, recibir eventos y cancelar la suscripción
Componente Lightning que hace uso de empApi para suscribirse, recibir eventos y cancelar la suscripción

Mi objetivo es capturar todos los cambios sobre OrderItem (caso de uso de logística que comentaba al principio). Para ello, y partiendo del ejemplo de la documentación he creado un componente cuyo Markup y Controller son los siguientes:

<!--  
 @author Esteve Graells 
 @date: Jan 2019  
 @description:  Componente para suscribirse a un canal, recibir eventos y mostrarlos
-->



<aura:component description="Componente para suscripción a eventos mediante su Channel name" implements="flexipage:availableForAllPageTypes"
  access="global">

  <!-- IMPORTACION COMPONENTES-->
  <!-- Importación del componente empApi para la suscripción/recepción/cancelación-->
  <lightning:empApi aura:id="empApi" />

  <!-- DEFINICION ATRIBUTOS-->
  <!--Contiene el objeto resultado de establecer la suscripción -->
  <aura:attribute name="subscription" type="Map" />

  <!-- Lista de layoutItems con la información y Markup que se
  van creando dinámica en el Controller -->
  <aura:attribute name="listaEventosRecibidos" type="Aura.Component[]" />

  <!-- En la inicialización el Controller comprueba que se haya podido cargar empApi-->
  <aura:handler name="init" value="{!this}" action="{!c.onInit}" />

  <div class="c-container">

        <div class="slds-form-element">
          Introducir el nombre del canal: 
          <div class="slds-form-element__control">

          </div>
        </div>
      </lightning:layoutItem>
      <lightning:layoutItem flexibility="auto" size="4" padding="horizontal-small">
        <lightning:button label="Suscribirse a los Eventos CDC" onclick="{! c.subscribe }" class="c-btn" variant="brand" />
      </lightning:layoutItem>

      <!-- Este item permanece oculto hasta que se produce una suscripción correcta -->
      <aura:renderIf isTrue="{! !empty(v.subscription) }">

        <lightning:layoutItem padding="vertical-small" size="8" class="slds-p-top_large">
          <lightning:card title="Suscripción a un Channel">
            <p class="slds-p-horizontal_small"> Se ha realizado una suscripción al canal <strong>{!v.subscription.channel}.</strong>
              <h2>A partir de ahora se mostrarán los eventos recibidos.</h2>
            </p>
          </lightning:card>
        </lightning:layoutItem>

        <lightning:layoutItem flexibility="auto" size="4" padding="horizontal-small">
          <lightning:button label="Anular la suscripción al canal" onclick="{! c.unsubscribe }" class="c-btn" variant="brand" />
        </lightning:layoutItem>

      </aura:renderIf>

      <!-- En este elemento se van insertando dinámicamente item de eventos recibidos -->
      {!v.listaEventosRecibidos}

    </Lightning:layout>
  </div>
</aura:component>

El Controller:

/*
* @author Esteve Graells
* @date: Jan 2019
* @description: Controller para recibir eventos
*/

({
// Comprobamos que se haya cargado correctamente empApi
onInit: function (component, event, helper) {
// Get the empApi component
const empApi = component.find("empApi");
console.log('Buscando empApi component');

// Uncomment below line to enable debug logging (optional)
empApi.setDebugFlag(true);

// Register error listener and pass in the error handler function
empApi.onError(
$A.getCallback(error => {
// Error can be any type of error (subscribe, unsubscribe...)
console.error("EMP API error: ", error);
})
);
},

// Función de suscripción qu utiliza la primitiva de empApi
subscribe: function (component, event, helper) {

const empApi = component.find('empApi');

// Get the channel from the input box
const channel = component.find('channel').get('v.value');
console.log("Petición de suscripción al canal: ", channel);

// Replay option to get new events
const replayId = -1;

//Callback de recepción de nuevo evento recibido, creo dinámicamente un layoutItem y lo inserto
var eventoRecibidoCallback = function (eventoRecibido) {
console.log("Nuevo Evento recibido: ", JSON.stringify(eventoRecibido.data.payload.ChangeEventHeader.changeType));

var operacion = JSON.stringify(eventoRecibido.data.payload.ChangeEventHeader.changeType);
var entidad = JSON.stringify(eventoRecibido.data.payload.ChangeEventHeader.entityName);
var mensaje = "Se ha producido un " + operacion + " sobre la entidad: " + entidad;

$A.createComponents([
["lightning:layoutItem", { "flexibility": "auto", "padding": "vertical-small", "size": "8", "class": "slds-p-top_large" }],
["lightning:card", { "title": "Evento Recibido" }],
["aura:html", { "tag": "p", "body": mensaje, HTMLAttributes: { "class": "slds-p-horizontal_small" } }]
], function (components, status, stausMessageList) {

if (status == "SUCCESS") {

//Anidamos los 2 tags p en el body del card
components[1].set("v.body", components[2]);

//Anidamos el card dentro del layoutItem
components[0].set("v.body", components[1]);

//Insertamos el resultado en la variable
var listadoEventosRecibidosBody = component.get("v.listaEventosRecibidos");
listadoEventosRecibidosBody.push(components[0]);

//Actualizamos el valor lo que producirá una actualización de la UI
component.set("v.listaEventosRecibidos", listadoEventosRecibidosBody);
}

});

}.bind(this);

// Suscribirse al Channel con la empApi y establecer el callback para recibir eventos
empApi.subscribe(channel, replayId, eventoRecibidoCallback).then(function (subscription) {

console.log("Suscripción correcta al canal: " + channel);

//Fijamos el valor al atributo para la UI y para unsubscribe posterior
component.set('v.subscription', subscription);
});

},

// Cancelación de la suscripción: invocando empApi y limpiando variables, para actualización de la UI
unsubscribe: function (component, event, helper) {

const empApi = component.find("empApi");
const subscription = component.get("v.subscription");

empApi.unsubscribe(
subscription,
$A.getCallback(unsubscribed => {
console.log("Cancelación correcta de la suscripción del canal: " + unsubscribed.subscription);
component.set("v.subscription", null);

})
);
}
});

En este video demostrativo puedes ver cual es su funcionamiento a partir del segundo 33”:

El código es muy sencillo y como puede verse, el componente empApi, simplifica enormemente las funcionalidades de suscripción/recepción y cancelación.

Nota: es importante utilizar correctamente las Promises con sus Callbacks, para obtener los eventos.

Hasta aquí hemos visto la columna vertebral de CDC en la plataforma. Pero además la plataforma proporciona 2 tipos de eventos adicionales, que nos pueden ayudar a sincronizar con total fidelidad los cambios en origen con los suscriptores para 2 situaciones especiales.

Eventos Gap y Overflow

Estos 2 tipos de eventos, no están directamente relacionados con cambios en los datos de los registros de un objeto, sin de la propia plataforma, se denominan Gap y Overflow.

Eventos Gap

Los eventos Gap son actividades que suceden directamente en la base de datos, por ejemplo:

  • Archiving de datos
  • Jobs de limpieza de datos

Para que los suscriptores puedan suscribirse a estos eventos se generan 4 nuevos eventos:

  1. GAP_CREATE
  2. GAP_UPDATE
  3. GAP_DELETE
  4. GAP_UNDELETE

Te animo a consultar la documentación oficial si estás interesado.

Eventos Overflow

Los eventos Overflow tienen como objetivo limitar el volumen de eventos generado cuando se realizan actualizaciones masivas. Así cuando se producen actividades que generan un volumen superior a 100.000 mensajes, todos los mensajes a partir de esa cifra serán resumidos utilizando eventos Overflow.

Y, ¿cuál es la diferencia? Pues que un evento Overflow solo contiene datos en cabecera, no contiene los detalles sobre el cambio que se ha producido (dado que se puede deducir de sus predecesores) y no contiene el ID del récord identificado.

Esto puede suponer un inconveniente si por ejemplo estamos realizando un borrado masivo en cascada, con varios objetos involucrados, ya que solo recibiremos los IDs de los registros borrados de los 100.000 primeros registros, debes tenerlo en cuenta en tu diseño arquitectónico.

Despliegue de CDC

La Metadata ha sido ampliada para soportar el despliegue de los eventos que queremos disponibilizar en la plataforma.
El componente se ha denominado PlatformEventChannel (consulta la documentación y verás que simple es).

Conclusiones

En esta entrada, me he centrado en las capacidades principales de CDC, pero en la documentación encontrarás más detalle para temas más específicos como: eventos sobre Tasks/Events, Person Accounts, Users, como detectar diferencias y transmitirlas, etc.

La sencillez de uso que nos proporciona Salesforce para este mecanismo, es realmente sorprendente, y simplifica los esfuerzos y costes a los que nos enfrentaríamos si fuera otra la plataforma. La activación mediante un sólo click, el uso de un protocolo conocido como CometD, y disponer de varias alternativas de obtención de los eventos, son realmente útiles.

Por pedir, pediría que Process Builder pudiera procesar estos eventos y así los administradores y usuarios avanzados sin conocimientos de programación podrían construir flujos aún más potentes.

Finalmente, queda pendiente por parte de Salesforce que CDC llegue a todos los objetos Standard para completar el producto y el coste de licenciamiento a partir del paquete básico.

Repositorio con el código

Este es el repositorio donde puedes encontrar el código que he generado para esta entrada, encontrarás el trigger y el componente lightning que he creado que hace uso de empApi.
– Repositorio de código: [https://bitbucket.org/estevegraells/change-data-capture/]
(https://bitbucket.org/estevegraells/change-data-capture/)

Enlaces interesantes

Videos:
Change Data Capture Video Oficial
Synchronize Data & Orchestrate Workflows in Real-Time with Change Data Capture
Synchronize Data to Postgres with Change Data Capture Final
Advanced Logging Patterns With Platform Events
External Objects Change Data Capture

Espero que te sea de ayuda.