

기계 번역으로 제공되는 번역입니다. 제공된 번역과 원본 영어의 내용이 상충하는 경우에는 영어 버전이 우선합니다.

# 실패한 활동 재시도
<a name="features-retry"></a>

잠시 연결이 끊기는 등 일시적인 이유로 활동이 실패하는 경우가 가끔 있습니다. 딴 때는 활동이 성공할 수 있으므로 많은 경우 활동 실패를 처리하는 적절한 방법은 활동을 재시도하는 것입니다. 시도 횟수는 여러 번이 될 수 있습니다.

활동 재시도와 관련해 다양한 전략이 있지만, 워크플로의 세부 내용에 따라 가장 좋은 전략이 무엇인지 결정됩니다. 그러한 전략은 다음과 같은 세 가지 기본 범주로 구분됩니다.
+ 성공할 때까지 재시도하는 전략은 활동이 완료될 때까지 계속해서 재시도하는 것입니다.
+ 기하급수적 재시도 전략에서는 활동이 완료되거나 프로세스가 최대 시도 횟수와 같이 지정된 중단 지점에 이를 때까지 재시도 사이의 시간 간격을 기하급수적으로 늘립니다.
+ 사용자 지정 재시도 전략에서는 시도가 실패한 후에 활동을 재시도할지 여부, 재시도한다면 재시도 방법을 결정합니다.

다음 단원에서는 이러한 전략을 구현하는 방법에 관해 설명합니다. 예시 워크플로 작업자는 모두 단일 활동인 `unreliableActivity`를 사용하는데, 이 활동에서는 임의로 다음 중 하나를 수행합니다.
+ 즉시 완료
+ 의도적으로 제한 시간 값을 초과하여 실패
+ 의도적으로 `IllegalStateException`을 발생시켜 실패 

## 성공할 때까지 재시도하는 전략
<a name="features-retry-success"></a>

가장 단순한 재시도 전략은 결국 성공할 때까지 활동 재시도가 실패할 때마다 계속 재시도하는 것입니다. 기본 패턴은 다음과 같습니다.

1. 워크플로의 진입점 메서드에서 중첩된 `TryCatch` 또는 `TryCatchFinally` 클래스 구현합니다.

1. `doTry`에서 활동을 실행합니다.

1. 이 활동이 실패한 경우, 프레임워크에서 `doCatch`를 호출하면 진입점 메서드가 다시 실행됩니다.

1. 활동이 성공적으로 완료될 때까지 2-3단계를 반복합니다.

다음 워크플로에서는 성공할 떄까지 재시도하는 전략을 구현합니다. 이 워크플로 인터페이스는 `RetryActivityRecipeWorkflow`에서 구현되고 이 워크플로의 진입점인 `runUnreliableActivityTillSuccess`라는 메서드가 한 개 있습니다. 워크플로 작업자는 `RetryActivityRecipeWorkflowImpl`에서 다음과 같이 구현됩니다.

```
public class RetryActivityRecipeWorkflowImpl
    implements RetryActivityRecipeWorkflow {

    @Override
    public void runUnreliableActivityTillSuccess() {
        final Settable<Boolean> retryActivity = new Settable<Boolean>();

        new TryCatch() {
            @Override
            protected void doTry() throws Throwable {
                Promise<Void> activityRanSuccessfully
                    = client.unreliableActivity();
                setRetryActivityToFalse(activityRanSuccessfully, retryActivity);
            }

            @Override
            protected void doCatch(Throwable e) throws Throwable {
                retryActivity.set(true);
            }
        };
        restartRunUnreliableActivityTillSuccess(retryActivity);
    }

    @Asynchronous
    private void setRetryActivityToFalse(
            Promise<Void> activityRanSuccessfully,
            @NoWait Settable<Boolean> retryActivity) {
        retryActivity.set(false);
    }

    @Asynchronous
    private void restartRunUnreliableActivityTillSuccess(
            Settable<Boolean> retryActivity) {
        if (retryActivity.get()) {
            runUnreliableActivityTillSuccess();
        }
    }
}
```

워크플로는 다음과 같이 작동합니다.

