Compensação
A compensação no Windows Workflow Foundation (WF) é o mecanismo pelo qual o trabalho concluído anteriormente pode ser desfeito ou compensado (seguindo a lógica definida pelo aplicativo) quando ocorre uma falha subsequente. Esta seção descreve como usar a compensação em fluxos de trabalho.
Compensação vs. Transações
Uma transação permite combinar várias operações em uma única unidade de trabalho. O uso de uma transação dá ao seu aplicativo a capacidade de anular (reverter) todas as alterações executadas a partir da transação se ocorrerem erros durante qualquer parte do processo de transação. No entanto, o uso de transações pode não ser apropriado se o trabalho for de longa duração. Por exemplo, um aplicativo de planejamento de viagens é implementado como um fluxo de trabalho. As etapas do fluxo de trabalho podem consistir em reservar um voo, aguardar a aprovação do gerente e, em seguida, pagar pelo voo. Este processo pode demorar muitos dias e não é prático que as etapas de reserva e pagamento do voo participem na mesma transação. Em um cenário como este, a compensação pode ser usada para desfazer a etapa de reserva do fluxo de trabalho se houver uma falha posteriormente no processamento.
Nota
Este tópico aborda a remuneração em fluxos de trabalho. Para obter mais informações sobre transações em fluxos de trabalho, consulte Transações e TransactionScope. Para obter mais informações sobre transações, consulte System.Transactions e System.Transactions.Transaction.
Usando CompensableActivity
CompensableActivity é a principal atividade de compensação em WF. Quaisquer atividades que executem trabalho que possa precisar ser compensado são colocadas no Body de um CompensableActivity. Neste exemplo, a etapa de reserva da compra de um voo é colocada no Body de a CompensableActivity e o cancelamento da reserva é colocado no CompensationHandler. Imediatamente após o CompensableActivity fluxo de trabalho são duas atividades que aguardam a aprovação do gerente e, em seguida, concluem a etapa de compra do voo. Se uma condição de erro fizer com que o fluxo de trabalho seja cancelado após a CompensableActivity conclusão bem-sucedida, as atividades no CompensationHandler manipulador serão agendadas e o voo será cancelado.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new ManagerApproval(),
new PurchaseFlight()
}
};
O exemplo a seguir é o fluxo de trabalho em 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>
Quando o fluxo de trabalho é invocado, a saída a seguir é exibida no console.
ReserveFlight: O bilhete é reservado.ManagerApproval: aprovação do gerente recebida.PurchaseFlight: O bilhete é comprado.Fluxo de trabalho concluído com êxito com status: Fechado.
Nota
As atividades de exemplo neste tópico, como ReserveFlight
exibir seu nome e finalidade para o console para ajudar a ilustrar a ordem em que as atividades são executadas quando a compensação ocorre.
Compensação de fluxo de trabalho padrão
Por padrão, se o fluxo de trabalho for cancelado, a lógica de compensação será executada para qualquer atividade compensável que tenha sido concluída com êxito e ainda não tenha sido confirmada ou compensada.
Nota
Quando a CompensableActivity é confirmada, a compensação pela atividade não pode mais ser invocada. O processo de confirmação é descrito mais adiante nesta seção.
Neste exemplo, uma exceção é lançada depois que o voo é reservado, mas antes da etapa de aprovação do gerente.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new SimulatedErrorCondition(),
new ManagerApproval(),
new PurchaseFlight()
}
};
Este exemplo é o fluxo de trabalho em 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();
Quando o fluxo de trabalho é invocado, a exceção de condição de erro simulada é manipulada pelo aplicativo host no OnUnhandledException, o fluxo de trabalho é cancelado e a lógica de compensação é invocada.
ReserveFlight: O bilhete é reservado.SimulatedErrorCondition: lançando um ApplicationException.Workflow Unhandled Exception:System.ApplicationException: Condição de erro simulada no fluxo de trabalho.CancelFlight: O bilhete é cancelado.Fluxo de trabalho concluído com êxito com status: Cancelado.
Cancelamento e Atividade Compensatória
Se as atividades no Body de um CompensableActivity não foram concluídas e a atividade é cancelada, as atividades no CancellationHandler são executadas.
Nota
O CancellationHandler só é invocado se as atividades no Body do CompensableActivity não foram concluídas e a atividade é cancelada. O CompensationHandler só é executado se as atividades no Body âmbito do tiverem sido concluídas com sucesso e a CompensableActivity compensação for posteriormente invocada sobre a atividade.
O CancellationHandler fluxo de trabalho dá aos autores a oportunidade de fornecer qualquer lógica de cancelamento apropriada. No exemplo a seguir, uma exceção é lançada durante a execução do e, em Bodyseguida, o CancellationHandler é invocado.
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 exemplo é o fluxo de trabalho em 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>
Quando o fluxo de trabalho é invocado, a exceção de condição de erro simulada é manipulada pelo aplicativo host em OnUnhandledException, o fluxo de trabalho é cancelado e a lógica de cancelamento do CompensableActivity é invocada. Neste exemplo, a lógica de compensação e a lógica de cancelamento têm objetivos diferentes. Se o Body concluído com sucesso, isso significa que o cartão de crédito foi cobrado e o voo reservado, então a compensação deve desfazer ambas as etapas. (Neste exemplo, cancelar o voo cancela automaticamente as cobranças do cartão de crédito.) No entanto, se o CompensableActivity for cancelado, isso significa que o Body não foi concluído e, portanto, a lógica do precisa ser capaz de CancellationHandler determinar como lidar melhor com o cancelamento. Neste exemplo, o CancellationHandler cancela a cobrança do cartão de crédito, mas como ReserveFlight
foi a última atividade no Body, ele não tenta cancelar o voo. Uma vez que ReserveFlight
foi a última atividade no Body, se tivesse sido concluída com sucesso, então o Body teria sido concluído e nenhum cancelamento seria possível.
ChargeCreditCard: Cobrar cartão de crédito para o voo.SimulatedErrorCondition: lançando um ApplicationException.Workflow Unhandled Exception:System.ApplicationException: Condição de erro simulada no fluxo de trabalho.CancelCreditCard: Cancele cobranças de cartão de crédito.Fluxo de trabalho concluído com êxito com status: Cancelado. Para obter mais informações sobre cancelamento, consulte Cancelamento.
Compensação explícita usando a atividade de compensação
Na secção anterior, a indemnização implícita foi abrangida. A compensação implícita pode ser apropriada para cenários simples, mas se for necessário um controle mais explícito sobre o agendamento do tratamento da compensação, a Compensate atividade pode ser usada. Para iniciar o processo de compensação com a Compensate atividade, utiliza-se a CompensationToken compensação para a CompensableActivity qual a compensação é desejada. A Compensate atividade pode ser usada para iniciar a compensação em qualquer conclusão CompensableActivity que não tenha sido confirmada ou compensada. Por exemplo, uma Compensate atividade pode ser usada na Catches seção de uma TryCatch atividade ou a qualquer momento após a CompensableActivity conclusão. Neste exemplo, a Compensate atividade é usada na Catches seção de uma TryCatch atividade para reverter a ação do 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 exemplo é o fluxo de trabalho em 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>
Quando o fluxo de trabalho é invocado, a saída a seguir é exibida no console.
ReserveFlight: O bilhete é reservado.SimulatedErrorCondition: lançando um ApplicationException.CancelFlight: O bilhete é cancelado.Fluxo de trabalho concluído com êxito com status: Fechado.
Confirmação da Compensação
Por defeito, as atividades compensáveis podem ser compensadas a qualquer momento após a sua conclusão. Em alguns cenários, isso pode não ser apropriado. No exemplo anterior, a compensação para reservar o bilhete era cancelar a reserva. No entanto, após a conclusão do voo, esta etapa de compensação deixa de ser válida. A confirmação da atividade compensável invoca a atividade especificada pelo ConfirmationHandler. Um uso possível para isso é permitir que todos os recursos necessários para realizar a compensação sejam liberados. Depois que uma atividade compensável é confirmada, não é possível que ela seja compensada e, se isso for tentado, uma InvalidOperationException exceção é lançada. Quando um fluxo de trabalho é concluído com êxito, todas as atividades compensáveis não confirmadas e não compensadas concluídas com êxito são confirmadas na ordem inversa de conclusão. Neste exemplo, o voo é reservado, comprado e concluído e, em seguida, a atividade compensável é confirmada. Para confirmar um CompensableActivity, use a Confirm atividade e especifique o CompensationTokenCompensableActivity do 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 exemplo é o fluxo de trabalho em 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>
Quando o fluxo de trabalho é invocado, a saída a seguir é exibida no console.
ReserveFlight: O bilhete é reservado.ManagerApproval: aprovação do gerente recebida.PurchaseFlight: O bilhete é comprado.TakeFlight: O voo está concluído.ConfirmFlight: O voo foi realizado, sem possibilidade de compensação.Fluxo de trabalho concluído com êxito com status: Fechado.
Atividades de compensação de aninhamento
A CompensableActivity pode ser colocado na Body seção de outro CompensableActivity. A CompensableActivity não pode ser colocado em um manipulador de outro CompensableActivity. É responsabilidade de um pai CompensableActivity garantir que, quando for cancelado, confirmado ou compensado, todas as atividades compensáveis da criança que tenham sido concluídas com sucesso e ainda não tenham sido confirmadas ou compensadas devem ser confirmadas ou compensadas antes que o pai complete o cancelamento, confirmação ou compensação. Se isso não for modelado explicitamente, o pai CompensableActivity compensará implicitamente as atividades compensáveis da criança se o pai recebeu o sinal de cancelamento ou compensação. Se o pai recebeu o sinal de confirmação, o pai confirmará implicitamente as atividades compensáveis da criança. Se a lógica para lidar com cancelamento, confirmação ou compensação for explicitamente modelada no manipulador do pai CompensableActivity, qualquer criança não tratada explicitamente será implicitamente confirmada.