다음을 통해 공유


보상

Windows WF(Workflow Foundation)의 보정은 이후에 실패가 발생할 경우 애플리케이션에서 정의한 논리에 따라 이전에 완료된 작업을 취소하거나 보정하는 메커니즘입니다. 이 단원에서는 워크플로에서 보정을 사용하는 방법에 대해 설명합니다.

보정과 트랜잭션 비교

트랜잭션을 사용하여 여러 작업을 하나의 작업 단위로 결합할 수 있습니다. 트랜잭션을 사용하면 애플리케이션은 트랜잭션 처리의 어떠한 부분에서도 오류가 발생하지 않은 경우 트랜잭션 내부에서 실행된 모든 변경 내용을 취소(롤백)할 수 있습니다. 그러나 작업이 오래 실행되는 경우에는 트랜잭션을 사용하는 방법이 적합하지 않습니다. 예를 들어 여행 계획 애플리케이션은 워크플로로 구현됩니다. 이 워크플로의 각 단계는 항공권 예약, 관리자 승인 대기, 항공권 결제로 구성될 수 있습니다. 이 과정에 며칠이 걸릴 수도 있으므로 항공권 예약 및 결제 단계를 같은 트랜잭션으로 묶는 방법은 적절하지 않습니다. 이 시나리오에서는 보정을 사용하여 이후 처리 과정에서 오류가 발생하면 워크플로의 예약 단계를 취소할 수 있습니다.

참고 항목

이 항목에서는 워크플로의 보정에 대해 설명합니다. 워크플로의 트랜잭션에 대한 자세한 내용은 트랜잭션TransactionScope를 참조하세요. 트랜잭션에 대한 자세한 내용은 System.TransactionsSystem.Transactions.Transaction을 참조하세요.

CompensableActivity 사용

CompensableActivity는 WF의 핵심 보정 활동입니다. 보정이 필요할 수 있는 작업을 수행하는 활동은 BodyCompensableActivity에 배치됩니다. 이 예제에서는 항공권 구매 예약 단계가 BodyCompensableActivity에 배치되고 예약 취소가 CompensationHandler에 배치됩니다. 워크플로의 CompensableActivity 바로 다음에 관리자 승인을 대기하고 항공권 구매 단계를 완료하는 두 활동이 옵니다. CompensableActivity를 완료한 이후에 오류 조건으로 인해 워크플로가 취소될 경우 CompensationHandler 처리기의 활동이 예약되고 항공권이 취소됩니다.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight()
        },
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

다음 예제는 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>

워크플로가 호출되면 다음 출력이 콘솔에 표시됩니다.

ReserveFlight: Ticket is reserved.ManagerApproval: Manager approval received.PurchaseFlight: Ticket is purchased.Workflow completed successfully with status: Closed.

참고 항목

이 항목에서 ReserveFlight와 같은 샘플 활동은 이름과 목적을 콘솔에 표시하여 보정이 발생하는 경우 활동의 실행 순서를 보여 줄 수 있습니다.

기본 워크플로 보정

기본적으로 워크플로가 취소되면 성공적으로 완료되었지만 아직 확인되거나 보정되지 않은 보정 가능한 활동에 대한 보정 논리가 실행됩니다.

참고 항목

CompensableActivity‘확인’되면 활동에 대한 보정을 더 이상 호출할 수 없습니다. 확인 프로세스에 대해서는 이 단원의 뒷부분에서 설명합니다.

이 예제에서는 항공권을 예약한 이후에 관리자 승인 단계 이전에 예외가 throw됩니다.

Activity wf = new Sequence()
{
    Activities =
    {
        new CompensableActivity
        {
            Body = new ReserveFlight(),
            CompensationHandler = new CancelFlight()
        },
        new SimulatedErrorCondition(),
        new ManagerApproval(),
        new PurchaseFlight()
    }
};

이 예제는 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();

워크플로가 호출되면 OnUnhandledException에서 호스트 애플리케이션이 시뮬레이션된 오류 조건 예외를 처리하고 워크플로가 취소되고 보정 논리가 호출됩니다.

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.

취소 및 CompensableActivity

BodyCompensableActivity에 있는 활동이 완료되지 않은 상태에서 활동이 취소되면 CancellationHandler의 활동이 실행됩니다.

참고 항목

CancellationHandlerBodyCompensableActivity에 있는 활동이 완료되지 않은 상태에서 활동이 취소되는 경우에만 호출됩니다. CompensationHandlerBodyCompensableActivity에 있는 활동이 성공적으로 완료된 다음 활동에 대해 보정이 호출되는 경우에만 실행됩니다.

CancellationHandler는 워크플로 작성자에게 적절한 취소 논리를 제공합니다. 다음 예제에서는 Body가 실행되는 동안 예외가 throw된 다음 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()
    }
};

이 예제는 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>