1. `runUnreliableActivityTillSuccess`에서는 활동이 실패했는지 여부와 재시도할지 여부를 나타내는 데 사용되는 `retryActivity`라는 `Settable<Boolean>` 객체를 생성합니다. `Settable<T>`는 `Promise<T>`에서 파생된 것으로서 거의 동일한 방식으로 작동하지만, `Settable<T>` 객체의 값은 사용자가 수동으로 설정해 줍니다.

1. `runUnreliableActivityTillSuccess`에서는 익명의 중첩된 `TryCatch` 클래스를 구현하여 `unreliableActivity` 활동에서 발생하는 모든 예외를 처리합니다. 비동기식 코드에서 발생하는 예외를 처리하는 방법에 관한 자세한 내용은 [오류 처리](errorhandling.md) 단원을 참조하십시오.

1. `doTry`에서는 `activityRanSuccessfully`라는 `Promise<Void>` 객체를 반환하는 `unreliableActivity` 활동을 실행합니다.

1. `doTry`에서는 다음과 같이 파라미터가 두 개인 비동기식 `setRetryActivityToFalse` 메서드를 호출합니다.
   + `activityRanSuccessfully`에서는 `unreliableActivity` 활동에서 반환하는 `Promise<Void>` 객체를 받아들입니다.
   + `retryActivity`에서는 `retryActivity` 객체를 받아들입니다.

   `unreliableActivity`가 완료되면 `activityRanSuccessfully`가 준비 상태가 되고 `setRetryActivityToFalse`에서는 `retryActivity`를 false로 설정합니다. 완료되지 않으면 `activityRanSuccessfully`는 결코 준비 상태가 되지 않고 `setRetryActivityToFalse`는 실행되지 않습니다.

1. `unreliableActivity`에서 예외가 발생하면 프레임워크에서는 `doCatch`를 호출하여 예외 객체를 전달합니다. `doCatch`는 `retryActivity`를 true로 설정합니다.

1. `runUnreliableActivityTillSuccess`에서는 비동기식 `restartRunUnreliableActivityTillSuccess` 메서드를 호출한 후 이 메서드에 `retryActivity` 객체를 전달합니다. `retryActivity`이 `Promise<T>` 유형이므로 `restartRunUnreliableActivityTillSuccess`에서는 `retryActivity`가 준비 상태가 될 때까지 실행을 연기합니다. 실행은 `TryCatch`가 완료된 후에 이뤄집니다.

1. `retryActivity`가 준비 상태가 되면 `restartRunUnreliableActivityTillSuccess`에서 값을 추출합니다.
   + 값이 `false`이면 재시도가 성공한 것입니다. `restartRunUnreliableActivityTillSuccess`는 아무것도 하지 않고 재시도 시퀀스는 종료됩니다.
   + 값이 true이면 재시도가 실패한 것입니다. `restartRunUnreliableActivityTillSuccess`에서 `runUnreliableActivityTillSuccess`를 호출하여 다시 활동을 실행합니다.

1. `unreliableActivity`가 완료될 때까지 1-7단계가 반복됩니다.

**참고**  
`doCatch`에서는 예외를 처리하지 않고 단지 `retryActivity` 객체를 true로 설정하여 활동이 실패하였음을 나타냅니다. 재시도는 비동기식 `restartRunUnreliableActivityTillSuccess` 메서드에서 처리하는데, 이 메서드에서는 `TryCatch`가 완료될 때까지 실행을 연기합니다. 이 접근 방식을 사용하는 이유는 `doCatch`에서 활동을 재시도하면 이를 취소할 수 없기 때문입니다. `restartRunUnreliableActivityTillSuccess`에서 활동을 재시도하면 사용자는 취소 가능한 활동을 실행할 수 있습니다.

## 기하급수적 재시도 전략
<a name="features-retry-exponential"></a>

기하급수적 재시도 전략의 경우 지정된 시간, 즉 N초 후 프레임워크에서 실패한 활동을 다시 실행합니다. 이 시도가 실패하면 프레임워크에서는 2N초 후에, 이어서 4N초 후에 실행하는 식으로 계속해서 활동을 다시 실행합니다. 대기 시간이 상당히 길어질 수 있으므로 일반적으로 무한정 재시도를 계속하는 것보다는 일정 시점에서 중단하는 것이 좋습니다.

