Compensación
La compensación en Windows Workflow Foundation (WF) es el mecanismo por el que se puede deshacer o compensar el trabajo previamente finalizado (siguiendo la lógica definida por la aplicación) cuando se produce un error posterior. Esta sección describe cómo usar la compensación en los flujos de trabajo.
Compensación en comparación con transacciones
Las transacciones permiten combinar varias operaciones en una sola unidad de trabajo. El uso de una transacción le da a la aplicación la posibilidad de anular (revertir) todos los cambios ejecutados desde dentro de la transacción si se produce algún error en algún momento del proceso de transacción. Sin embargo, el uso de las transacciones puede que no sea adecuado si el trabajo se está ejecutando mucho tiempo. Por ejemplo, se implementa una aplicación que planea un viaje como un flujo de trabajo. Los pasos del flujo de trabajo pueden consistir en la reserva de un vuelo, la espera de la aprobación del administrador y, a continuación, el pago del vuelo. Este proceso podría tardar muchos días y no es práctico que los pasos de reservar y pagar el vuelo formen parte de la misma transacción. En un escenario como este, se podría usar la compensación para deshacer el paso de la reserva del flujo de trabajo si se produce un error posterior durante el procesamiento.
Nota
Este tema trata la compensación en flujos de trabajo. Para más información sobre las transacciones en los flujos de trabajo, consulte Transacciones y TransactionScope. Para más información acerca de las transacciones, consulte System.Transactions y System.Transactions.Transaction.
Usar CompensableActivity
CompensableActivity es la actividad de compensación principal en WF. Cualquier actividad que realice un trabajo que cabe la posibilidad de que deba compensarse se coloca en la propiedad Body de la clase CompensableActivity. En este ejemplo, el paso de la reserva de compra de un vuelo se encuentra en la propiedad Body de una clase CompensableActivity y la cancelación de la reserva está en la propiedad CompensationHandler. Justo después de CompensableActivity en el flujo de trabajo hay dos actividades que esperan la aprobación del administrador y, a continuación, se completa la compra del vuelo. Si una condición de error provoca la cancelación del flujo de trabajo después de que la clase CompensableActivity se haya completado correctamente, las actividades del controlador CompensationHandler se programan y el vuelo se cancela.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new ManagerApproval(),
new PurchaseFlight()
}
};
El siguiente ejemplo es el flujo de trabajo en XAML.
<Sequence
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<CompensableActivity>
<CompensableActivity.Result>
<OutArgument
x:TypeArguments="CompensationToken" />
</CompensableActivity.Result>
<CompensableActivity.CompensationHandler>
<c:CancelFlight />
</CompensableActivity.CompensationHandler>
<c:ReserveFlight />
</CompensableActivity>
<c:ManagerApproval />
<c:PurchaseFlight />
</Sequence>
Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado.
ReserveFlight: se reserva el vale.ManagerApproval: aprobación del administrador recibida.PurchaseFlight: se compra el vale.Flujo de trabajo completado correctamente con el estado: cerrado.
Nota
Las actividades de ejemplo de este tema como ReserveFlight
muestran su nombre y propósito a la consola para ayudar a mostrar el orden en el que se ejecutan las actividades cuando se produce la compensación.
Compensación predeterminada del flujo de trabajo
De forma predeterminada, si se cancela el flujo de trabajo, se ejecuta la lógica de compensación para cualquier actividad compensable que se haya completado y que aún no se haya confirmado o compensado.
Nota
Cuando una clase CompensableActivity se confirma, ya no se podrá invocar la compensación para la actividad. El proceso de confirmación se describe más adelante en esta sección.
En este ejemplo, se inicia una excepción después de que se haya reservado el vuelo, aunque antes de que lo apruebe el administrador.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new SimulatedErrorCondition(),
new ManagerApproval(),
new PurchaseFlight()
}
};
Este ejemplo es el flujo de trabajo en XAML.
<Sequence
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<CompensableActivity>
<CompensableActivity.Result>
<OutArgument
x:TypeArguments="CompensationToken" />
</CompensableActivity.Result>
<CompensableActivity.CompensationHandler>
<c:CancelFlight />
</CompensableActivity.CompensationHandler>
<c:ReserveFlight />
</CompensableActivity>
<c:SimulatedErrorCondition />
<c:ManagerApproval />
<c:PurchaseFlight />
</Sequence>
AutoResetEvent syncEvent = new AutoResetEvent(false);
WorkflowApplication wfApp = new WorkflowApplication(wf);
wfApp.Completed = delegate(WorkflowApplicationCompletedEventArgs e)
{
if (e.TerminationException != null)
{
Console.WriteLine("Workflow terminated with exception:\n{0}: {1}",
e.TerminationException.GetType().FullName,
e.TerminationException.Message);
}
else
{
Console.WriteLine("Workflow completed successfully with status: {0}.",
e.CompletionState);
}
syncEvent.Set();
};
wfApp.OnUnhandledException = delegate(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Console.WriteLine("Workflow Unhandled Exception:\n{0}: {1}",
e.UnhandledException.GetType().FullName,
e.UnhandledException.Message);
return UnhandledExceptionAction.Cancel;
};
wfApp.Run();
syncEvent.WaitOne();
Cuando se invoca el flujo de trabajo, la aplicación host administra la excepción de condiciones de error simulada en OnUnhandledException, el flujo de trabajo se cancela y se invoca la lógica de compensación.
ReserveFlight: se reserva el vale.SimulatedErrorCondition: inicia una ApplicationException.Excepción no controlada del flujo de trabajo:System.ApplicationException: condición de error simulada en el flujo de trabajo.CancelFlight: se cancela el vale.Flujo de trabajo completado correctamente con el estado: cancelado.
Cancelación y CompensableActivity
Si las actividades de la propiedad Body de una clase CompensableActivity no se han completado y la actividad se cancela, se ejecutan las actividades de la propiedad CancellationHandler.
Nota
La propiedad CancellationHandler solo se invoca si las actividades de la propiedad Body de la clase CompensableActivity no se han completado y se cancela la actividad. La propiedad CompensationHandler solo se ejecuta si las actividades de la propiedad Body de la clase CompensableActivity se han completado correctamente y por lo tanto se invoca la compensación en la actividad.
La propiedad CancellationHandler permite a los autores del flujo de trabajo proporcionar una lógica de cancelación adecuada. En el siguiente ejemplo se produce una excepción durante la ejecución de la clase Body y, a continuación, se invoca la propiedad CancellationHandler.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new Sequence
{
Activities =
{
new ChargeCreditCard(),
new SimulatedErrorCondition(),
new ReserveFlight()
}
},
CompensationHandler = new CancelFlight(),
CancellationHandler = new CancelCreditCard()
},
new ManagerApproval(),
new PurchaseFlight()
}
};
Este ejemplo es el flujo de trabajo en XAML.
<Sequence
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<CompensableActivity>
<CompensableActivity.Result>
<OutArgument
x:TypeArguments="CompensationToken" />
</CompensableActivity.Result>
<Sequence>
<c:ChargeCreditCard />
<c:SimulatedErrorCondition />
<c:ReserveFlight />
</Sequence>
<CompensableActivity.CancellationHandler>
<c:CancelCreditCard />
</CompensableActivity.CancellationHandler>
<CompensableActivity.CompensationHandler>
<c:CancelFlight />
</CompensableActivity.CompensationHandler>
</CompensableActivity>
<c:ManagerApproval />
<c:PurchaseFlight />
</Sequence>
Cuando se invoca el flujo de trabajo, la aplicación host administra la excepción de condiciones de error simulada en OnUnhandledException, el flujo de trabajo se cancela y se invoca la lógica de cancelación de la clase CompensableActivity. En este ejemplo, la lógica de la compensación y la lógica de cancelación tienen distintos objetivos. Si Body se completó correctamente, significa que la tarjeta de crédito se cargó y el vuelo se reservó, por lo que la compensación debe deshacer ambos pasos. (En este ejemplo, la cancelación del vuelo cancela automáticamente los cargos de la tarjeta de crédito). Sin embargo, si CompensableActivity se cancela, significa que Body no se ha completado y por tanto la lógica de CancellationHandler necesita poder determinar cómo controlar mejor la cancelación. En este ejemplo, CancellationHandler cancela el cargo de la tarjeta de crédito, pero como ReserveFlight
era la última actividad de Body, no intenta cancelar el vuelo. Puesto que ReserveFlight
era la última actividad de Body, si se hubiera realizado correctamente Body se habría completado y no sería posible la cancelación.
ChargeCreditCard: cargar tarjeta de crédito para el vuelo.SimulatedErrorCondition: inicia una ApplicationException.Excepción no controlada del flujo de trabajo:System.ApplicationException: condición de error simulada en el flujo de trabajo.CancelCreditCard: cancelación de cargos de tarjeta de crédito.Flujo de trabajo completado correctamente con el estado: Cancelado. Para más información sobre la cancelación, consulte Cancelación.
Compensación explícita usando la actividad Compensate
En la sección anterior, se describió la compensación implícita. La compensación implícita puede ser adecuada para escenarios simples, pero si es necesario un mayor control sobre la programación de la administración de la compensación, se podrá usar la actividad Compensate. Para iniciar el proceso de compensación con la actividad Compensate, se usa la clase CompensationToken de la clase CompensableActivity para la que se desea la compensación. Se puede usar la actividad Compensate para iniciar la compensación en cualquier CompensableActivity completada que no se haya confirmado o compensado. Por ejemplo, se puede usar una actividad Compensate en la sección Catches de una actividad TryCatch o cuando desee después de que CompensableActivity se haya completado. En este ejemplo, la actividad Compensate se usa en la sección Catches de una actividad TryCatch para invertir la acción de CompensableActivity.
Variable<CompensationToken> token1 = new Variable<CompensationToken>
{
Name = "token1",
};
Activity wf = new TryCatch()
{
Variables =
{
token1
},
Try = new Sequence
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight(),
ConfirmationHandler = new ConfirmFlight(),
Result = token1
},
new SimulatedErrorCondition(),
new ManagerApproval(),
new PurchaseFlight()
}
},
Catches =
{
new Catch<ApplicationException>()
{
Action = new ActivityAction<ApplicationException>()
{
Handler = new Compensate()
{
Target = token1
}
}
}
}
};
Este ejemplo es el flujo de trabajo en XAML.
<TryCatch
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TryCatch.Variables>
<Variable
x:TypeArguments="CompensationToken"
x:Name="__ReferenceID0"
Name="token1" />
</TryCatch.Variables>
<TryCatch.Try>
<Sequence>
<CompensableActivity>
<CompensableActivity.Result>
<OutArgument
x:TypeArguments="CompensationToken">
<VariableReference
x:TypeArguments="CompensationToken"
Variable="{x:Reference __ReferenceID0}">
<VariableReference.Result>
<OutArgument
x:TypeArguments="Location(CompensationToken)" />
</VariableReference.Result>
</VariableReference>
</OutArgument>
</CompensableActivity.Result>
<CompensableActivity.CompensationHandler>
<c:CancelFlight />
</CompensableActivity.CompensationHandler>
<CompensableActivity.ConfirmationHandler>
<c:ConfirmFlight />
</CompensableActivity.ConfirmationHandler>
<c:ReserveFlight />
</CompensableActivity>
<c:SimulatedErrorCondition />
<c:ManagerApproval />
<c:PurchaseFlight />
</Sequence>
</TryCatch.Try>
<TryCatch.Catches>
<Catch
x:TypeArguments="s:ApplicationException">
<ActivityAction
x:TypeArguments="s:ApplicationException">
<Compensate>
<Compensate.Target>
<InArgument
x:TypeArguments="CompensationToken">
<VariableValue
x:TypeArguments="CompensationToken"
Variable="{x:Reference __ReferenceID0}">
<VariableValue.Result>
<OutArgument
x:TypeArguments="CompensationToken" />
</VariableValue.Result>
</VariableValue>
</InArgument>
</Compensate.Target>
</Compensate>
</ActivityAction>
</Catch>
</TryCatch.Catches>
</TryCatch>
Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado.
ReserveFlight: se reserva el vale.SimulatedErrorCondition: inicia una ApplicationException.CancelFlight: se cancela el vale.Flujo de trabajo completado correctamente con el estado: cerrado.
Confirmar compensación
De forma predeterminada, las actividades pueden compensarse en cualquier momento después de que se hayan completado. Sin embargo, en algunos escenarios es posible que no convenga hacerlo. En el ejemplo anterior la compensación para reservar el vale fue cancelar la reserva. No obstante, una vez que se haya completado el vuelo, la compensación dejará de tener validez. Al confirmar la actividad compensable, se invoca la actividad especificada por la propiedad ConfirmationHandler. Un posible uso de ello es permitir cualquier recurso que sea necesario para realizar la compensación que se va a liberar. Una vez confirmada la actividad compensable, no es posible compensarla y, si se intentara, se iniciaría una excepción InvalidOperationException. Cuando un flujo de trabajo se completa, todas las actividades compensables no confirmadas y no compensadas que se hayan completado se confirmarán en el orden inverso de la finalización. En este ejemplo, el vuelo se reserva, se compra y se completa y, a continuación, se confirma la actividad compensable. Para confirmar CompensableActivity, use la actividad Confirm y especifique la clase CompensationToken de la clase CompensableActivity para confirmar.
Variable<CompensationToken> token1 = new Variable<CompensationToken>
{
Name = "token1",
};
Activity wf = new Sequence()
{
Variables =
{
token1
},
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight(),
ConfirmationHandler = new ConfirmFlight(),
Result = token1
},
new ManagerApproval(),
new PurchaseFlight(),
new TakeFlight(),
new Confirm()
{
Target = token1
}
}
};
Este ejemplo es el flujo de trabajo en XAML.
<Sequence
xmlns="http://schemas.microsoft.com/netfx/2009/xaml/activities"
xmlns:c="clr-namespace:CompensationExample;assembly=CompensationExample"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Sequence.Variables>
<x:Reference>__ReferenceID0</x:Reference>
</Sequence.Variables>
<CompensableActivity>
<CompensableActivity.Result>
<OutArgument
x:TypeArguments="CompensationToken">
<VariableReference
x:TypeArguments="CompensationToken">
<VariableReference.Result>
<OutArgument
x:TypeArguments="Location(CompensationToken)" />
</VariableReference.Result>
<VariableReference.Variable>
<Variable
x:TypeArguments="CompensationToken"
x:Name="__ReferenceID0"
Name="token1" />
</VariableReference.Variable>
</VariableReference>
</OutArgument>
</CompensableActivity.Result>
<CompensableActivity.CompensationHandler>
<c:CancelFlight />
</CompensableActivity.CompensationHandler>
<CompensableActivity.ConfirmationHandler>
<c:ConfirmFlight />
</CompensableActivity.ConfirmationHandler>
<c:ReserveFlight />
</CompensableActivity>
<c:ManagerApproval />
<c:PurchaseFlight />
<c:TakeFlight />
<Confirm>
<Confirm.Target>
<InArgument
x:TypeArguments="CompensationToken">
<VariableValue
x:TypeArguments="CompensationToken"
Variable="{x:Reference __ReferenceID0}">
<VariableValue.Result>
<OutArgument
x:TypeArguments="CompensationToken" />
</VariableValue.Result>
</VariableValue>
</InArgument>
</Confirm.Target>
</Confirm>
</Sequence>
Cuando se invoca el flujo de trabajo, en la consola se muestra el siguiente resultado.
ReserveFlight: el vale se reserva.ManagerApproval: aprobación del administrador recibida.PurchaseFlight: se compra el vale.TakeFlight: se ha completado el vuelo.ConfirmFlight: se ha tomado el vuelo, no hay una posible una compensación.Flujo de trabajo completado correctamente con el estado: cerrado.
Anidar las actividades de compensación
CompensableActivity se puede colocar en la sección Body de otra CompensableActivity. CompensableActivity no se puede colocar en un controlador de otra CompensableActivity. Es responsabilidad de un elemento primario CompensableActivity garantizar que cuando se cancela, confirma o compensa, todas las actividades compensables secundarias que se hayan completado correctamente y que no se hayan confirmado o compensado todavía se deben confirmar o compensar antes de que el elemento primario complete la cancelación, la confirmación o la compensación. Si esto no se modeló explícitamente, la CompensableActivity primaria compensará implícitamente las actividades compensables secundarias si el elemento primario recibió la señal de cancelación o compensación. Si el elemento primario recibió la señal de confirmación, el elemento primario confirmará implícitamente las actividades compensables secundarias. Si la lógica para controlar la cancelación, confirmación o compensación se modela explícitamente en el controlador de la CompensableActivity primaria, cualquier elemento secundario no controlado explícitamente se confirmará implícitamente.