

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

# 구독 워크플로 자습서 파트 3: 활동 구현
<a name="swf-sns-tutorial-implementing-activities"></a>

활동 코드에 일반적인 몇 가지 기능을 제공하는 기본 클래스부터 시작해 워크플로의 각 활동을 구현하려고 합니다.

**Topics**
+ [기본 활동 유형 정의](#defining-a-basic-activity-type)
+ [GetContactActivity 정의](#defining-getcontactactivity)
+ [SubscribeTopicActivity 정의](#defining-subscribetopicactivity)
+ [WaitForConfirmationActivity 정의](#defining-waitforconfirmationactivity)
+ [SendResultActivity 정의](#defining-sendresultactivity)
+ [다음 단계](#implementing-activities-next-steps)

## 기본 활동 유형 정의
<a name="defining-a-basic-activity-type"></a>

워크플로를 설계할 때 다음 활동을 식별했습니다.
+ `get_contact_activity`
+ `subscribe_topic_activity`
+ `wait_for_confirmation_activity`
+ `send_result_activity`

이제 이러한 각 활동을 구현합니다. 활동은 몇 가지 기능을 공유하므로 몇 가지 토대를 마련하고 공유할 수 있는 몇 가지 공통 코드를 만들어 보겠습니다. 이러한 공통 코드를 **BasicActivity**라고 하고 `basic_activity.rb`라는 새 파일에 정의해 보겠습니다.

다른 소스 파일에서처럼 `utils.rb`를 포함해 샘플 도메인을 설정하는 `init_domain` 함수에 액세스합니다.

```
   require_relative 'utils.rb' 
```

다음으로, 기본 활동 클래스와 각 활동에서 관심을 둘 몇 가지 공통 데이터를 선언합니다. 클래스의 속성에 활동의 [AWS::SimpleWorkflow::ActivityType](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SimpleWorkflow/ActivityType.html) 인스턴스, *이름* 및 *결과*를 저장합니다.

```
   class BasicActivity

     attr_accessor :activity_type
     attr_accessor :name
     attr_accessor :results
```

이러한 속성은 활동 이름과 Amazon SWF에 활동을 등록할 때 사용할 선택 버전 및 옵션 맵을 사용하는 클래스의 `initialize` 메서드에 정의된 인스턴스 데이터에 액세스합니다.******

```
     def initialize(name, version = 'v1', options = nil)

       @activity_type = nil
       @name = name
       @results = nil

       # get the domain to use for activity tasks.
       @domain = init_domain

       # Check to see if this activity type already exists.
       @domain.activity_types.each do | a |
         if (a.name == @name) && (a.version == version)
           @activity_type = a
         end
       end

       if @activity_type.nil?
         # If no options were specified, use some reasonable defaults.
         if options.nil?
           options = {
             # All timeouts are in seconds.
             :default_task_heartbeat_timeout => 900,
             :default_task_schedule_to_start_timeout => 120,
             :default_task_schedule_to_close_timeout => 3800,
             :default_task_start_to_close_timeout => 3600 }
         end
         @activity_type = @domain.activity_types.register(@name, version, options)
       end
     end
```

워크플로우 유형을 등록할 때처럼 활동 유형이 이미 등록된 경우에는 도메인의 [activity\_types](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SimpleWorkflow/Domain.html#activity_types-instance_method) 컬렉션을 살펴보고 활동 유형을 검색할 수 있습니다. 활동을 찾을 수 없는 경우에는 등록합니다.

또한 워크플로 유형처럼 등록 시 활동 유형과 함께 저장될 *기본 옵션*을 설정할 수 있습니다.

기본 활동으로 가져올 마지막 항목은 일관된 실행 방식입니다. 활동 작업을 가져오는 `do_activity` 메서드를 정의합니다. 표시된 것처럼 전달된 활동 작업을 사용해 `input` 인스턴스 속성을 통해 데이터를 수신할 수 있습니다.

```
     def do_activity(task)
       @results = task.input # may be nil
       return true
     end
   end
```

이 코드는 **BasicActivity** 클래스를 마무리합니다. 이제 이 코드를 사용해 활동 정의를 더욱 간단하고 일관되게 합니다.

## GetContactActivity 정의
<a name="defining-getcontactactivity"></a>

워크플로 실행 중 가장 먼저 실행되는 활동은 `get_contact_activity`로, 사용자의 Amazon SNS 주제 구독 정보를 검색합니다.

`get_contact_activity.rb`라는 새 파일을 생성하고 Amazon SWF에 전달할 문자열을 준비하는 데 사용할 `yaml`과 이 **GetContactActivity** 클래스의 기초로 사용할 `basic_activity.rb`가 모두 필요합니다.

```
   require 'yaml'
   require_relative 'basic_activity.rb'

   # **GetContactActivity** provides a prompt for the user to enter contact
   # information. When the user successfully enters contact information, the
   # activity is complete.
   class GetContactActivity < BasicActivity
```

활동 등록 코드를 **BasicActivity**에 넣기 때문에 **GetContactActivity**의 `initialize` 방법은 매우 간단합니다. 활동 이름 `get_contact_activity`를 사용해 기본 클래스 생성자를 호출하기만 하면 됩니다. 활동을 등록하는 데 이렇게 하기만 하면 됩니다.

```
     # initialize the activity
     def initialize
       super('get_contact_activity')
     end
```

이제 사용자의 이메일 및/또는 전화번호를 입력하라고 표시하는 `do_activity` 메서드를 정의합니다.

```
     def do_activity(task)
       puts ""
       puts "Please enter either an email address or SMS message (mobile phone) number to"
       puts "receive SNS notifications. You can also enter both to use both address types."
       puts ""
       puts "If you enter a phone number, it must be able to receive SMS messages, and must"
       puts "be 11 digits (such as 12065550101 to represent the number 1-206-555-0101)."

       input_confirmed = false
       while !input_confirmed
         puts ""
         print "Email: "
         email = $stdin.gets.strip

         print "Phone: "
         phone = $stdin.gets.strip

         puts ""
         if (email == '') && (phone == '')
           print "You provided no subscription information. Quit? (y/n)"
            confirmation = $stdin.gets.strip.downcase
            if confirmation == 'y'
              return false
            end
         else
            puts "You entered:"
            puts "  email: #{email}"
            puts "  phone: #{phone}"
            print "\nIs this correct? (y/n): "
            confirmation = $stdin.gets.strip.downcase
            if confirmation == 'y'
              input_confirmed = true
            end
         end
       end

       # make sure that @results is a single string. YAML makes this easy.
       @results = { :email => email, :sms => phone }.to_yaml
       return true
     end
   end
```

`do_activity` 끝에서는 사용자로부터 검색한 이메일 및 전화번호를 가져와 맵에 배치한 다음 `to_yaml`을 사용해 전체 맵을 YAML 문자열로 변환합니다. 이렇게 변환하는 데에는 중요한 이유가 있는데, 활동 완료 시 Amazon SWF에 전달하는 모든 결과는 *문자열 데이터여야* 하기 때문입니다. 객체를 YAML 문자열로 변환한 다음 다시 객체로 쉽게 변환하는 Ruby의 기능은 다행스럽게도 이러한 용도에 잘 맞습니다.

`get_contact_activity` 구현을 마칩니다. 이러한 데이터는 다음에 `subscribe_topic_activity` 구현 시 사용됩니다.

## SubscribeTopicActivity 정의
<a name="defining-subscribetopicactivity"></a>

이제 Amazon SNS에 대해 좀 더 자세히 알아보고 `get_contact_activity`가 생성한 정보를 사용해 Amazon SNS 주제를 구독하도록 사용자를 등록하는 활동을 생성합니다.

`subscribe_topic_activity.rb`라는 새 파일을 생성하고, `get_contact_activity`에 사용한 것과 동일한 요구 사항을 추가하고, 클래스를 선언한 다음 `initialize` 메서드를 제공합니다.

```
   require 'yaml'
   require_relative 'basic_activity.rb'

   # **SubscribeTopicActivity** sends an SMS / email message to the user, asking for
   # confirmation.  When this action has been taken, the activity is complete.
   class SubscribeTopicActivity < BasicActivity

     def initialize
       super('subscribe_topic_activity')
     end
```

지금까지 활동을 설정 및 등록하는 코드를 준비했으므로 일부 코드를 추가하여 Amazon SNS 주제를 생성할 차례입니다. 주제를 생성하려면 [AWS::SNS::Client](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Client.html) 객체의 [create\_topic](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Client.html#create_topic-instance_method) 메서드를 사용합니다.

전달된 Amazon SNS 클라이언트 객체를 가져오는 `create_topic` 메서드를 클래스에 추가합니다.

```
     def create_topic(sns_client)
       topic_arn = sns_client.create_topic(:name => 'SWF_Sample_Topic')[:topic_arn]

       if topic_arn != nil
         # For an SMS notification, setting `DisplayName` is *required*. Note that
         # only the *first 10 characters* of the DisplayName will be shown on the
         # SMS message sent to the user, so choose your DisplayName wisely!
         sns_client.set_topic_attributes( {
           :topic_arn => topic_arn,
           :attribute_name => 'DisplayName',
           :attribute_value => 'SWFSample' } )
       else
         @results = {
           :reason => "Couldn't create SNS topic", :detail => "" }.to_yaml
         return nil
       end

       return topic_arn
     end
```

주제의 Amazon 리소스 이름(ARN)이 있으면 클라이언트의 [set\_topic\_attributes](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Client.html#set_topic_attributes-instance_method) 메서드에 이 이름을 사용해 주제의 *DisplayName*을 설정합니다. 이 이름은 Amazon SNS에서 SMS 메시지를 전송하는 데 필요합니다.

마지막으로 `do_activity` 메서드를 정의합니다. 활동을 예약할 때 `input` 옵션을 통해 전달한 데이터를 모두 수집해 시작합니다. 앞서 언급한 것처럼 데이터는 `to_yaml`을 사용해 생성한 문자열로 전달해야 합니다. 데이터를 검색하면 `YAML.load`를 사용해 데이터를 Ruby 객체로 전환합니다.

다음은 입력 데이터를 검색하는 `do_activity`의 시작 부분입니다.

```
     def do_activity(task)
       activity_data = {
         :topic_arn => nil,
         :email => { :endpoint => nil, :subscription_arn => nil },
         :sms => { :endpoint => nil, :subscription_arn => nil },
       }

       if task.input != nil
         input = YAML.load(task.input)
         activity_data[:email][:endpoint] = input[:email]
         activity_data[:sms][:endpoint] = input[:sms]
       else
         @results = { :reason => "Didn't receive any input!", :detail => "" }.to_yaml
         puts("  #{@results.inspect}")
         return false
       end

       # Create an SNS client. This is used to interact with the service. Set the
       # region to $SMS_REGION, which is a region that supports SMS notifications
       # (defined in the file `utils.rb`).
       sns_client = AWS::SNS::Client.new(
         :config => AWS.config.with(:region => $SMS_REGION))
```

입력 데이터를 검색하지 못한 경우 수행할 작업이 별로 없으므로 활동을 실패로 처리합니다.

하지만 모든 것이 괜찮다고 가정하면 계속해서 `do_activity` 메서드를 작성하고,를 사용하여 Amazon SNS 클라이언트를 가져오고 AWS SDK for Ruby, 이를 `create_topic` 메서드에 전달하여 Amazon SNS 주제를 생성합니다.

```
       # Create the topic and get the ARN
       activity_data[:topic_arn] = create_topic(sns_client)

       if activity_data[:topic_arn].nil?
         return false
       end
```

여기서 짚고 넘어가야 할 부분이 몇 가지 있습니다.
+ Amazon SNS 클라이언트의 리전을 설정하는 데 [https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/Core/Configuration.html#with-instance_method](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/Core/Configuration.html#with-instance_method)를 사용합니다. SMS 메시지를 전송해야 하기 때문에 `utils.rb`에 선언한 SMS 지원 리전을 사용합니다.
+ `activity_data` 맵에 주제의 ARN을 저장합니다. ARN은 워크플로 내 *다음* 활동으로 전달될 데이터의 일부입니다.

마지막으로, 이 활동은 전달된 엔드포인트(이메일 및 SMS)를 사용해 Amazon SNS 주제를 구독하도록 사용자를 가입합니다. 사용자가 엔드포인트를 *둘 다* 입력할 필요는 없지만 둘 중 하나는 필요합니다.

```
       # Subscribe the user to the topic, using either or both endpoints.
       [:email, :sms].each do | x |
         ep = activity_data[x][:endpoint]
         # don't try to subscribe an empty endpoint
         if (ep != nil && ep != "")
           response = sns_client.subscribe( {
             :topic_arn => activity_data[:topic_arn],
             :protocol => x.to_s, :endpoint => ep } )
           activity_data[x][:subscription_arn] = response[:subscription_arn]
         end
       end
```

[AWS::SNS::Client.subscribe](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Client.html#subscribe-instance_method)는 주제 ARN과 *프로토콜*(해당 엔드포인트에 대한 `activity_data` 맵 키로 지능적으로 위장함)을 가져옵니다.

마지막으로 다음 활동에 대한 정보를 YAML 형식으로 다시 패키징합니다. 그러면 해당 정보를 Amazon SWF로 다시 보낼 수 있습니다.

```
       # if at least one subscription arn is set, consider this a success.
       if (activity_data[:email][:subscription_arn] != nil) or (activity_data[:sms][:subscription_arn] != nil)
         @results = activity_data.to_yaml
       else
         @results = { :reason => "Couldn't subscribe to SNS topic", :detail => "" }.to_yaml
         puts("  #{@results.inspect}")
         return false
       end
       return true
     end
   end
```

이것으로 `subscribe_topic_activity` 구현을 마칩니다. 다음으로, `wait_for_confirmation_activity`를 정의합니다.

## WaitForConfirmationActivity 정의
<a name="defining-waitforconfirmationactivity"></a>

사용자가 Amazon SNS 주제를 구독하도록 가입되면 사용자는 구독 요청을 확인해야 합니다. 이 경우 사용자가 이메일 또는 SMS 메시지로 확인할 때까지 대기합니다.

사용자가 구독을 확인하도록 대기하는 활동을 `wait_for_confirmation_activity`라고 하고 여기서 이 활동을 정의합니다. 시작하려면 `wait_for_confirmation_activity.rb`라는 새 파일을 생성해 이전 활동을 설정한 것처럼 설정합니다.

```
   require 'yaml'
   require_relative 'basic_activity.rb'

   # **WaitForConfirmationActivity** waits for the user to confirm the SNS
   # subscription.  When this action has been taken, the activity is complete. It
   # might also time out...
   class WaitForConfirmationActivity < BasicActivity

     # Initialize the class
     def initialize
       super('wait_for_confirmation_activity')
     end
```

다음으로, `do_activity` 메서드 정의를 시작하고 `subscription_data`라는 로컬 변수로 입력 데이터를 가져옵니다.

```
     def do_activity(task)
       if task.input.nil?
         @results = { :reason => "Didn't receive any input!", :detail => "" }.to_yaml
         return false
       end

       subscription_data = YAML.load(task.input)
```

이제 주제 ARN이 있으므로 [AWS::SNS::Topic](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Topic.html)의 새 인스턴스를 생성해 주제를 검색하여 해당 주제에 ARN을 전달합니다.

```
       topic = AWS::SNS::Topic.new(subscription_data[:topic_arn])

       if topic.nil?
         @results = {
           :reason => "Couldn't get SWF topic ARN",
           :detail => "Topic ARN: #{topic.arn}" }.to_yaml
         return false
       end
```

이제, 엔드포인트 중 하나를 사용해 사용자가 구독을 확인했는지 살펴보기 위해 주제를 확인합니다. 활동을 성공으로 간주하기 위해서는 엔드포인트 하나만 확인되면 됩니다.

Amazon SNS 주제는 해당 주제에 대한 [구독](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Topic.html#subscriptions-instance_method) 목록을 유지하고, 구독의 ARN이 `PendingConfirmation` 외의 다른 값으로 설정되어 있는지 확인하여 사용자가 특정 구독을 확인했는지 여부를 알아볼 수 있습니다.

```
       # loop until we get some indication that a subscription was confirmed.
       subscription_confirmed = false
       while(!subscription_confirmed)
         topic.subscriptions.each do | sub |
           if subscription_data[sub.protocol.to_sym][:endpoint] == sub.endpoint
             # this is one of the endpoints we're interested in. Is it subscribed?
             if sub.arn != 'PendingConfirmation'
               subscription_data[sub.protocol.to_sym][:subscription_arn] = sub.arn
               puts "Topic subscription confirmed for (#{sub.protocol}: #{sub.endpoint})"
               @results = subscription_data.to_yaml
               return true
             else
               puts "Topic subscription still pending for (#{sub.protocol}: #{sub.endpoint})"
             end
           end
         end
```

구독에 대한 ARN을 얻을 경우 활동의 결과 데이터에 저장해 YAML로 변환하고 `do_activity`에서 true를 반환합니다. 그러면 활동이 성공적으로 완료되었다는 신호를 보냅니다.

구독이 확인될 때까지 기다리는 데 시간이 걸릴 수 있으므로 가끔 활동 작업`record_heartbeat`에서를 호출합니다. 그러면 Amazon SWF에 활동이 계속 처리 중이고 해당 활동을 사용해 활동 진행 상황에 대한 업데이트를 제공할 수 있음을 알리는 신호를 보냅니다(진행 상황을 보고할 수 있는 파일 처리와 같은 작업을 수행하는 경우).

```
         task.record_heartbeat!(
           { :details => "#{topic.num_subscriptions_confirmed} confirmed, #{topic.num_subscriptions_pending} pending" })
         # sleep a bit.
         sleep(4.0)
       end
```

이것으로 `while` 루프를 마칩니다. 성공 없이 while 루프에서 빠져나오면 실패를 보고하고 `do_activity` 메서드를 마칩니다.

```
       if (subscription_confirmed == false)
         @results = {
           :reason => "No subscriptions could be confirmed",
           :detail => "#{topic.num_subscriptions_confirmed} confirmed, #{topic.num_subscriptions_pending} pending" }.to_yaml
         return false
       end
     end
   end
```

이것으로 `wait_for_confirmation_activity` 구현을 마칩니다. 정의할 활동이 `send_result_activity` 하나만 남았습니다.

## SendResultActivity 정의
<a name="defining-sendresultactivity"></a>

워크플로를 여기까지 진행했으면 Amazon SNS 주제를 구독하도록 사용자를 성공적으로 가입했고 사용자는 구독을 확인한 것입니다.

마지막 활동 `send_result_activity`는 사용자가 구독한 주제와 구독을 확인한 엔드포인트를 사용해 사용자에게 성공적인 주제 구독 확인을 보냅니다.

`send_result_activity.rb`라는 새 파일을 생성해 지금까지 모든 활동을 설정한 것처럼 설정합니다.

```
   require 'yaml'
   require_relative 'basic_activity.rb'

   # **SendResultActivity** sends the result of the activity to the screen, and, if
   # the user successfully registered using SNS, to the user using the SNS contact
   # information collected.
   class SendResultActivity < BasicActivity

     def initialize
       super('send_result_activity')
     end
```

`do_activity` 메서드 역시 유사하게 시작되어 워크플로우에서 입력 데이터를 가져와 YAML에서 변환한 다음 주제 ARN을 사용해 [AWS::SNS::Topic](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Topic.html) 인스턴스를 생성합니다.

```
     def do_activity(task)
       if task.input.nil?
         @results = { :reason => "Didn't receive any input!", :detail => "" }
         return false
       end

       input = YAML.load(task.input)

       # get the topic, so we publish a message to it.
       topic = AWS::SNS::Topic.new(input[:topic_arn])

       if topic.nil?
         @results = {
           :reason => "Couldn't get SWF topic",
           :detail => "Topic ARN: #{topic.arn}" }
         return false
       end
```

주제가 있으면 그 주제에 메시지를 [게시합니다](https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/SNS/Topic.html#publish-instance_method)(화면에도 동일하게 반영).

```
       @results = "Thanks, you've successfully confirmed registration, and your workflow is complete!"

       # send the message via SNS, and also print it on the screen.
       topic.publish(@results)
       puts(@results)

       return true
     end
   end
```

Amazon SNS 주제에 게시하면 해당 주제에 대해 존재하는 *모든* 구독 및 확인 엔드포인트에 제공한 메시지가 전송됩니다. 따라서 사용자가 이메일 및 SMS 번호 *둘 다*로 확인한 경우 사용자는 각 엔드포인트에 대해 하나씩 확인 메시지 2개를 수신합니다.

## 다음 단계
<a name="implementing-activities-next-steps"></a>

이것으로 `send_result_activity` 구현을 마칩니다. 이제, 활동 작업을 처리해 [구독 워크플로 자습서 파트 4: 활동 작업 Poller 구현](swf-sns-tutorial-implementing-activities-poller.md) 단원에서의 응답으로 활동을 시작할 수 있는 활동 애플리케이션에서 이러한 모든 활동을 함께 연결합니다.