프레임워크에서는 다음과 같이 기하급수적 재시도 전략을 구현할 수 있는 세 가지 방식을 제공합니다.
+ `@ExponentialRetry` 주석은 가장 단순한 접근 방식이지만, 컴파일 시간에 재시도 구성 옵션을 설정해야 합니다.
+ `RetryDecorator` 클래스를 통해 런타임에 재시도 구성을 설정하고 필요에 따라 이를 변경할 수 있습니다.
+ `AsyncRetryingExecutor` 클래스를 통해 런타임에 재시도 구성을 설정하고 필요에 따라 이를 변경할 수 있습니다. 뿐만 아니라 프레임워크에서는 사용자 구현 `AsyncRunnable.run` 메서드를 호출하여 매번 재시도를 실행합니다.

모든 접근 방식에서는 다음 구성 옵션을 지원합니다. 이 옵션에서 시간 값은 초 단위입니다.
+ 최초 재시도 대기 시간
+ 다음과 같이 재시도 간격을 계산하는 데 사용되는 백오프 계수

  ```
  retryInterval = initialRetryIntervalSeconds * Math.pow(backoffCoefficient, numberOfTries - 2)
  ```

  기본값은 2.0입니다.
+ 최대 재시도 횟수. 기본값은 무제한입니다.
+ 최대 재시도 간격. 기본값은 무제한입니다.
+ 만료 시간. 프로세스의 총 시간이 이 값을 초과하면 재시도가 중단됩니다. 기본값은 무제한입니다.
+ 재시도 프로세스를 트리거할 예외. 기본적으로 모든 예외가 재시도 프로세스를 트리거합니다.
+ 재시도를 트리거하지 않을 예외. 기본적으로 어떤 예외도 배제되지 않습니다.

다음 단원에서는 기하급수적 재시도 전략을 구현하는 다양한 방법에 관해 설명합니다.

### @ExponentialRetry을 이용한 기하급수적 재시도
<a name="features-retry-exponential-annotation"></a>

활동에 대해 기하급수적 재시도 전략을 구현하는 가장 간단한 방법은 인터페이스 정의에서 활동에 대해 `@ExponentialRetry ` 주석을 붙이는 것입니다. 활동이 실패하면 프레임워크에서는 지정된 옵션 값에 따라 재시도 프로세스를 자동으로 처리합니다. 기본 패턴은 다음과 같습니다.

1. `@ExponentialRetry`를 적절한 활동에 붙이고 재시도 구성을 지정합니다.

1. 주석이 달린 활동이 실패하면 프레임워크에서는 주석의 인수에서 지정한 구성에 따라 활동을 자동으로 재시도합니다.

`ExponentialRetryAnnotationWorkflow` 워크플로 작업자는 `@ExponentialRetry` 주석을 사용하여 기하급수적 재시도 전략을 구현합니다. 이 작업자는 다음과 같이 인터페이스 정의가 `ExponentialRetryAnnotationActivities`에 구현되는 `unreliableActivity` 활동을 사용합니다.

```
@Activities(version = "1.0")
@ActivityRegistrationOptions(
    defaultTaskScheduleToStartTimeoutSeconds = 30,
    defaultTaskStartToCloseTimeoutSeconds = 30)
public interface ExponentialRetryAnnotationActivities {
    @ExponentialRetry(
        initialRetryIntervalSeconds = 5,
        maximumAttempts = 5,
        exceptionsToRetry = IllegalStateException.class)
    public void unreliableActivity();
}
```

`@ExponentialRetry`옵션에서는 다음과 같은 전략을 지정합니다.
+ 활동에서 `IllegalStateException`이 발생한 경우에만 재시도합니다.
+ 초기 대기 시간 5초를 사용합니다.
+ 재시도를 5회 이내로 제한합니다.

이 워크플로 인터페이스는 `RetryWorkflow`에서 구현되고 이 워크플로의 진입점인 `process`라는 메서드가 한 개 있습니다. 워크플로 작업자는 `ExponentialRetryAnnotationWorkflowImpl`에서 다음과 같이 구현됩니다.

```
public class ExponentialRetryAnnotationWorkflowImpl implements RetryWorkflow {
    public void process() {
        handleUnreliableActivity();
    }

    public void handleUnreliableActivity() {
        client.unreliableActivity();
    }
}
```

워크플로는 다음과 같이 작동합니다.

1. `process`에서는 동기식 `handleUnreliableActivity` 메서드를 실행합니다.

