Compensation
La compensation dans Windows Workflow Foundation (WF) est le mécanisme qui permet d’annuler ou de compenser (suivant la logique définie par l’application) un travail terminé précédemment quand un échec se produit par la suite. Cette section décrit comment utiliser une compensation dans les flux de travail.
Compensation contre Transactions
Une transaction vous permet de combiner plusieurs opérations en une seule unité de travail. L’utilisation d’une transaction permet à votre application d’annuler (restaurer) toute modification exécutée depuis une transaction en cas d’erreur au cours du processus de transaction. Toutefois, l’utilisation de transactions peut ne pas convenir dans le cas d’un travail de longue durée. Par exemple, une application de planification de voyage est implémentée en tant que flux de travail. Les étapes du flux de travail peuvent porter sur la réservation d'un vol, l'attente de l'approbation du gestionnaire et le paiement du vol. Ce processus pourrait prendre de nombreux jours et ne s’avère pas pratique pour que les étapes de réservation et de paiement du vol puissent participer à la même transaction. Dans un tel scénario, la compensation pourrait être utilisée pour annuler l'étape de réservation du flux de travail en cas d'erreur ultérieure lors du traitement.
Notes
Cette rubrique couvre la compensation dans les workflows. Pour plus d’informations sur les transactions dans les workflows, consultez Transactions et TransactionScope. Pour plus d’informations sur les transactions, consultez System.Transactions et System.Transactions.Transaction.
Utilisation de CompensableActivity
CompensableActivity est l’activité de compensation principale dans WF. Toutes les activités qui effectuent un travail pouvant nécessiter d'être compensé sont placées dans le Body d'un CompensableActivity. Dans cet exemple, l'étape de réservation de l'achat d'un vol est placée dans le Body d'un CompensableActivity et l'annulation de la réservation est placée dans le CompensationHandler. Juste après le CompensableActivity dans le workflow, deux activités doivent être exécutées, d'une part l'approbation du gestionnaire, d'autre part l'achat du vol. Si une condition d'erreur entraîne l'annulation du workflow une fois CompensableActivity correctement terminé, les activités du gestionnaire CompensationHandler sont planifiées et le vol est annulé.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new ManagerApproval(),
new PurchaseFlight()
}
};
L'exemple suivant est le workflow 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>
Lorsque le workflow est appelé, la sortie suivante s'affiche sur la console.
ReserveFlight: Ticket is reserved.ManagerApproval: Manager approval received.PurchaseFlight: Ticket is purchased.Workflow completed successfully with status: Closed.
Notes
Les exemples d'activités de cette rubrique, telles que ReserveFlight
, affichent leur nom et leur but dans la console pour faciliter l'illustration de l'ordre dans lequel les activités sont exécutées lorsque la compensation se produit.
Compensation de workflow par défaut
Par défaut, si le workflow est annulé, la logique de compensation est exécutée pour toute activité compensable ayant abouti et n'ayant pas encore été confirmée ou compensée.
Notes
Quand un CompensableActivity est confirmé, la compensation de l’activité ne peut plus être appelée. Le processus de confirmation est décrit plus loin dans cette section.
Dans cet exemple, une exception est levée après que le vol a été réservé mais avant l'étape d'approbation par le gestionnaire.
Activity wf = new Sequence()
{
Activities =
{
new CompensableActivity
{
Body = new ReserveFlight(),
CompensationHandler = new CancelFlight()
},
new SimulatedErrorCondition(),
new ManagerApproval(),
new PurchaseFlight()
}
};
Cet exemple est le workflow 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();
Lorsque le workflow est appelé, l'exception de condition d'erreur simulée est gérée par l'application hôte dans OnUnhandledException, le workflow est annulé, et la logique de compensation est appelée.
ReserveFlight: Ticket is reserved.SimulatedErrorCondition: Throwing an ApplicationException.Workflow Unhandled Exception:System.ApplicationException: Simulated error condition in the workflow.CancelFlight: Ticket is canceled.Workflow completed successfully with status: Canceled.
Annulation et CompensableActivity
Si les activités dans le Body d'un CompensableActivity ne se sont pas terminées et que l'activité est annulée, les activités dans le CancellationHandler sont exécutées.
Notes
CancellationHandler est appelé uniquement si les activités dans le Body de CompensableActivity ne se sont pas terminées et que l'activité est annulée. CompensationHandler est exécuté uniquement si les activités dans le Body de CompensableActivity se sont terminées avec succès et que la compensation est, par la suite, appelée sur l'activité.
CancellationHandler donne aux auteurs de workflow la possibilité de fournir n’importe quelle logique d’annulation appropriée. Dans l'exemple suivant, une exception est levée pendant l'exécution de Body, puis CancellationHandler est appelé.
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()
}
};
Cet exemple est le workflow 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>
Lorsque le workflow est appelé, l'exception de condition d'erreur simulée est gérée par l'application hôte dans OnUnhandledException, le workflow est annulé, et la logique d'annulation de CompensableActivity est appelée. Dans cet exemple, la logique de compensation et la logique d'annulation ont des objectifs différents. Si Body se termine avec succès, cela signifie que la carte de crédit a été facturée et le vol réservé, donc la compensation doit annuler les deux étapes. (Dans cet exemple, l’annulation du vol annule automatiquement les frais sur la carte de crédit.) Toutefois, si CompensableActivity est annulé, cela signifie que Body ne s’est pas terminé et que la logique de CancellationHandler doit être en mesure de déterminer comment traiter l’annulation au mieux. Dans cet exemple, le CancellationHandler annule la facturation de la carte de crédit, mais comme ReserveFlight
était la dernière activité dans Body, il n'essaie pas d'annuler le vol. Comme ReserveFlight
était la dernière activité dans le Body, si elle s'est terminée avec succès, Body s'est terminé et aucune annulation n'est possible.
ChargeCreditCard: Charge credit card for flight.SimulatedErrorCondition: Throwing an ApplicationException.Workflow Unhandled Exception:System.ApplicationException: Simulated error condition in the workflow.CancelCreditCard: Cancel credit card charges.Workflow completed successfully with status: Canceled. Pour plus d’informations sur l’annulation, consultez Annulation.
Compensation explicite à l'aide de l'activité Compensate
Dans la section précédente, la compensation implicite a été couverte. La compensation implicite peut convenir à des scénarios simples, mais si un contrôle explicite supplémentaire est requis sur la planification de compensation, la gestion de l'activité Compensate peut être utilisée. Pour initialiser le processus de compensation avec l'activité Compensate, le CompensationToken du CompensableActivity pour lequel la compensation est désirée est utilisé. L'activité Compensate peut être utilisée pour initialiser la compensation sur tout CompensableActivity ayant abouti et qui n'a pas été confirmé ou compensé. Par exemple, une activité Compensate pourrait être utilisée dans la section Catches d'une activité TryCatch, ou à n'importe quel moment après que le CompensableActivity a abouti. Dans cet exemple, l'activité Compensate est utilisée dans la propriété Catches d'une activité TryCatch pour inverser l'action du 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
}
}
}
}
};
Cet exemple est le workflow 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>
Lorsque le workflow est appelé, la sortie suivante s'affiche sur la console.
ReserveFlight: Ticket is reserved.SimulatedErrorCondition: Throwing an ApplicationException.CancelFlight: Ticket is canceled.Workflow completed successfully with status: Closed.
Confirmer la compensation
Par défaut, les activités compensables peuvent être compensées à n'importe quel moment, à condition qu'elles soient achevées. Toutefois, dans certains cas cela peut ne pas être suffisant. Dans l'exemple précédent la compensation destinée à réserver le ticket devait permettre d'annuler la réservation. Toutefois, une fois le vol effectué cette étape de compensation n'est plus valide. La confirmation de l'activité compensable appelle l'activité spécifiée dans la section ConfirmationHandler. Une utilisation possible de cela est de permettre la libération de toutes les ressources qui sont nécessaires pour effectuer la compensation. Lorsqu'une activité compensable est confirmée, il n'est pas possible de la dédommager, et si vous tentez cette opération, une exception InvalidOperationException est levée. Lorsqu'un flux de travail aboutit, toutes les activités non confirmées et non dédommagées ayant abouti sont confirmées dans l'ordre inverse d'achèvement. Dans cet exemple le vol est réservé, acheté et finalisé, puis, l'activité compensable est confirmée. Pour confirmer un CompensableActivity, utilisez l'activité Confirm et spécifiez le CompensationToken du CompensableActivity à confirmer.
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
}
}
};
Cet exemple est le workflow 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>
Lorsque le workflow est appelé, la sortie suivante s'affiche sur la console.
ReserveFlight: Ticket is reserved.ManagerApproval: Manager approval received.PurchaseFlight: Ticket is purchased.TakeFlight: Flight is completed.ConfirmFlight: Flight has been taken, no compensation possible.Workflow completed successfully with status: Closed.
Imbrication d'activités de compensation
Un CompensableActivity peut être placé dans la section Body d'un autre CompensableActivity. CompensableActivity ne peut pas être placé dans un gestionnaire d'un autre CompensableActivity. Il est de la responsabilité d'un CompensableActivity parent de garantir que lorsqu'il est annulé, confirmé, ou compensé, toutes les activités enfants compensables terminées avec succès et qui n'ont pas encore été confirmées ou compensées doivent être confirmées ou compensées avant que le parent complète l'annulation, la confirmation ou la compensation. Si cela n'est pas modélisé explicitement, le CompensableActivity parent compensera implicitement les activités compensables enfants si le parent a reçu le signal d'annulation ou de compensation. Si le parent a reçu le signal de confirmation, il confirme implicitement les activités compensables enfants. Si la logique pour gérer l'annulation, la confirmation ou la compensation est explicitement modélisée dans le gestionnaire du CompensableActivityparent, les enfants qui ne sont pas gérés explicitement seront confirmés implicitement.