Going Serverless for Shopify Plugin/App Development

By Amit Bisht

We Currently shifted our Shopify app from a server to a function in the cloud, on AWS that's called a lambda function.

Why we did it?

The app is built for Shopify and a Shopify solution is automatically managed, everything from server scaling, balancing the load, updating security patches, etc is managed by Shopify. We wanted the same model for our app, where we would just focus on adding new features and scaling etc were managed automatically.

We choose Lambda as it fitted our requirement perfectly. We used a combination of AWS API gateway, Lambda functions, and cloud-watch.

We needed a URL to hit from the Shopify Flow app and then the server would perform the necessary operations.<br/><br/>

API GATEWAY

AWS API gateway provides easy to build and use end-points. All you need to do is create a resource (that will become the URL) and add methods (HTTP methods - Get, Post, etc). For each method, you add you will be provided a UI to control the data flow with four sections (Method request, integration request, integration response, method response). The section method request is used for mapping data in a format that we will consume.

We can create a model schema to inform about the data that will be passed by the client as attach it to request body section inside method request

Example model-

{

"type":"object",

"properties":{

"order_number":{"type":"string"},

"shop_domain":{"type":"string"},

"order_id":{"type":"string"},

"key": {"type":"string"}

},

"title":"Input"

}

Integration request section contains the actual logic integration with the API, in our case, we will choose lambda and add the name of our lambda.

LAMBDA-

Lambda is a function that runs on a trigger, in our case the API, and the data passed to the API gets to the function as a parameter.

We choose ruby as our build language for the great inbuilt ruby library- NET/HTTP. In our use case, we need to make some external API calls through a lambda.

Example of making get request -

uri = URI(‘www.myapi.com/get_me_data’)

req = Net::HTTP::Get.new(uri)

req['Authorization'] = 'Basic XXXXXXXXXX'

res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) {|http|

http.request(req)

}

JSON.parse(res.body)

Example of making a post request -

uri = URI("www.myapi.com/post_my_data")

req = Net::HTTP::Post.new(uri)

req['Authorization'] = 'Basic XXXXXX'

req['Content-Type']= 'application/json'

req.body = obj_to_be_send.to_json

res = Net::HTTP.start(uri.hostname, uri.port, :use_ssl => true) {|http|

http.request(req)

}

body = JSON.parse(res.body)

Beware of the ruby JSON.parse as it will randomly through error for some API response due to encoding problems, you can use force_encoding("utf-8") function to get the correct format and for it to parse JSON correctly.

CLOUD WATCH

Lambda function by default puts it execution flow logs in cloud watch and every time we do some changes and save the lambda a new log set is created and will contain all logs till changes are made again.

To put our app custom logs, for example- we need to log the response that came from the API(s) that we called from the lambda we just need to print it to stdout, which in ruby is using ‘puts’, but you need to create a special IAM role for land for custom logging while following permissions-

{ "Statement": [ { "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Effect": "Allow", "Resource": "arn:aws:logs:*:*:*" } ] }

Thus we were able to remove the whole extra work or deploying, scaling and managing a server, using lambda we created a solution the is automatically scalable and very economical too.