1. `handleUnreliableActivity`에서는 `unreliableActivity` 활동을 실행합니다.

`IllegalStateException` 예외가 발생하여 활동이 실패하면 프레임워크에서는 `ExponentialRetryAnnotationActivities`에 지정된 재시도 전략을 자동으로 실행합니다.

### RetryDecorator 클래스를 이용한 기하급수적 재시도
<a name="features-retry-exponential-decorator"></a>

`@ExponentialRetry`는 사용하기 간편합니다. 그러나 구성이 정적이고 컴파일 시에 설정되므로 프레임워크에서는 활동이 실패할 때마다 동일한 재시도 전략을 사용합니다. 런타임에 구성을 지정하고 필요에 따라 이를 변경할 수 있게 해주는 `RetryDecorator` 클래스를 사용하여 더 유연한 기하급수적 재시도 전략을 구현할 수 있습니다. 기본 패턴은 다음과 같습니다.

1. 재시도 구성을 지정하는 `ExponentialRetryPolicy` 객체를 만들고 구성합니다.

1. `RetryDecorator` 객체를 만들고 1단계의 `ExponentialRetryPolicy` 객체를 생성자에게 전달합니다.

1. 활동 클라이언트의 클래스 이름을 `RetryDecorator` 객체의 장식 메서드에 전달하여 장식자 객체를 활동에 추가합니다.

1. 활동을 실행합니다.

활동이 실패하면 프레임워크에서는 `ExponentialRetryPolicy` 객체의 구성에 따라 활동을 재시도합니다. 이 객체를 수정하여 필요에 따라 재시도 구성을 변경할 수 있습니다.

**참고**  
`@ExponentialRetry`주석 및 `RetryDecorator` 클래스는 상호 배타적입니다. `@ExponentialRetry` 주석에서 지정한 재시도 정책을 동적으로 재정의하는 데 `RetryDecorator`를 사용할 수는 없습니다.

다음 워크플로 구현에서는 `RetryDecorator` 클래스를 사용하여 기하급수적 재시도 전략을 구현하는 방법을 보여줍니다. 이 구현에서는 `@ExponentialRetry` 주석이 없는 `unreliableActivity` 활동을 사용합니다. 이 워크플로 인터페이스는 `RetryWorkflow`에서 구현되고 이 워크플로의 진입점인 `process`라는 메서드가 한 개 있습니다. 워크플로 작업자는 `DecoratorRetryWorkflowImpl`에서 다음과 같이 구현됩니다.

```
public class DecoratorRetryWorkflowImpl implements RetryWorkflow {
   ...
  public void process() {
      long initialRetryIntervalSeconds = 5;
      int maximumAttempts = 5;
      ExponentialRetryPolicy retryPolicy = new ExponentialRetryPolicy(
              initialRetryIntervalSeconds).withMaximumAttempts(maximumAttempts);

      Decorator retryDecorator = new RetryDecorator(retryPolicy);
      client = retryDecorator.decorate(RetryActivitiesClient.class, client);
      handleUnreliableActivity();
  }

  public void handleUnreliableActivity() {
      client.unreliableActivity();
  }
}
```

워크플로는 다음과 같이 작동합니다.

1. `process`에서는 다음 방법으로 `ExponentialRetryPolicy` 객체를 생성 및 구성합니다.
   + 초기 재시도 간격을 생성자에게 전달
   + 객체의 `withMaximumAttempts` 메서드를 호출하여 최대 시도 횟수를 5로 설정. `ExponentialRetryPolicy`에서는 다른 구성 옵션을 지정하는 데 사용할 수 있는 다른 `with` 객체를 노출합니다.

1. `process`에서는 `retryDecorator`이라는 `RetryDecorator` 객체를 만들고 1단계의 `ExponentialRetryPolicy` 객체를 생성자에게 전달합니다.

1. `process`에서는 `retryDecorator.decorate` 메서드를 호출한 후 이 메서드에 활동 클라이언트의 클래스 이름을 전달하여 활동에 장식자를 추가합니다.

1. `handleUnreliableActivity`에서는 활동을 실행합니다.

활동이 실패하면 프레임워크에서는 1단계에서 지정한 구성에 따라 활동을 재시도합니다.

