4.4 Transacciones y Rollback Automático

Entiende el comportamiento transaccional y el rollback automático

Las transacciones son uno de los conceptos más importantes y menos comprendidos del desarrollo de plugins. Saber cómo funciona el rollback automático te permite diseñar plugins que se comportan correctamente tanto cuando todo va bien como cuando algo falla.

Objetivos de aprendizaje

  • Entender qué stages participan en la transacción
  • Conocer el comportamiento del rollback automático
  • Identificar operaciones que no se pueden revertir
  • Diseñar plugins que manejan correctamente escenarios de fallo

Entendiendo las transacciones en Dataverse

Cuando un usuario guarda un registro en Dataverse, se inicia una transacción de base de datos. Esta transacción agrupa todas las operaciones relacionadas: la escritura del registro principal, los plugins que se ejecutan, las reglas de negocio que aplican.

Si cualquier parte de esta cadena falla, la transacción completa se revierte. Es como si nada hubiera pasado. El registro no se guarda, cualquier cosa que los plugins hayan modificado se deshace.

Pero no todos los plugins participan en esta transacción. El comportamiento varía según el stage y el modo de ejecución:

Pre-validation (Stage 10)

Pre-validation se ejecuta ANTES de que la transacción comience. Si lanzas una excepción aquí, no hay nada que revertir porque todavía no se ha escrito nada en la base de datos.

Esto lo hace ideal para validaciones rápidas: verificar formatos, rechazar valores inválidos y demás comprobaciones que no requieren consultar otros datos. Al estar fuera de la transacción, rechazar aquí es más eficiente.

Pre-operation (Stage 20)

Pre-operation está DENTRO de la transacción. Cuando tu código se ejecuta aquí, la transacción ya comenzó pero la operación principal aún no se ha completado.

Si lanzas una excepción en Pre-operation, se revierte todo lo que haya pasado hasta ese punto, incluyendo cambios hechos por otros plugins que se ejecutaron antes del tuyo.

Post-operation síncrono (Stage 40)

Post-operation síncrono también está dentro de la transacción. En este punto la operación principal ya se completó, pero la transacción sigue abierta.

Si tu plugin Post-operation falla, se revierte todo: la operación principal Y cualquier cosa que hayas hecho tú u otros plugins.

Post-operation asíncrono

Los plugins asíncronos se ejecutan FUERA de la transacción. Cuando tu código asíncrono se ejecuta, la transacción original ya se cerró, el registro ya está guardado en la base de datos.

Si tu plugin asíncrono falla, la operación original no se afecta. El registro sigue existiendo. Debes manejar la inconsistencia de otra forma.


El rollback en acción

Veamos un ejemplo concreto para entender el rollback:


// Plugin en Post-operation síncrono de Create de Account
public void Execute(IServiceProvider serviceProvider)
{
    var factory = (IOrganizationServiceFactory)
        serviceProvider.GetService(typeof(IOrganizationServiceFactory));
    var service = factory.CreateOrganizationService(null);
    var trace = (ITracingService)
        serviceProvider.GetService(typeof(ITracingService));
    
    // Crear una tarea asociada a la cuenta
    Entity tarea = new Entity("task");
    tarea["subject"] = "Dar seguimiento a nueva cuenta";
    tarea["regardingobjectid"] = new EntityReference("account", 
        ((Entity)((IPluginExecutionContext)serviceProvider
            .GetService(typeof(IPluginExecutionContext)))
            .InputParameters["Target"]).Id);
    
    Guid tareaId = service.Create(tarea);
    trace.Trace($"Tarea creada: {tareaId}");
    
    // Ahora algo falla...
    throw new InvalidPluginExecutionException(
        "Error intencional para demostrar rollback");
}

Cuando este plugin se ejecuta:

  1. El usuario intenta crear una cuenta
  2. La cuenta se inserta en la base de datos (dentro de la transacción)
  3. Nuestro plugin crea una tarea (también dentro de la transacción)
  4. Nuestro plugin lanza una excepción
  5. La transacción se revierte: la cuenta Y la tarea desaparecen
  6. El usuario ve el mensaje de error

Desde la perspectiva del usuario, es como si nada hubiera pasado. No hay cuenta huérfana ni tarea sin propósito.


Operaciones que no se pueden revertir