워크플로가 호출되면 OnUnhandledException에서 호스트 애플리케이션이 시뮬레이션된 오류 조건 예외를 처리하고 워크플로가 취소되고 CompensableActivity의 취소 논리가 호출됩니다. 이 예제에서는 보정 논리와 취소 논리가 다른 목표를 가지고 있습니다. Body가 성공적으로 완료되면 신용 카드가 청구되고 비행기가 예약되었으므로 보정은 두 단계 모두 실행 취소해야 합니다. (이 예제에서는 항공편을 취소하면 신용 카드 청구가 자동으로 취소됩니다.) 하지만 CompensableActivity가 취소되면 Body가 완료되지 않았으므로 취소를 가장 잘 처리하는 방법을 결정할 수 있도록 CancellationHandler의 논리가 필요합니다. 이 예제에서 CancellationHandler가 신용 카드 청구를 취소하지만 ReserveFlightBody에서 마지막 활동이었으므로 비행기를 취소하려고 하지 않습니다. ReserveFlightBody에서 마지막 활동이었으므로 성공적으로 완료되었다면 Body가 완료되었으므로 가능한 취소도 없습니다.

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. 취소에 대한 자세한 내용은 취소를 참조하세요.

보정 활동을 통한 명시적 보정

이전 단원에서는 암시적 보정에 대해 설명했습니다. 암시적 보정은 간단한 시나리오에 적합하지만, 보정 처리 일정에 대한 보다 명시적인 제어가 필요할 경우 Compensate 활동을 사용할 수 있습니다. Compensate 활동을 사용하여 보정 프로세스를 시작하려면 보정을 원하는 CompensationTokenCompensableActivity이 사용됩니다. Compensate 활동을 사용하면 완료되었지만 아직 확인되거나 보정되지 않은 CompensableActivity에 대한 보정을 시작할 수 있습니다. 예를 들어 Compensate 활동을 Catches 활동의 TryCatch 섹션에서 또는 CompensableActivity가 완료된 이후에 언제든지 사용할 수 있습니다. 이 예제에서는 Compensate 활동의 Catches 섹션에서 TryCatch 활동을 사용하여 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
                }
            }
        }
    }
};

이 예제는 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>

워크플로가 호출되면 다음 출력이 콘솔에 표시됩니다.

ReserveFlight: Ticket is reserved.SimulatedErrorCondition: Throwing an ApplicationException.CancelFlight: Ticket is canceled.Workflow completed successfully with status: Closed.

보정 확인

기본적으로 보정 가능한 활동은 완료된 이후에 언제든지 보정할 수 있습니다. 일부 시나리오에서는 이 방법이 적합하지 않을 수 있습니다. 이전 예제에서는 예약을 취소하기 위해 티켓 예약에 대한 보정을 수행했습니다. 하지만 항공권이 완료된 이후에는 이 보정 단계는 더 이상 유효하지 않습니다. 보정 가능한 활동을 확인하면 ConfirmationHandler가 지정한 활동이 호출됩니다. 예를 들어 보정을 수행하는 데 필요한 리소스를 릴리스할 수 있습니다. 보정 가능한 활동이 확인된 이후에는 활동을 보정할 수 없습니다. 이 경우 보정을 시도하면 InvalidOperationException 예외가 throw됩니다. 워크플로가 성공적으로 완료되면 완료되었지만 확인되거나 보정되지 않은 모든 보정 가능한 활동이 완료된 역순으로 확인됩니다. 이 예제에서는 항공권을 예약하고 구매한 후 완료하면 보정 가능한 활동이 확인됩니다. CompensableActivity를 확인하려면 Confirm 활동을 사용하고 확인할 CompensationTokenCompensableActivity을 지정합니다.

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
        }
    }
};

이 예제는 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>

워크플로가 호출되면 다음 출력이 콘솔에 표시됩니다.

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.

보정 활동 중첩

CompensableActivity를 다른 BodyCompensableActivity 섹션에 배치할 수 있습니다. CompensableActivity가 다른 CompensableActivity의 처리기에 배치되지 않을 수도 있습니다. 이는 부모 CompensableActivity의 책임입니다. 활동을 취소, 확인 또는 보정할 경우 성공적으로 완료되었지만 아직 확인 또는 보정되지 않은 보정 가능한 모든 자식 활동은 부모가 취소, 확인 또는 보정을 완료하기 전에 확인 또는 보정되어야 합니다. 이러한 내용이 명시적으로 모델링되지 않은 경우 부모가 취소 또는 보정 신호를 받으면 부모 CompensableActivity는 보정 가능한 활동을 암시적으로 보정합니다. 부모가 확인 신호를 받으면 부모는 보정 가능한 자식 활동을 암시적으로 확인합니다. 취소, 확인 또는 보정을 처리하는 논리가 부모 CompensableActivity의 처리기에 명시적으로 모델링되어 있을 경우 명시적으로 처리되지 않은 모든 자식은 암시적으로 확인됩니다.

참고 항목