**참고**  
`ExponentialRetryPolicy` 클래스의 `with` 메서드 중 몇 가지, 즉 `setBackoffCoefficient`, `setMaximumAttempts`, `setMaximumRetryIntervalSeconds` 및 `setMaximumRetryExpirationIntervalSeconds`는 사용자가 호출하여 언제든지 해당 구성 옵션을 변경할 수 있는 해당 `set` 메서드가 있습니다.

### AsyncRetryingExecutor 클래스를 이용한 기하급수적 재시도
<a name="features-retry-exponential-async"></a>

`RetryDecorator` 클래스에서는 재시도 프로세스 구성과 관련해 `@ExponentialRetry`보다 더 많은 유연성을 제공하지만, 프레임워크에서는 `ExponentialRetryPolicy` 객체의 현재 구성에 따라 여전히 재시도를 자동으로 실행합니다. 더 유연한 접근 방식은 `AsyncRetryingExecutor` 클래스를 사용하는 것입니다. 런타임에 재시도 프로세스를 구성할 수 있게 해줄 뿐 아니라 프레임워크에서는 단순히 활동을 실행하는 대신에 사용자 구현 `AsyncRunnable.run` 메서드를 호출하여 각 재시도를 실행합니다.

기본 패턴은 다음과 같습니다.

1. 재시도 구성을 지정할 `ExponentialRetryPolicy` 객체를 만들고 구성합니다.

1. `AsyncRetryingExecutor` 객체를 생성하여 이 객체에 `ExponentialRetryPolicy` 객체와 워크플로 클록의 인스턴스를 전달합니다.

1.  익명의 중첩된 `TryCatch` 또는 `TryCatchFinally` 클래스를 구현합니다.

1. 익명의 `AsyncRunnable` 클래스를 구현하고 `run` 메서드를 재정의하여 활동 실행을 위한 사용자 지정 코드를 구현합니다.

1.  `doTry`를 재정의하여 `AsyncRetryingExecutor` 객체의 `execute` 메서드를 호출한 후 이 메서드에 단계 4의 `AsyncRunnable` 클래스를 전달합니다. `AsyncRetryingExecutor` 객체에서는 `AsyncRunnable.run`을 호출하여 활동을 실행합니다.

1. 활동이 실패하면 `AsyncRetryingExecutor` 객체에서는 1단계에서 지정한 재시도 정책에 따라 `AsyncRunnable.run` 메서드를 다시 호출합니다.

다음 워크플로에서는 `AsyncRetryingExecutor` 클래스를 사용하여 기하급수적 재시도 전략을 구현하는 방법을 보여줍니다. 이 워크플로에서는 앞서 설명한 `DecoratorRetryWorkflow` 워크플로 동일한 `unreliableActivity` 활동을 사용합니다. 이 워크플로 인터페이스는 `RetryWorkflow`에서 구현되고 이 워크플로의 진입점인 `process`라는 메서드가 한 개 있습니다. 워크플로 작업자는 `AsyncExecutorRetryWorkflowImpl`에서 다음과 같이 구현됩니다.

```
public class AsyncExecutorRetryWorkflowImpl implements RetryWorkflow {
  private final RetryActivitiesClient client = new RetryActivitiesClientImpl();
  private final DecisionContextProvider contextProvider = new DecisionContextProviderImpl();
  private final WorkflowClock clock = contextProvider.getDecisionContext().getWorkflowClock();

  public void process() {
      long initialRetryIntervalSeconds = 5;
      int maximumAttempts = 5;
      handleUnreliableActivity(initialRetryIntervalSeconds, maximumAttempts);
  }
  public void handleUnreliableActivity(long initialRetryIntervalSeconds, int maximumAttempts) {

      ExponentialRetryPolicy retryPolicy = new ExponentialRetryPolicy(initialRetryIntervalSeconds).withMaximumAttempts(maximumAttempts);
      final AsyncExecutor executor = new AsyncRetryingExecutor(retryPolicy, clock);

      new TryCatch() {
          @Override
          protected void doTry() throws Throwable {
              executor.execute(new AsyncRunnable() {
                  @Override
                  public void run() throws Throwable {
                      client.unreliableActivity();
                  }
              });
          }
          @Override
          protected void doCatch(Throwable e) throws Throwable {
          }
      };
  }
}
```

워크플로는 다음과 같이 작동합니다.

