Tutorial de fluxo de trabalho de inscrição - Parte 3: Implementar as atividades - Amazon Simple Workflow Service

As traduções são geradas por tradução automática. Em caso de conflito entre o conteúdo da tradução e da versão original em inglês, a versão em inglês prevalecerá.

Tutorial de fluxo de trabalho de inscrição - Parte 3: Implementar as atividades

Agora, implementaremos cada uma das atividades no nosso fluxo de trabalho, começando com uma classe base que fornece alguns recursos comuns para o código de atividade.

Definir um tipo de atividade básica

Ao projetar o fluxo de trabalho, identificamos as seguintes atividades:

  • get_contact_activity

  • subscribe_topic_activity

  • wait_for_confirmation_activity

  • send_result_activity

Implementaremos cada uma dessas atividades agora. Como as nossas atividades compartilharão alguns recursos, faremos alguns trabalhos base e criaremos um código comum que possamos compartilhar. Chamaremos esse código de BasicActivity e o definiremos em um novo arquivo chamado basic_activity.rb.

Como acontece com os outros arquivos de origem, incluiremos utils.rb para acessar a função init_domain e configurar o domínio da amostra.

require_relative 'utils.rb'

Em seguida, declararemos a classe de atividade básica e alguns dados comuns que serão de nosso interesse para cada atividade. Salvaremos a instância AWS::SimpleWorkflow::ActivityType da atividade, bem como o nome e os resultados em atributos da classe.

class BasicActivity attr_accessor :activity_type attr_accessor :name attr_accessor :results

Esses atributos acessam dados de instância definidos no método initialize da classe, que recebe um nome de atividade e uma versão opcional e um mapa de opções a serem usados ao registrar a atividade com o Amazon SWF.

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

Como acontece com o registro do tipo de fluxo de trabalho, se um tipo de atividade já estiver registrado, poderemos recuperá-lo observando a coleção activity_types do domínio. Se a atividade não puder ser encontrada, ela será registrada.

Além disso, como em tipos de fluxo de trabalho, você pode definir opções padrão que são armazenadas com seu tipo de atividade quando você o registra.

A última coisa que nossa atividade básica obtém é uma maneira consistente de executá-la. Definiremos um método do_activity que usa uma tarefa de atividade. Conforme mostrado, podemos usar a tarefa de atividade transmitida para receber dados através de seu atributo de instância input.

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

Isso encerra a classe BasicActivity. Agora, vamos usá-la para tornar a definição das nossas atividades simples e consistente.

Definir GetContactActivity

A primeira atividade que é executada durante a execução de um fluxo de trabalho é get_contact_activity, que recupera as informações de assinatura do tópico do Amazon SNS do usuário.

Crie um novo arquivo chamado get_contact_activity.rb e exija ambos yaml, que usaremos para preparar uma cadeia de caracteres para passar para o Amazon SWF, e basic_activity.rb, que usaremos como base para essa classe GetContactActivity.

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

Como colocamos o código de registro de atividade em BasicActivity, o método initialize para GetContactActivity é bastante simples. Simplesmente chamamos o construtor da classe base com o nome da atividade, get_contact_activity. Isso é tudo o que é necessário para registrar nossa atividade.

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

Agora, vamos definir o método do_activity, que solicita o e-mail e/ou número de telefone do usuário.

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

No final de do_activity, usamos o e-mail e o número de telefone recuperados do usuário, colocamos esses dados em um mapa e, em seguida, usamos to_yaml para converter o mapa inteiro em uma string YAML. Há um motivo importante para isso: todos os resultados que você passar para o Amazon SWF ao concluir uma atividade devem ser apenas dados de string de caracteres. A habilidade do Ruby de converter objetos facilmente em strings YAML e depois novamente em objetos é, felizmente, bem adaptada para esse propósito.

Esse é o fim da implementação de get_contact_activity. Esses dados serão usados em seguida na implementação de subscribe_topic_activity.

Definir SubscribeTopicActivity

Agora, vamos nos aprofundar no Amazon SNS e criar uma atividade que use as informações geradas por get_contact_activity para inscrever o usuário em um tópico do Amazon SNS.

Crie um novo arquivo chamado subscribe_topic_activity.rb, adicione os mesmos requisitos que usamos para get_contact_activity, declare sua classe e forneça seu método 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

Agora que já temos o código para configurar e registrar a atividade, adicionaremos algum código para criar um tópico do Amazon SNS. Para fazer isso, usaremos o método create_topic do objeto AWS: :SNS: :Client.

Adicione o método create_topic à sua classe, que recebe um objeto de cliente Amazon SNS passado.

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

Quando tivermos o Amazon Resource Name (ARN) do tópico, poderemos usá-lo com o método set_topic_attributes do cliente Amazon SNS para definir o DisplayName do tópico, que é necessário para o envio de mensagens SMS com o Amazon SNS.

Por fim, definiremos o método do_activity. Começaremos coletando quaisquer dados que tenham sido transmitidos por meio da opção input quando a atividade foi agendada. Conforme mencionado anteriormente, isso deve ser transmitido como uma string, que nós criamos usando to_yaml. Ao recuperá-lo, usaremos YAML.load para transformar os dados em objetos Ruby.