El rollback funciona para operaciones dentro de Dataverse. Pero hay cosas que no se pueden deshacer:

Llamadas HTTP a servicios externos: Si tu plugin llama a una API externa y luego falla, la API ya recibió la llamada. No hay forma de "deshacer" una petición HTTP.

Emails enviados: Si usas el servicio de correo de Dataverse para enviar un email, y luego el plugin falla, el email ya se envió.

Archivos escritos: Cualquier operación en sistemas de archivos externos o servicios de almacenamiento no se puede revertir.

Esto tiene implicaciones importantes para el diseño de tus plugins:


// PATRÓN PROBLEMÁTICO
public void Execute(IServiceProvider serviceProvider)
{
    // ¡PRIMERO llamamos a API externa!
    EnviarDatosASistemaExterno(datos);
    
    // Luego hacemos validaciones
    if (!EsValido(datos))
    {
        // Si esto falla, los datos YA están en el sistema externo
        throw new InvalidPluginExecutionException("Datos inválidos");
    }
}

// PATRÓN CORRECTO
public void Execute(IServiceProvider serviceProvider)
{
    // PRIMERO validamos todo
    if (!EsValido(datos))
    {
        throw new InvalidPluginExecutionException("Datos inválidos");
    }
    
    // Operaciones internas de Dataverse
    service.Create(registroRelacionado);
    
    // ÚLTIMO: operaciones irrecuperables
    try
    {
        EnviarDatosASistemaExterno(datos);
    }
    catch (Exception ex)
    {
        throw new InvalidPluginExecutionException(
            "Error sincronizando con sistema externo. Los cambios se han revertido.", ex);
    }
}

Diseñando para el fallo

Un buen diseño de plugins considera qué pasa cuando las cosas van mal:

Valida primero, actúa después. Todas las validaciones que pueden fallar deberían ejecutarse antes de hacer cualquier operación que no se pueda deshacer.

Las llamadas externas van al final. Si necesitas sincronizar con un sistema externo, hazlo como último paso. Así, si la sincronización falla, puedes revertir todo lo interno de Dataverse.

Considera el patrón de compensación. Para operaciones externas críticas, puedes implementar lógica de compensación: si la operación B falla después de que A tuvo éxito, llamas a un método para deshacer A.


// Patrón de compensación simple
string externalRecordId = null;

try
{
    // Paso 1: Crear en sistema externo
    externalRecordId = CrearEnSistemaExterno(datos);
    
    // Paso 2: Actualizar en Dataverse
    service.Update(registroLocal);
    
    // Paso 3: Algo más que puede fallar
    OperacionQUEPuedeFallar();
}
catch (Exception ex)
{
    // Si algo falló después de crear en el sistema externo, compensar
    if (externalRecordId != null)
    {
        try
        {
            EliminarEnSistemaExterno(externalRecordId);
        }
        catch
        {
            // Loguear pero no ocultar el error original
        }
    }
    
    throw new InvalidPluginExecutionException("Operación fallida", ex);
}

Plugins asíncronos y la ausencia de transacción

Los plugins asíncronos tienen un modelo completamente diferente. Cuando se ejecutan, la transacción original ya terminó. El registro que disparó el plugin ya existe (o ya fue eliminado, si era un Delete).

Si un plugin asíncrono falla:

  • La operación original no se afecta
  • El plugin se reintenta automáticamente hasta 3 veces
  • Si sigue fallando, queda marcado como "Failed" en System Jobs
  • Debes manejar la inconsistencia manualmente

Esto hace que los plugins asíncronos sean inadecuados para operaciones que DEBEN completarse junto con la operación principal. Son ideales para tareas secundarias que pueden fallar sin romper el flujo principal: notificaciones, sincronizaciones no críticas, actualización de cachés.


Puntos clave

  • Pre-operation y Post-operation síncrono participan en la transacción
  • Si un plugin síncrono falla, toda la transacción se revierte automáticamente
  • Pre-validation está fuera de la transacción, es eficiente para validaciones rápidas
  • Llamadas HTTP, emails, y acciones externas NO se pueden revertir
  • Valida primero, haz operaciones irreversibles al final
  • Plugins asíncronos no participan en la transacción original

Para profundizar

Inicia sesión e inscríbete para guardar tu progreso.
En este curso
¿Te ha resultado útil?