1. `process`에서는 `handleUnreliableActivity` 메서드를 호출한 후 이 메서드에 구성 설정을 전달합니다.

1. `handleUnreliableActivity`에서는 1단계의 구성 설정을 사용하여 `ExponentialRetryPolicy`인 `retryPolicy`를 만듭니다.

1. `handleUnreliableActivity`에서는 `AsyncRetryExecutor` 객체인 `executor`를 생성하여 2단계의 `ExponentialRetryPolicy` 객체와 워크플로 클록의 인스턴스를 생성자에게 전달합니다.

1.  `handleUnreliableActivity`에서는 익명의 중첩된 `TryCatch` 클래스를 구현하고 `doTry` 및 `doCatch` 메서드를 재정의하여 재시도를 실행하고 모든 예외를 처리합니다.

1. `doTry`에서는 익명의 `AsyncRunnable` 클래스를 구현하고 `run` 메서드를 재정의하여 `unreliableActivity` 실행을 위한 사용자 지정 코드를 구현합니다. 간소화를 위해 `run`에서는 활동을 수행할 뿐이지만 사용자는 상황에 따라 적절하게 더 정교한 접근 방식을 구현할 수 있습니다.

1. `doTry`에서는 `executor.execute`를 호출한 후 이 메서드에 `AsyncRunnable` 객체를 전달합니다. `execute`에서는 `AsyncRunnable` 객체의 `run` 메서드를 호출하여 활동을 실행합니다.

1. 활동이 실패하면 실행기에서는 `retryPolicy` 객체 구성에 따라 `run`을 다시 호출합니다.

`TryCatch` 클래스를 사용하여 오류를 처리하는 방법에 관한 자세한 내용은 [AWS Flow Framework Java 예외](errorhandling.exceptions.md) 단원을 참조하십시오.

## 사용자 지정 재시도 전략
<a name="custom-retry-strategy"></a>

실패한 활동을 재시도하기 위한 가장 유연한 접근 방식은 사용자 지정 전략으로서, 성공할 때까지 재시도하는 전략과 흡사하게 이 전략에서는 재시도를 실행하는 비동기식 메서드를 반복적으로 호출합니다. 그러나 사용자는 단순히 활동을 다시 실행하는 대신에 연속적인 재시도 실행 여부 및 방법을 결정하는 사용자 지정 로직을 구현합니다. 기본 패턴은 다음과 같습니다.

1. 활동이 실패했는지 여부를 나타내는 데 사용되는 `Settable<T>` 상태 객체를 생성합니다.

1. 중첩된 `TryCatch` 또는 `TryCatchFinally` 클래스를 구현합니다.

1. `doTry`에서는 활동을 실행합니다.

1. 활동이 실패하면 `doCatch`에서는 상태 객체가 활동이 실패했음을 나타내도록 설정합니다.

1. 비동기식 실패 처리 메서드를 호출한 후 이 메서드에 상태 객체를 전달합니다. 이 메서드에서는 `TryCatch` 또는 `TryCatchFinally`가 완료될 때까지 실행을 연기합니다.

1. 실패 처리 메서드에서는 활동을 재시도할지 여부, 그리고 재시도한다면 언제할지 그 시점을 결정합니다.

다음 워크플로에서는 사용자 지정 재시도 전략을 구현하는 방법을 보여줍니다. 이 워크플로에서는 `DecoratorRetryWorkflow` 및 `AsyncExecutorRetryWorkflow` 워크플로와 동일한 `unreliableActivity` 활동을 사용합니다. 이 워크플로 인터페이스는 `RetryWorkflow`에서 구현되고 이 워크플로의 진입점인 `process`라는 메서드가 한 개 있습니다. 워크플로 작업자는 `CustomLogicRetryWorkflowImpl`에서 다음과 같이 구현됩니다.

