We love sharing knowledge at Arkency. Education is in our DNA. We’re happy when our readers and customers are benefiting from that as well. And we’ve set a Slack bot celebrate on each such occasion!

Pandas and sales

SlackProxy, which is the name of our application, notifies us whenever we make a sale from our e-commerce solution, that is DPD. This is an extremely rewarding experience when launching a new product but also a reminder to keep up improving existing ones.

Cheers!

Initially SlackProxy was a Rails application deployed on our internal infrastructure, then moved to Heroku. Technically it is nothing more than a proxy that transforms incoming webhooks from DPD into formatted messages posted on dedicated Slack channel.

See yourself, this one of the controllers:

module SlackProxy

class SaleController < ApplicationController

def create

notifier = SaleNotifier.new

items = [params.fetch(:item_name1), params[:item_name2], params[:item_name3], params[:item_name4]].compact

notifier.call(params.fetch(:mc_gross), params.fetch(:coupon_code), params.fetch(:payer_email), params.fetch(:first_name), params.fetch(:last_name), items)

render nothing: true

end

end

end

Nothing much interesting in the controller. The notification part is in the SaleNotifier which does the formatting and posting with help of a library to chat with Slack API.

module SlackProxy

class SaleNotifier

def call(money, code, email, name, surname, items)

notifier = Slack::Notifier.new(

db.fetch("webhook_url"),

{

username: db.fetch("username"),

icon_emoji: ":panda_face:",

attachments: [{

fallback: "+#{money}$",

text: "+#{money}$",

color: 'good'

}]

}

)

if code.present?

main_message = "#{name} #{surname} (#{email}) bought #{items.join(", ")} with the following code: #{code}"

else

main_message = "#{name} #{surname} (#{email}) bought #{items.join(", ")}"

end

notifier.ping(main_message)

end



private



def db

Rails.application.secrets.fetch(:sale_slack_data)

end

end

end

Enter serverless

When AWS announced Lambda support for Ruby I was really excited about the possibilities it opens. Not that those possibilities were unreachable before — with Ruby it is just more fun. I knew what would be the first thing we happily move there and we already had most of the code 😅

In fact the way traffic shapes for SlackProxy is an ideal candidate for a Lambda deployment — huge spikes for several launch days and more peaceful pings on other days. Nothing latency–critical as well.

Lambda functions may be triggered by several AWS events. Be it a repository event from CodeCommit, an upload to S3 or and update from SQS. For us, web developers, a request coming to an API Gateway sounds most familiar. It is a good entry point to explore Lambda.

I figured that an “API Gateway to Rack” adapter would be a natural glue for any Ruby web application and was relieved to find it contributed by AWS. After all, Rails application is just a an elaborate mechanisms to turn env into [status, headers, body] .

Some resistance against Lambda has formed in the opinion that “you cannot run this in development”. I find this argument hard to defend when the boundary of you application ends on Rack. We already manage that well with existing tooling. And in production you may need different set of checks anyway.

Serverless Panda

Without any further ado here’s a rewrite of a notifier in form of a simplest Rack application:

require 'slack-notifier'



module SlackProxy

class SaleNotifier

def initialize(slack_webhook_url, slack_username)

@slack_webhook_url = slack_webhook_url

@slack_username = slack_username

end



def call(env)

params = Rack::Request.new(env).params

money = params.fetch('mc_gross')

code = params.fetch('coupon_code')

email = params.fetch('payer_email')

given_name = params.fetch_values('first_name', 'last_name').join(' ')

items = params.values_at('item_name1', 'item_name2', 'item_name3', 'item_name4').compact



send_message(money, given_name, email, items, code)

render_nothing

end



private

attr_reader :slack_webhook_url, :slack_username



def render_nothing

Rack::Response.new

end



def send_message(money, given_name, email, items, code)

notifier = Slack::Notifier.new(slack_webhook_url, {

username: slack_username,

icon_emoji: ":panda_face:",

attachments: [{

fallback: "+#{money}$",

text: "+#{money}$",

color: 'good'

}]

})

main_message = "#{given_name} (#{email}) bought #{items.join(", ")}"

main_message << " with the following code: #{code}" unless code.empty?

notifier.ping(main_message)

end

end

end

This comes with a handy config.ru that rack-lambda handler expects:

$LOAD_PATH << File.join(__dir__, 'lib')

require 'slack_proxy'



use Rack::CommonLogger

run SlackProxy::SaleNotifier.new(ENV['SLACK_WEBHOOK_URL'], ENV['SLACK_USERNAME'])

That makes it trivial to run such app in development with rackup. You can find full source code with unit and mutation tests at slack_proxy.

The deployment and less pleasant part

So far it did not mention how we get this code deployed. There are several options possible:

copy and paste in web editor

package and deliver via S3 with help of SAM CLI

on git push via CodePipeline

First option is fine for exploring the environment. It gets you up to speed without bothering much. In the long run, being accustomed to Continuous Delivery I’d favor CodePipeline. I did not figure it out just yet. At the moment we rely on SAM CLI, as described in Ruby announcement post.

The biggest obstacle for me so far was getting familiar with AWS services involved (IAM, API Gateway, Certificate Manager) and making sense out of the documentation. That is not something Lambda specific and I guess you’d have to face it when dealing with any AWS service. This was far for me from the Heroku-like experience.

What could also be problematic for particular deployments is getting some required dependencies on board. It might be more desirable to lean on AWS ecosystem more deeply in that case (i.e. consider Dynamo storage).

Should you try AWS Lambda with Ruby after all? Yes, go explore it!

Originally published at blog.arkency.com.