Este é o início de do_activity, no qual recuperamos os dados de entrada.

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))

Se não recebermos nenhuma entrada, não haverá muito a fazer, então vamos simplesmente marcar a atividade como falha.

No entanto, supondo que tudo esteja bem, continuaremos a preencher nosso método do_activity, obteremos um cliente Amazon SNS com o AWS SDK for Ruby e o passaremos ao nosso método create_topic para criar o tópico 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

Há algumas coisas que merecem destaque aqui:

  • Usamos AWS.config.withpara definir a região do nosso cliente Amazon SNS. Como queremos enviar mensagens SMS, usamos a região habilitada para SMS que declaramos em utils.rb.

  • Salvamos o ARN do tópico em nosso mapa activity_data. Isso faz parte dos dados que serão transmitidos à próxima atividade no nosso fluxo de trabalho.

Por fim, essa atividade inscreve o usuário no tópico do Amazon SNS, usando os pontos de extremidade passados (e-mail e SMS). Não exigimos que o usuário insira ambos os endpoints, mas precisamos de pelo menos um.

# 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 usa o ARN do tópico, o protocolo (o que, habilmente, disfarçamos como a chave do mapa activity_data para o endpoint correspondente).

Por fim, reempacotamos as informações para a próxima atividade no formato YAML, para que possamos enviá-las de volta ao 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

Isso completa a implementação do subscribe_topic_activity. Em seguida, definiremos wait_for_confirmation_activity.

Definir WaitForConfirmationActivity

Depois que um usuário se inscrever em um tópico do Amazon SNS, ele ainda precisará confirmar a solicitação de inscrição. Nesse caso, aguardaremos que o usuário confirme por e-mail ou mensagem SMS.

A atividade que aguarda a confirmação da inscrição pelo usuário é chamada de wait_for_confirmation_activity, e nós a definiremos aqui. Para começar, crie um novo arquivo chamado wait_for_confirmation_activity.rb e configure-o como fizemos nas atividades anteriores.

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

Em seguida, vamos começar a definir o método do_activity e a recuperar todos os dados de entrada em uma variável local chamada de 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)

Agora que temos o ARN do tópico, podemos recuperar o tópico criando uma nova instância de AWS::SNS::Topic e transmitindo a ela o 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

Agora, verificamos o tópico para ver se o usuário confirmou a assinatura usando um dos endpoints. Só exigiremos que um endpoint tenha sido confirmado para considerar a atividade como bem-sucedida.

Um tópico do Amazon SNS mantém uma lista das assinaturas desse tópico, e podemos verificar se o usuário confirmou ou não uma assinatura específica, verificando se o ARN da assinatura está definido como algo diferente de 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

Se obtivermos um ARN para a assinatura, vamos salvá-lo nos dados do resultado da atividade, convertê-lo em YAML e retornar "true" de do_activity, que indica que a atividade foi concluída com êxito.

Como a espera pela confirmação de uma assinatura pode demorar um pouco, ocasionalmente chamaremos record_heartbeat na tarefa de atividade. Isso sinaliza para o Amazon SWF que a atividade ainda está sendo processada e também pode ser usado para fornecer atualizações sobre o progresso da atividade (se você estiver fazendo algo, como processar arquivos, para o qual possa relatar o progresso).

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

Isso finaliza nosso loop while. Se, de alguma forma, sairmos do loop sem sucesso, informaremos a falha e terminamos o método 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

Isso acaba com a implementação de wait_for_confirmation_activity. Temos apenas mais uma atividade para definir: send_result_activity.

Definir SendResultActivity

Se o fluxo de trabalho tiver progredido até aqui, teremos inscrito com êxito o usuário em um tópico do Amazon SNS e o usuário terá confirmado a inscrição.

Nossa última atividade, send_result_activity, envia ao usuário uma confirmação da inscrição bem-sucedida no tópico, usando o tópico no qual o usuário se inscreveu e o endpoint com o qual o usuário confirmou a assinatura.

Crie um novo arquivo chamado send_result_activity.rb e configure-o como configuramos todas as atividades até agora.

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

Nosso método do_activity também começa de forma semelhante, obtendo os dados de entrada do fluxo de trabalho, convertendo-os de YAML e depois usando o ARN do tópico para criar uma instância de 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

Quando tivermos o tópico, publicaremos uma mensagem nele (e também o ecoaremos na tela).

@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

A publicação em um tópico do Amazon SNS envia a mensagem que você forneceu a todos os pontos de extremidade inscritos e confirmados que existem para esse tópico. Portanto, se o usuário confirmar com um e-mail e também com um número de SMS, ele receberá duas mensagens de confirmação, uma em cada endpoint.

Próximas etapas

Isso completa a implementação de send_result_activity. Agora, você combinará todas essas atividades em um aplicativo de atividade que lida com as tarefas de atividades e que pode iniciar atividades em resposta, em Tutorial de fluxo de trabalho de inscrição - Parte 4: Implementar o agente de sondagem de tarefas de atividades.