```
public class CustomLogicRetryWorkflowImpl implements RetryWorkflow {
  ...
  public void process() {
      callActivityWithRetry();
  }
  @Asynchronous
  public void callActivityWithRetry() {
      final Settable<Throwable> failure = new Settable<Throwable>();
      new TryCatchFinally() {
          protected void doTry() throws Throwable {
              client.unreliableActivity();
          }
          protected void doCatch(Throwable e) {
              failure.set(e);
          }
          protected void doFinally() throws Throwable {
              if (!failure.isReady()) {
                  failure.set(null);
              }
          }
      };
      retryOnFailure(failure);
  }
  @Asynchronous
  private void retryOnFailure(Promise<Throwable> failureP) {
      Throwable failure = failureP.get();
      if (failure != null && shouldRetry(failure)) {
          callActivityWithRetry();
      }
  }
  protected Boolean shouldRetry(Throwable e) {
      //custom logic to decide to retry the activity or not
      return true;
  }
}
```

워크플로는 다음과 같이 작동합니다.

1. `process`에서는 비동기식 `callActivityWithRetry` 메서드를 실행합니다.

1. `callActivityWithRetry`에서는 활동이 실패했는지 여부를 나타내는 데 사용되는 실패라는 이름의 `Settable<Throwable>` 객체를 생성합니다. `Settable<T>`는 `Promise<T>`에서 파생된 것으로서 거의 동일한 방식으로 작동하지만, `Settable<T>` 객체의 값은 사용자가 수동으로 설정해 줍니다.

1. `callActivityWithRetry`에서는 익명의 중첩된 `TryCatchFinally` 클래스를 구현하여 `unreliableActivity`에서 발생하는 모든 예외를 처리합니다. 비동기식 코드에서 발생하는 예외를 처리하는 방법에 관한 자세한 내용은 [AWS Flow Framework Java 예외](errorhandling.exceptions.md) 단원을 참조하십시오.

1. `doTry`에서는 `unreliableActivity`를 실행합니다.

1. `unreliableActivity`에서 예외가 발생하면 프레임워크에서는 `doCatch`를 호출하여 예외 객체를 전달합니다. `doCatch`에서는 `failure`를 예외 객체로 설정합니다. 이로써 활동이 실패하였음을 나타내고 객체를 준비 상태로 둡니다.

1. `doFinally`에서는 `failure`가 준비 상태인지를 점검합니다. 준비 상태는 `failure`를 `doCatch`에서 설정한 경우에만 true가 됩니다.
   + `failure`가 준비되면 `doFinally`는 아무 작업도 수행하지 않습니다.
   + `failure`가 준비 상태가 아니면 완료된 활동과 `doFinally`에서는 실패를 `null`로 설정합니다.

1. `callActivityWithRetry`에서는 비동기식 `retryOnFailure` 메서드를 호출한 후 이 메서드에 실패를 전달합니다. 실패는 `Settable<T>` 유형이므로 `callActivityWithRetry`에서는 실패가 준비 상태가 될 때까지 실행을 연기합니다. 실행은 `TryCatchFinally`가 완료된 후에 이루어집니다.

1. `retryOnFailure`에서는 실패에서 값을 가져옵니다.
   + 실패가 null로 설정되어 있으면 재시도가 성공한 것입니다. `retryOnFailure`는 아무것도 하지 않고, 이로써 재시도 프로세스는 종료됩니다.
   + 실패가 예외 객체로 설정되고 `shouldRetry`에서 true를 반환하면 `retryOnFailure`에서는 `callActivityWithRetry`를 호출하여 활동을 재시도합니다.

     `shouldRetry`에서는 실패한 활동을 재시도할지 여부를 결정하는 사용자 지정 로직을 구현합니다. 간소화를 위해 `shouldRetry`에서는 항상 `true`를 반환하고 `retryOnFailure`에서는 즉시 활동을 실행하지만 사용자는 필요에 따라 더 정교한 로직을 구현할 수 있습니다.

1. `unreliableActivity`가 완료되거나 `shouldRetry`에서 프로세스를 중단하기로 결정할 때까지 2\$18단계가 반복됩니다.

**참고**  
`doCatch`에서는 재시도 프로세스를 처리하지 않고 단지 활동이 실패하였음을 나타내도록 실패를 설정합니다. 재시도 프로세스는 비동기식 `retryOnFailure` 메서드에서 처리하는데, 이 메서드에서는 `TryCatch`가 완료될 때까지 실행을 연기합니다. 이 접근 방식을 사용하는 이유는 `doCatch`에서 활동을 재시도하면 이를 취소할 수 없기 때문입니다. `retryOnFailure`에서 활동을 재시도하면 사용자는 취소 가능한 활동을 실행할 수 있습니다.