구독 워크플로 자습서 파트 3: 활동 구현 - Amazon Simple Workflow Service

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

구독 워크플로 자습서 파트 3: 활동 구현

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

기본 활동 유형 정의

워크플로를 설계할 때 다음 활동을 식별했습니다.

  • 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 인스턴스, 이름결과를 저장합니다.

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 컬렉션을 살펴보고 활동 유형을 검색할 수 있습니다. 활동을 찾을 수 없는 경우에는 등록합니다.

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

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

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

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

GetContactActivity 정의

워크플로 실행 중 가장 먼저 실행되는 활동은 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에 활동 등록 코드를 넣었으므로 GetContactActivityinitialize 메서드는 매우 간단합니다. 활동 이름 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 정의

이제 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 객체의 create_topic 메서드를 사용합니다.

전달된 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 메서드에 이 이름을 사용해 주제의 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 메서드를 채우고, AWS SDK for Ruby를 사용하여 Amazon SNS 클라이언트를 불러오고, 데이터를 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 클라이언트의 리전을 설정하는 데 AWS.config.with를 사용합니다. 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는 주제 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 정의

사용자가 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의 새 인스턴스를 생성해 주제를 검색하여 해당 주제에 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 주제는 해당 주제에 대한 구독 목록을 유지하고, 구독의 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 정의

워크플로를 여기까지 진행했으면 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 인스턴스를 생성합니다.

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

주제가 있으면 그 주제에 메시지를 게시합니다(화면에도 동일하게 반영).

@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개를 수신합니다.

다음 단계

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